diff --git a/src/Entity.c b/src/Entity.c index 2d91f6e05..f578635a5 100644 --- a/src/Entity.c +++ b/src/Entity.c @@ -67,11 +67,14 @@ static PackedCol Entity_GetCol(struct Entity* e) { } void Entity_Init(struct Entity* e) { + static const String model = String_FromConst("humanoid"); e->ModelScale = Vec3_Create1(1.0f); - e->uScale = 1.0f; - e->vScale = 1.0f; + e->uScale = 1.0f; + e->vScale = 1.0f; + e->StepSize = 0.5f; e->SkinNameRaw[0] = '\0'; e->DisplayNameRaw[0] = '\0'; + Entity_SetModel(e, &model); } Vec3 Entity_GetEyePosition(struct Entity* e) { @@ -316,6 +319,194 @@ void Entity_SetName(struct Entity* e, const String* name) { } +/*########################################################################################################################* +*------------------------------------------------------Entity skins-------------------------------------------------------* +*#########################################################################################################################*/ +static struct Entity* Entity_FirstOtherWithSameSkinAndFetchedSkin(struct Entity* except) { + struct Entity* e; + String skin, eSkin; + int i; + + skin = String_FromRawArray(except->SkinNameRaw); + for (i = 0; i < ENTITIES_MAX_COUNT; i++) { + if (!Entities.List[i] || Entities.List[i] == except) continue; + + e = Entities.List[i]; + eSkin = String_FromRawArray(e->SkinNameRaw); + if (e->FetchedSkin && String_Equals(&skin, &eSkin)) return e; + } + return NULL; +} + +/* Copies skin data from another entity */ +static void Entity_CopySkin(struct Entity* dst, struct Entity* src) { + String skin; + dst->TextureId = src->TextureId; + dst->SkinType = src->SkinType; + dst->uScale = src->uScale; + dst->vScale = src->vScale; + + /* Custom mob textures */ + dst->MobTextureId = GFX_NULL; + skin = String_FromRawArray(dst->SkinNameRaw); + if (Utils_IsUrlPrefix(&skin, 0)) dst->MobTextureId = dst->TextureId; +} + +/* Resets skin data for the given entity */ +static void Entity_ResetSkin(struct Entity* e) { + e->uScale = 1.0f; e->vScale = 1.0f; + e->MobTextureId = GFX_NULL; + e->TextureId = GFX_NULL; + e->SkinType = SKIN_64x32; +} + +/* Copies or resets skin data for all entity with same skin */ +static void Entity_SetSkinAll(struct Entity* source, bool reset) { + struct Entity* e; + String skin, eSkin; + int i; + + skin = String_FromRawArray(source->SkinNameRaw); + for (i = 0; i < ENTITIES_MAX_COUNT; i++) { + if (!Entities.List[i]) continue; + + e = Entities.List[i]; + eSkin = String_FromRawArray(e->SkinNameRaw); + if (!String_Equals(&skin, &eSkin)) continue; + + if (reset) { + Entity_ResetSkin(e); + } else { + Entity_CopySkin(e, source); + } + } +} + +/* Clears hat area from a skin bitmap if it's completely white or black, + so skins edited with Microsoft Paint or similiar don't have a solid hat */ +static void Entity_ClearHat(Bitmap* bmp, uint8_t skinType) { + int sizeX = (bmp->Width / 64) * 32; + int yScale = skinType == SKIN_64x32 ? 32 : 64; + int sizeY = (bmp->Height / yScale) * 16; + int x, y; + + /* determine if we actually need filtering */ + for (y = 0; y < sizeY; y++) { + BitmapCol* row = Bitmap_GetRow(bmp, y) + sizeX; + for (x = 0; x < sizeX; x++) { + if (row[x].A != 255) return; + } + } + + /* only perform filtering when the entire hat is opaque */ + uint32_t white = PackedCol_ARGB(255, 255, 255, 255); + uint32_t black = PackedCol_ARGB(0, 0, 0, 255); + for (y = 0; y < sizeY; y++) { + uint32_t* row = Bitmap_RawRow(bmp, y) + sizeX; + for (x = 0; x < sizeX; x++) { + uint32_t pixel = row[x]; + if (pixel == white || pixel == black) row[x] = 0; + } + } +} + +/* Ensures skin is a power of two size, resizing if needed. */ +static void Entity_EnsurePow2(struct Entity* e, Bitmap* bmp) { + uint32_t stride; + int width, height; + Bitmap scaled; + int y; + + width = Math_NextPowOf2(bmp->Width); + height = Math_NextPowOf2(bmp->Height); + if (width == bmp->Width && height == bmp->Height) return; + + Bitmap_Allocate(&scaled, width, height); + e->uScale = (float)bmp->Width / width; + e->vScale = (float)bmp->Height / height; + stride = bmp->Width * 4; + + for (y = 0; y < bmp->Height; y++) { + BitmapCol* src = Bitmap_GetRow(bmp, y); + BitmapCol* dst = Bitmap_GetRow(&scaled, y); + Mem_Copy(dst, src, stride); + } + + Mem_Free(bmp->Scan0); + *bmp = scaled; +} + +static void Entity_CheckSkin(struct Entity* e) { + struct Entity* first; + String url, skin = String_FromRawArray(e->SkinNameRaw); + + struct HttpRequest item; + struct Stream mem; + Bitmap bmp; + ReturnCode res; + + if (!e->FetchedSkin && e->Model->UsesSkin) { + first = Entity_FirstOtherWithSameSkinAndFetchedSkin(e); + if (!first) { + Http_AsyncGetSkin(&skin); + } else { + Entity_CopySkin(e, first); + } + e->FetchedSkin = true; + } + + if (!Http_GetResult(&skin, &item)) return; + if (!item.Success) { Entity_SetSkinAll(e, true); return; } + Stream_ReadonlyMemory(&mem, item.Data, item.Size); + + if ((res = Png_Decode(&bmp, &mem))) { + url = String_FromRawArray(item.URL); + Logger_Warn2(res, "decoding", &url); + Mem_Free(bmp.Scan0); return; + } + + Gfx_DeleteTexture(&e->TextureId); + Entity_SetSkinAll(e, true); + Entity_EnsurePow2(e, &bmp); + e->SkinType = Utils_CalcSkinType(&bmp); + + if (bmp.Width > Gfx.MaxTexWidth || bmp.Height > Gfx.MaxTexHeight) { + Chat_Add1("&cSkin %s is too large", &skin); + } else if (e->SkinType != SKIN_INVALID) { + if (e->Model->UsesHumanSkin) Entity_ClearHat(&bmp, e->SkinType); + e->TextureId = Gfx_CreateTexture(&bmp, true, false); + Entity_SetSkinAll(e, false); + } + Mem_Free(bmp.Scan0); +} + +/* Returns true if no other entities are sharing this skin texture */ +static bool Entity_CanDeleteTexture(struct Entity* except) { + int i; + if (!except->TextureId) return false; + + for (i = 0; i < ENTITIES_MAX_COUNT; i++) { + if (!Entities.List[i] || Entities.List[i] == except) continue; + if (Entities.List[i]->TextureId == except->TextureId) return false; + } + return true; +} + +CC_NOINLINE static void Entity_DeleteSkin(struct Entity* e) { + if (Entity_CanDeleteTexture(e)) { + Gfx_DeleteTexture(&e->TextureId); + } + + Entity_ResetSkin(e); + e->FetchedSkin = false; +} + +void Entity_SetSkin(struct Entity* e, const String* skin) { + Entity_DeleteSkin(e); + String_CopyToRawArray(e->SkinNameRaw, skin); +} + + /*########################################################################################################################* *--------------------------------------------------------Entities---------------------------------------------------------* *#########################################################################################################################*/ @@ -471,7 +662,6 @@ void Entities_DrawShadows(void) { if (Entities.ShadowsMode == SHADOW_MODE_CIRCLE_ALL) { for (i = 0; i < ENTITIES_SELF_ID; i++) { if (!Entities.List[i]) continue; - if (Entities.List[i]->EntityType != ENTITY_TYPE_PLAYER) continue; ShadowComponent_Draw(Entities.List[i]); } } @@ -567,218 +757,11 @@ struct IGameComponent TabList_Component = { }; -/*########################################################################################################################* -*---------------------------------------------------------Player----------------------------------------------------------* -*#########################################################################################################################*/ -static struct Player* Player_FirstOtherWithSameSkin(struct Player* player) { - struct Entity* entity = &player->Base; - struct Player* p; - String skin, pSkin; - int i; - - skin = String_FromRawArray(entity->SkinNameRaw); - for (i = 0; i < ENTITIES_MAX_COUNT; i++) { - if (!Entities.List[i] || Entities.List[i] == entity) continue; - if (Entities.List[i]->EntityType != ENTITY_TYPE_PLAYER) continue; - - p = (struct Player*)Entities.List[i]; - pSkin = String_FromRawArray(p->Base.SkinNameRaw); - if (String_Equals(&skin, &pSkin)) return p; - } - return NULL; -} - -static struct Player* Player_FirstOtherWithSameSkinAndFetchedSkin(struct Player* player) { - struct Entity* entity = &player->Base; - struct Player* p; - String skin, pSkin; - int i; - - skin = String_FromRawArray(entity->SkinNameRaw); - for (i = 0; i < ENTITIES_MAX_COUNT; i++) { - if (!Entities.List[i] || Entities.List[i] == entity) continue; - if (Entities.List[i]->EntityType != ENTITY_TYPE_PLAYER) continue; - - p = (struct Player*)Entities.List[i]; - pSkin = String_FromRawArray(p->Base.SkinNameRaw); - if (p->FetchedSkin && String_Equals(&skin, &pSkin)) return p; - } - return NULL; -} - -/* Copies skin data from another player */ -static void Player_CopySkin(struct Player* player, struct Player* from) { - struct Entity* dst = &player->Base; - struct Entity* src = &from->Base; - String skin; - - dst->TextureId = src->TextureId; - dst->SkinType = src->SkinType; - dst->uScale = src->uScale; - dst->vScale = src->vScale; - - /* Custom mob textures */ - dst->MobTextureId = GFX_NULL; - skin = String_FromRawArray(dst->SkinNameRaw); - if (Utils_IsUrlPrefix(&skin, 0)) dst->MobTextureId = dst->TextureId; -} - -/* Resets skin data for the given player */ -void Player_ResetSkin(struct Player* player) { - struct Entity* e = &player->Base; - e->uScale = 1.0f; e->vScale = 1.0f; - e->MobTextureId = GFX_NULL; - e->TextureId = GFX_NULL; - e->SkinType = SKIN_64x32; -} - -/* Copies or resets skin data for all players with same skin */ -static void Player_SetSkinAll(struct Player* player, bool reset) { - struct Entity* entity = &player->Base; - struct Player* p; - String skin, pSkin; - int i; - - skin = String_FromRawArray(entity->SkinNameRaw); - for (i = 0; i < ENTITIES_MAX_COUNT; i++) { - if (!Entities.List[i]) continue; - if (Entities.List[i]->EntityType != ENTITY_TYPE_PLAYER) continue; - - p = (struct Player*)Entities.List[i]; - pSkin = String_FromRawArray(p->Base.SkinNameRaw); - if (!String_Equals(&skin, &pSkin)) continue; - - if (reset) { - Player_ResetSkin(p); - } else { - Player_CopySkin(p, player); - } - } -} - -/* Clears hat area from a skin bitmap if it's completely white or black, - so skins edited with Microsoft Paint or similiar don't have a solid hat */ -static void Player_ClearHat(Bitmap* bmp, uint8_t skinType) { - int sizeX = (bmp->Width / 64) * 32; - int yScale = skinType == SKIN_64x32 ? 32 : 64; - int sizeY = (bmp->Height / yScale) * 16; - int x, y; - - /* determine if we actually need filtering */ - for (y = 0; y < sizeY; y++) { - BitmapCol* row = Bitmap_GetRow(bmp, y) + sizeX; - for (x = 0; x < sizeX; x++) { - if (row[x].A != 255) return; - } - } - - /* only perform filtering when the entire hat is opaque */ - uint32_t white = PackedCol_ARGB(255, 255, 255, 255); - uint32_t black = PackedCol_ARGB(0, 0, 0, 255); - for (y = 0; y < sizeY; y++) { - uint32_t* row = Bitmap_RawRow(bmp, y) + sizeX; - for (x = 0; x < sizeX; x++) { - uint32_t pixel = row[x]; - if (pixel == white || pixel == black) row[x] = 0; - } - } -} - -/* Ensures skin is a power of two size, resizing if needed. */ -static void Player_EnsurePow2(struct Player* p, Bitmap* bmp) { - uint32_t stride; - int width, height; - Bitmap scaled; - int y; - - width = Math_NextPowOf2(bmp->Width); - height = Math_NextPowOf2(bmp->Height); - if (width == bmp->Width && height == bmp->Height) return; - - Bitmap_Allocate(&scaled, width, height); - p->Base.uScale = (float)bmp->Width / width; - p->Base.vScale = (float)bmp->Height / height; - stride = bmp->Width * 4; - - for (y = 0; y < bmp->Height; y++) { - BitmapCol* src = Bitmap_GetRow(bmp, y); - BitmapCol* dst = Bitmap_GetRow(&scaled, y); - Mem_Copy(dst, src, stride); - } - - Mem_Free(bmp->Scan0); - *bmp = scaled; -} - -static void Player_CheckSkin(struct Player* p) { - struct Entity* e = &p->Base; - struct Player* first; - String url, skin = String_FromRawArray(e->SkinNameRaw); - - struct HttpRequest item; - struct Stream mem; - Bitmap bmp; - ReturnCode res; - - if (!p->FetchedSkin && e->Model->UsesSkin) { - first = Player_FirstOtherWithSameSkinAndFetchedSkin(p); - if (!first) { - Http_AsyncGetSkin(&skin); - } else { - Player_CopySkin(p, first); - } - p->FetchedSkin = true; - } - - if (!Http_GetResult(&skin, &item)) return; - if (!item.Success) { Player_SetSkinAll(p, true); return; } - Stream_ReadonlyMemory(&mem, item.Data, item.Size); - - if ((res = Png_Decode(&bmp, &mem))) { - url = String_FromRawArray(item.URL); - Logger_Warn2(res, "decoding", &url); - Mem_Free(bmp.Scan0); return; - } - - Gfx_DeleteTexture(&e->TextureId); - Player_SetSkinAll(p, true); - Player_EnsurePow2(p, &bmp); - e->SkinType = Utils_CalcSkinType(&bmp); - - if (bmp.Width > Gfx.MaxTexWidth || bmp.Height > Gfx.MaxTexHeight) { - Chat_Add1("&cSkin %s is too large", &skin); - } else if (e->SkinType != SKIN_INVALID) { - if (e->Model->UsesHumanSkin) Player_ClearHat(&bmp, e->SkinType); - e->TextureId = Gfx_CreateTexture(&bmp, true, false); - Player_SetSkinAll(p, false); - } - Mem_Free(bmp.Scan0); -} - static void Player_Despawn(struct Entity* e) { - struct Player* player = (struct Player*)e; - struct Player* first = Player_FirstOtherWithSameSkin(player); - - if (!first) { - Gfx_DeleteTexture(&e->TextureId); - Player_ResetSkin(player); - } + Entity_DeleteSkin(e); Entity_ContextLost(e); } -void Player_SetSkin(struct Player* p, const String* skin) { - String_CopyToRawArray(p->Base.SkinNameRaw, skin); -} - -static void Player_Init(struct Entity* e) { - static const String model = String_FromConst("humanoid"); - Entity_Init(e); - - e->StepSize = 0.5f; - e->EntityType = ENTITY_TYPE_PLAYER; - Entity_SetModel(e, &model); -} - /*########################################################################################################################* *------------------------------------------------------LocalPlayer--------------------------------------------------------* @@ -862,7 +845,7 @@ static void LocalPlayer_Tick(struct Entity* e, double delta) { AnimatedComp_Update(e, p->Interp.Prev.Pos, p->Interp.Next.Pos, delta); TiltComp_Update(&p->Tilt, delta); - Player_CheckSkin((struct Player*)p); + Entity_CheckSkin(&p->Base); SoundComp_Tick(wasOnGround); } @@ -895,9 +878,9 @@ static void LocalPlayer_Init(void) { struct LocalPlayer* p = &LocalPlayer_Instance; struct HacksComp* hacks = &p->Hacks; - Player_Init(&p->Base); + Entity_Init(&p->Base); Entity_SetName(&p->Base, &Game_Username); - Player_SetSkin((struct Player*)p, &Game_Username); + Entity_SetSkin(&p->Base, &Game_Username); Event_RegisterVoid(&UserEvents.HackPermissionsChanged, NULL, LocalPlayer_CheckJumpVelocity); p->Collisions.Entity = &p->Base; @@ -1089,7 +1072,7 @@ static void NetPlayer_SetLocation(struct Entity* e, struct LocationUpdate* updat static void NetPlayer_Tick(struct Entity* e, double delta) { struct NetPlayer* p = (struct NetPlayer*)e; - Player_CheckSkin((struct Player*)p); + Entity_CheckSkin(e); NetInterpComp_AdvanceState(&p->Interp); AnimatedComp_Update(e, p->Interp.Prev.Pos, p->Interp.Next.Pos, delta); } @@ -1119,10 +1102,9 @@ struct EntityVTABLE netPlayer_VTABLE = { NetPlayer_Tick, Player_Despawn, NetPlayer_SetLocation, Entity_GetCol, NetPlayer_RenderModel, NetPlayer_RenderName }; -void NetPlayer_Init(struct NetPlayer* p, const String* skinName) { +void NetPlayer_Init(struct NetPlayer* p) { Mem_Set(p, 0, sizeof(struct NetPlayer)); - Player_Init(&p->Base); - Player_SetSkin((struct Player*)p, skinName); + Entity_Init(&p->Base); p->Base.VTABLE = &netPlayer_VTABLE; } diff --git a/src/Entity.h b/src/Entity.h index bee29e343..e45847237 100644 --- a/src/Entity.h +++ b/src/Entity.h @@ -77,7 +77,8 @@ struct Entity { Vec3 ModelScale, Size; float StepSize; - uint8_t SkinType, EntityType; + uint8_t SkinType; + bool FetchedSkin; bool NoShade, OnGround; GfxResourceID TextureId, MobTextureId; float uScale, vScale; @@ -117,6 +118,8 @@ bool Entity_TouchesAnyWater(struct Entity* e); /* Sets the nametag above the given entity's head */ void Entity_SetName(struct Entity* e, const String* name); +/* Sets the skin name of the given entity. */ +void Entity_SetSkin(struct Entity* e, const String* skin); /* Global data for all entities */ /* (Actual entities may point to NetPlayers_List or elsewhere) */ @@ -165,27 +168,19 @@ CC_API void TabList_Set(EntityID id, const String* player, const String* list, c #define TabList_UNSAFE_GetPlayer(id) StringsBuffer_UNSAFE_Get(&TabList.Buffer, TabList.PlayerNames[id]); #define TabList_UNSAFE_GetList(id) StringsBuffer_UNSAFE_Get(&TabList.Buffer, TabList.ListNames[id]); #define TabList_UNSAFE_GetGroup(id) StringsBuffer_UNSAFE_Get(&TabList.Buffer, TabList.GroupNames[id]); -#define Player_Layout struct Entity Base; bool FetchedSkin; - -/* Represents a player entity. */ -struct Player { Player_Layout }; -/* Sets the display name (name tag above entity) and skin name of the given player. */ -void Player_SetSkin(struct Player* player, const String* skin); -/* Resets the skin of the entity to default. */ -void Player_ResetSkin(struct Player* player); /* Represents another entity in multiplayer */ struct NetPlayer { - Player_Layout + struct Entity Base; struct NetInterpComp Interp; bool ShouldRender; }; -void NetPlayer_Init(struct NetPlayer* player, const String* skinName); +void NetPlayer_Init(struct NetPlayer* player); extern struct NetPlayer NetPlayers_List[ENTITIES_SELF_ID]; /* Represents the user/player's own entity. */ struct LocalPlayer { - Player_Layout + struct Entity Base; Vec3 Spawn, OldVelocity; float SpawnRotY, SpawnHeadX, ReachDistance; struct HacksComp Hacks; diff --git a/src/Protocol.c b/src/Protocol.c index d3d2056da..b08217b38 100644 --- a/src/Protocol.c +++ b/src/Protocol.c @@ -157,19 +157,13 @@ static void Protocol_AddEntity(uint8_t* data, EntityID id, const String* display if (Entities.List[id]) Entities_Remove(id); e = &NetPlayers_List[id].Base; - NetPlayer_Init((struct NetPlayer*)e, skinName); + NetPlayer_Init((struct NetPlayer*)e); Entities.List[id] = e; Event_RaiseInt(&EntityEvents.Added, id); } else { e = &LocalPlayer_Instance.Base; - e->VTABLE->Despawn(e); - /* Always reset the texture here, in case other network players are using the same skin as us */ - /* In that case, we don't want the fetching of new skin for us to delete the texture used by them */ - Player_ResetSkin((struct Player*)p); - p->FetchedSkin = false; - - Player_SetSkin((struct Player*)p, skinName); } + Entity_SetSkin(e, skinName); Entity_SetName(e, displayName); if (!readPosition) return;