From f0c9faf6541c4f9738d050b02a1d68a3e89c325d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Fri, 13 Sep 2024 21:19:40 +0200 Subject: [PATCH] Refactor `CSkins7` accessor functions and 0.7 skin json parsing - Combine `CSkins7::FindSkinPart` and `CSkins7::GetSkinPart` functions into `CSkins7::FindSkinPart` function to simplify the usage. - Replace `CSkins7::Num` and `CSkins7::Get` functions with `CSkins7::GetSkins` function to return all skins to improve readability using for-each loops. - Replace `CSkins7::NumSkinPart` and `CSkins7::GetSkinPart` functions with `CSkins7::GetSkinParts` function to return all skin parts to improve readability using for-each loops. - Add `CSkins7::FindSkinPartOrNullptr` function to find a skin by name or return `nullptr` if it's not found. Let the `CSkins7::FindSkinPart` function return the desired part, then the default part, and lastly the placeholder part. - Add `CSkins7::FindDefaultSkinPart` function to find the default skin part or placeholder skin part (never `nullptr`). - Remove redundant check for duplicate skin parts. This is already prevented by the `IStorage::ListDirectory` function. - Remove separate placeholder skin that was being used to initialized every loaded and created skin. - Remove unused `CSkins7::GetInitAmount` function. - Add `CSkins7::InitPlaceholderSkinParts` function to initialize the placeholder skin parts. Keep placeholder skin parts separate from normal skin parts, i.e. not in the vectors of parts. - Rename `CSkins7::AddSkin` function to `AddSkinFromConfigVariables` for clarity about its behavior. - Fix `CSkins7::RandomizeSkin` function not terminating if there exist only special skin parts for any part type. - Add `CSkins7::XmasHatTexture` and `CSkins7::BotDecorationTexture` getter functions instead of exposing the respective member variables. - Improve validation when parsing 0.7 skin json format. Fail loading and log error messages on invalid skin json files instead of silently loading incorrect/incomplete skins. --- .../client/components/menus_settings7.cpp | 77 ++--- src/game/client/components/skins7.cpp | 307 +++++++++--------- src/game/client/components/skins7.h | 52 +-- src/game/client/sixup_translate_game.cpp | 5 +- 4 files changed, 224 insertions(+), 217 deletions(-) diff --git a/src/game/client/components/menus_settings7.cpp b/src/game/client/components/menus_settings7.cpp index cfde9d187..13c804d36 100644 --- a/src/game/client/components/menus_settings7.cpp +++ b/src/game/client/components/menus_settings7.cpp @@ -116,8 +116,7 @@ void CMenus::RenderSettingsTee7(CUIRect MainView) OwnSkinInfo.m_Size = 50.0f; for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++) { - int SkinPart = m_pClient->m_Skins7.FindSkinPart(Part, apSkinPartsPtr[Part], false); - const CSkins7::CSkinPart *pSkinPart = m_pClient->m_Skins7.GetSkinPart(Part, SkinPart); + const CSkins7::CSkinPart *pSkinPart = m_pClient->m_Skins7.FindSkinPart(Part, apSkinPartsPtr[Part], false); if(aUCCVars[Part]) { OwnSkinInfo.m_aSixup[g_Config.m_ClDummy].m_aTextures[Part] = pSkinPart->m_ColorTexture; @@ -160,21 +159,12 @@ void CMenus::RenderSettingsTee7(CUIRect MainView) } m_pClient->m_Skins7.ValidateSkinParts(apSkinPartsPtr, aUCCVars, aColorVars, GAMEFLAG_TEAMS); - CTeeRenderInfo TeamSkinInfo = OwnSkinInfo; + CTeeRenderInfo TeamSkinInfo; + TeamSkinInfo.m_Size = OwnSkinInfo.m_Size; for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++) { - int SkinPart = m_pClient->m_Skins7.FindSkinPart(Part, apSkinPartsPtr[Part], false); - const CSkins7::CSkinPart *pSkinPart = m_pClient->m_Skins7.GetSkinPart(Part, SkinPart); - if(aUCCVars[Part]) - { - TeamSkinInfo.m_aSixup[g_Config.m_ClDummy].m_aTextures[Part] = pSkinPart->m_ColorTexture; - TeamSkinInfo.m_aSixup[g_Config.m_ClDummy].m_aColors[Part] = m_pClient->m_Skins7.GetColor(aColorVars[Part], Part == protocol7::SKINPART_MARKING); - } - else - { - TeamSkinInfo.m_aSixup[g_Config.m_ClDummy].m_aTextures[Part] = pSkinPart->m_OrgTexture; - TeamSkinInfo.m_aSixup[g_Config.m_ClDummy].m_aColors[Part] = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f); - } + const CSkins7::CSkinPart *pSkinPart = m_pClient->m_Skins7.FindSkinPart(Part, apSkinPartsPtr[Part], false); + TeamSkinInfo.m_aSixup[g_Config.m_ClDummy].m_aTextures[Part] = aUCCVars[Part] ? pSkinPart->m_ColorTexture : pSkinPart->m_OrgTexture; } for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++) @@ -327,22 +317,21 @@ void CMenus::RenderSkinSelection7(CUIRect MainView) static float s_LastSelectionTime = -10.0f; static std::vector s_vpSkinList; static CListBox s_ListBox; - static int s_SkinCount = 0; - if(m_SkinListNeedsUpdate || m_pClient->m_Skins7.Num() != s_SkinCount) + static size_t s_SkinCount = 0; + + const std::vector &vCurrentSkins = GameClient()->m_Skins7.GetSkins(); + if(m_SkinListNeedsUpdate || vCurrentSkins.size() != s_SkinCount) { s_vpSkinList.clear(); - s_SkinCount = m_pClient->m_Skins7.Num(); - for(int i = 0; i < s_SkinCount; ++i) + s_SkinCount = vCurrentSkins.size(); + for(const CSkins7::CSkin &Skin : vCurrentSkins) { - const CSkins7::CSkin *pSkin = m_pClient->m_Skins7.Get(i); - if(g_Config.m_ClSkinFilterString[0] != '\0' && !str_utf8_find_nocase(pSkin->m_aName, g_Config.m_ClSkinFilterString)) + if((Skin.m_Flags & CSkins7::SKINFLAG_SPECIAL) != 0) + continue; + if(g_Config.m_ClSkinFilterString[0] != '\0' && !str_utf8_find_nocase(Skin.m_aName, g_Config.m_ClSkinFilterString)) continue; - // no special skins - if((pSkin->m_Flags & CSkins7::SKINFLAG_SPECIAL) == 0) - { - s_vpSkinList.emplace_back(pSkin); - } + s_vpSkinList.emplace_back(&Skin); } m_SkinListNeedsUpdate = false; } @@ -416,24 +405,23 @@ void CMenus::RenderSkinPartSelection7(CUIRect MainView) { static std::vector s_paList[protocol7::NUM_SKINPARTS]; static CListBox s_ListBox; - static int s_aSkinPartCount[protocol7::NUM_SKINPARTS] = {0}; + static size_t s_aSkinPartCount[protocol7::NUM_SKINPARTS] = {0}; for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++) { - if(m_SkinPartListNeedsUpdate || m_pClient->m_Skins7.NumSkinPart(Part) != s_aSkinPartCount[Part]) + const std::vector &vCurrentSkinParts = GameClient()->m_Skins7.GetSkinParts(Part); + if(m_SkinPartListNeedsUpdate || vCurrentSkinParts.size() != s_aSkinPartCount[Part]) { s_paList[Part].clear(); - s_aSkinPartCount[Part] = m_pClient->m_Skins7.NumSkinPart(Part); - for(int i = 0; i < s_aSkinPartCount[Part]; ++i) + s_aSkinPartCount[Part] = vCurrentSkinParts.size(); + for(const CSkins7::CSkinPart &SkinPart : vCurrentSkinParts) { - const CSkins7::CSkinPart *pPart = m_pClient->m_Skins7.GetSkinPart(Part, i); - if(g_Config.m_ClSkinFilterString[0] != '\0' && !str_utf8_find_nocase(pPart->m_aName, g_Config.m_ClSkinFilterString)) + if((SkinPart.m_Flags & CSkins7::SKINFLAG_SPECIAL) != 0) continue; - // no special skins - if((pPart->m_Flags & CSkins7::SKINFLAG_SPECIAL) == 0) - { - s_paList[Part].emplace_back(pPart); - } + if(g_Config.m_ClSkinFilterString[0] != '\0' && !str_utf8_find_nocase(SkinPart.m_aName, g_Config.m_ClSkinFilterString)) + continue; + + s_paList[Part].emplace_back(&SkinPart); } } } @@ -460,19 +448,18 @@ void CMenus::RenderSkinPartSelection7(CUIRect MainView) Item.m_Rect.HSplitBottom(12.0f, &Item.m_Rect, &Label); CTeeRenderInfo Info; - for(int j = 0; j < protocol7::NUM_SKINPARTS; j++) + for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++) { - int SkinPart = m_pClient->m_Skins7.FindSkinPart(j, CSkins7::ms_apSkinVariables[(int)m_Dummy][j], false); - const CSkins7::CSkinPart *pSkinPart = m_pClient->m_Skins7.GetSkinPart(j, SkinPart); - if(*CSkins7::ms_apUCCVariables[(int)m_Dummy][j]) + const CSkins7::CSkinPart *pSkinPart = m_pClient->m_Skins7.FindSkinPart(Part, CSkins7::ms_apSkinVariables[(int)m_Dummy][Part], false); + if(*CSkins7::ms_apUCCVariables[(int)m_Dummy][Part]) { - Info.m_aSixup[g_Config.m_ClDummy].m_aTextures[j] = m_TeePartSelected == j ? pPart->m_ColorTexture : pSkinPart->m_ColorTexture; - Info.m_aSixup[g_Config.m_ClDummy].m_aColors[j] = m_pClient->m_Skins7.GetColor(*CSkins7::ms_apColorVariables[(int)m_Dummy][j], j == protocol7::SKINPART_MARKING); + Info.m_aSixup[g_Config.m_ClDummy].m_aTextures[Part] = m_TeePartSelected == Part ? pPart->m_ColorTexture : pSkinPart->m_ColorTexture; + Info.m_aSixup[g_Config.m_ClDummy].m_aColors[Part] = m_pClient->m_Skins7.GetColor(*CSkins7::ms_apColorVariables[(int)m_Dummy][Part], Part == protocol7::SKINPART_MARKING); } else { - Info.m_aSixup[g_Config.m_ClDummy].m_aTextures[j] = m_TeePartSelected == j ? pPart->m_OrgTexture : pSkinPart->m_OrgTexture; - Info.m_aSixup[0].m_aColors[j] = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f); + Info.m_aSixup[g_Config.m_ClDummy].m_aTextures[Part] = m_TeePartSelected == Part ? pPart->m_OrgTexture : pSkinPart->m_OrgTexture; + Info.m_aSixup[g_Config.m_ClDummy].m_aColors[Part] = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f); } } Info.m_Size = 50.0f; diff --git a/src/game/client/components/skins7.cpp b/src/game/client/components/skins7.cpp index 6dc5a18a2..9287180f3 100644 --- a/src/game/client/components/skins7.cpp +++ b/src/game/client/components/skins7.cpp @@ -48,11 +48,6 @@ int CSkins7::SkinPartScan(const char *pName, int IsDir, int DirType, void *pUser return 0; } - CSkinPart Part; - str_copy(Part.m_aName, pName, minimum(PartNameSize + 1, sizeof(Part.m_aName))); - if(pSelf->FindSkinPart(pSelf->m_ScanningPart, Part.m_aName, true) != -1) - return 0; - char aFilename[IO_MAX_PATH_LENGTH]; str_format(aFilename, sizeof(aFilename), SKINS_DIR "/%s/%s", CSkins7::ms_apSkinPartNames[pSelf->m_ScanningPart], pName); CImageInfo Info; @@ -68,6 +63,8 @@ int CSkins7::SkinPartScan(const char *pName, int IsDir, int DirType, void *pUser return 0; } + CSkinPart Part; + str_copy(Part.m_aName, pName, minimum(PartNameSize + 1, sizeof(Part.m_aName))); Part.m_OrgTexture = pSelf->Graphics()->LoadTextureRaw(Info, 0, aFilename); Part.m_BloodColor = ColorRGBA(1.0f, 1.0f, 1.0f); @@ -131,14 +128,15 @@ int CSkins7::SkinScan(const char *pName, int IsDir, int DirType, void *pUser) } // init - CSkin Skin = pSelf->m_DummySkin; - int NameLength = str_length(pName); - str_copy(Skin.m_aName, pName, 1 + NameLength - str_length(".json")); - bool SpecialSkin = pName[0] == 'x' && pName[1] == '_'; + CSkin Skin; + str_copy(Skin.m_aName, pName, 1 + str_length(pName) - str_length(".json")); + const bool SpecialSkin = pName[0] == 'x' && pName[1] == '_'; + Skin.m_Flags = SpecialSkin ? SKINFLAG_SPECIAL : 0; + if(DirType != IStorage::TYPE_SAVE) + Skin.m_Flags |= SKINFLAG_STANDARD; // parse json data - json_settings JsonSettings; - mem_zero(&JsonSettings, sizeof(JsonSettings)); + json_settings JsonSettings{}; char aError[256]; json_value *pJsonData = json_parse_ex(&JsonSettings, static_cast(pFileData), JsonFileSize, aError); free(pFileData); @@ -151,64 +149,78 @@ int CSkins7::SkinScan(const char *pName, int IsDir, int DirType, void *pUser) // extract data const json_value &Start = (*pJsonData)["skin"]; - if(Start.type == json_object) + if(Start.type != json_object) { - for(int PartIndex = 0; PartIndex < protocol7::NUM_SKINPARTS; ++PartIndex) + log_error("skins7", "Failed to parse skin json file '%s': root must be an object", aFilename); + json_value_free(pJsonData); + return 0; + } + + for(int PartIndex = 0; PartIndex < protocol7::NUM_SKINPARTS; ++PartIndex) + { + Skin.m_aUseCustomColors[PartIndex] = 0; + Skin.m_aPartColors[PartIndex] = (PartIndex == protocol7::SKINPART_MARKING ? 0xFF000000u : 0u) + 0x00FF80u; + + const json_value &Part = Start[(const char *)ms_apSkinPartNames[PartIndex]]; + if(Part.type == json_none) { - const json_value &Part = Start[(const char *)ms_apSkinPartNames[PartIndex]]; - if(Part.type != json_object) + Skin.m_apParts[PartIndex] = pSelf->FindDefaultSkinPart(PartIndex); + continue; + } + if(Part.type != json_object) + { + log_error("skins7", "Failed to parse skin json file '%s': attribute '%s' must specify an object", aFilename, ms_apSkinPartNames[PartIndex]); + json_value_free(pJsonData); + return 0; + } + + // filename + const json_value &Filename = Part["filename"]; + if(Filename.type == json_string) + { + Skin.m_apParts[PartIndex] = pSelf->FindSkinPart(PartIndex, (const char *)Filename, SpecialSkin); + } + else + { + log_error("skins7", "Failed to parse skin json file '%s': part '%s' attribute 'filename' must specify a string", aFilename, ms_apSkinPartNames[PartIndex]); + json_value_free(pJsonData); + return 0; + } + + // use custom colors + bool UseCustomColors = false; + const json_value &Color = Part["custom_colors"]; + if(Color.type == json_string) + UseCustomColors = str_comp((const char *)Color, "true") == 0; + else if(Color.type == json_boolean) + UseCustomColors = Color.u.boolean; + Skin.m_aUseCustomColors[PartIndex] = UseCustomColors; + + // color components + if(!UseCustomColors) + continue; + + for(int i = 0; i < NUM_COLOR_COMPONENTS; i++) + { + if(PartIndex != protocol7::SKINPART_MARKING && i == 3) continue; - // filename - const json_value &Filename = Part["filename"]; - if(Filename.type == json_string) + const json_value &Component = Part[(const char *)ms_apColorComponents[i]]; + if(Component.type == json_integer) { - int SkinPart = pSelf->FindSkinPart(PartIndex, (const char *)Filename, SpecialSkin); - if(SkinPart > -1) - Skin.m_apParts[PartIndex] = pSelf->GetSkinPart(PartIndex, SkinPart); - } - - // use custom colors - bool UseCustomColors = false; - const json_value &Color = Part["custom_colors"]; - if(Color.type == json_string) - UseCustomColors = str_comp((const char *)Color, "true") == 0; - else if(Color.type == json_boolean) - UseCustomColors = Color.u.boolean; - Skin.m_aUseCustomColors[PartIndex] = UseCustomColors; - - // color components - if(!UseCustomColors) - continue; - - for(int i = 0; i < NUM_COLOR_COMPONENTS; i++) - { - if(PartIndex != protocol7::SKINPART_MARKING && i == 3) - continue; - - const json_value &Component = Part[(const char *)ms_apColorComponents[i]]; - if(Component.type == json_integer) + switch(i) { - switch(i) - { - case 0: Skin.m_aPartColors[PartIndex] = (Skin.m_aPartColors[PartIndex] & 0xFF00FFFF) | (Component.u.integer << 16); break; - case 1: Skin.m_aPartColors[PartIndex] = (Skin.m_aPartColors[PartIndex] & 0xFFFF00FF) | (Component.u.integer << 8); break; - case 2: Skin.m_aPartColors[PartIndex] = (Skin.m_aPartColors[PartIndex] & 0xFFFFFF00) | Component.u.integer; break; - case 3: Skin.m_aPartColors[PartIndex] = (Skin.m_aPartColors[PartIndex] & 0x00FFFFFF) | (Component.u.integer << 24); break; - } + case 0: Skin.m_aPartColors[PartIndex] = (Skin.m_aPartColors[PartIndex] & 0xFF00FFFFu) | (Component.u.integer << 16); break; + case 1: Skin.m_aPartColors[PartIndex] = (Skin.m_aPartColors[PartIndex] & 0xFFFF00FFu) | (Component.u.integer << 8); break; + case 2: Skin.m_aPartColors[PartIndex] = (Skin.m_aPartColors[PartIndex] & 0xFFFFFF00u) | Component.u.integer; break; + case 3: Skin.m_aPartColors[PartIndex] = (Skin.m_aPartColors[PartIndex] & 0x00FFFFFFu) | (Component.u.integer << 24); break; } } } } - // clean up json_value_free(pJsonData); - // set skin data - Skin.m_Flags = SpecialSkin ? SKINFLAG_SPECIAL : 0; - if(DirType != IStorage::TYPE_SAVE) - Skin.m_Flags |= SKINFLAG_STANDARD; - if(pSelf->Config()->m_Debug) { log_trace("skins7", "Loaded skin '%s'", Skin.m_aName); @@ -218,11 +230,6 @@ int CSkins7::SkinScan(const char *pName, int IsDir, int DirType, void *pUser) return 0; } -int CSkins7::GetInitAmount() const -{ - return protocol7::NUM_SKINPARTS * 5 + 8; -} - void CSkins7::OnInit() { int Dummy = 0; @@ -267,6 +274,8 @@ void CSkins7::OnInit() ms_apColorVariables[Dummy][protocol7::SKINPART_FEET] = &Config()->m_ClDummy7ColorFeet; ms_apColorVariables[Dummy][protocol7::SKINPART_EYES] = &Config()->m_ClDummy7ColorEyes; + InitPlaceholderSkinParts(); + for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++) { m_avSkinParts[Part].clear(); @@ -282,56 +291,36 @@ void CSkins7::OnInit() } // load skin parts - char aBuf[64]; + char aBuf[IO_MAX_PATH_LENGTH]; str_format(aBuf, sizeof(aBuf), SKINS_DIR "/%s", ms_apSkinPartNames[Part]); m_ScanningPart = Part; Storage()->ListDirectory(IStorage::TYPE_ALL, aBuf, SkinPartScan, this); - // add dummy skin part - if(m_avSkinParts[Part].empty()) - { - CSkinPart DummySkinPart; - DummySkinPart.m_Flags = SKINFLAG_STANDARD; - str_copy(DummySkinPart.m_aName, "dummy"); - DummySkinPart.m_BloodColor = vec3(1.0f, 1.0f, 1.0f); - m_avSkinParts[Part].emplace_back(DummySkinPart); - } - GameClient()->m_Menus.RenderLoading(Localize("Loading DDNet Client"), Localize("Loading skin files"), 0); } - // create dummy skin - m_DummySkin.m_Flags = SKINFLAG_STANDARD; - str_copy(m_DummySkin.m_aName, "dummy"); - for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++) - { - int Default; - if(Part == protocol7::SKINPART_MARKING || Part == protocol7::SKINPART_DECORATION) - Default = FindSkinPart(Part, "", false); - else - Default = FindSkinPart(Part, "standard", false); - if(Default < 0) - Default = 0; - m_DummySkin.m_apParts[Part] = GetSkinPart(Part, Default); - m_DummySkin.m_aPartColors[Part] = Part == protocol7::SKINPART_MARKING ? (255 << 24) + 65408 : 65408; - m_DummySkin.m_aUseCustomColors[Part] = 0; - } - GameClient()->m_Menus.RenderLoading(Localize("Loading DDNet Client"), Localize("Loading skin files"), 0); - // load skins m_vSkins.clear(); Storage()->ListDirectory(IStorage::TYPE_ALL, SKINS_DIR, SkinScan, this); GameClient()->m_Menus.RenderLoading(Localize("Loading DDNet Client"), Localize("Loading skin files"), 0); - // add dummy skin - if(m_vSkins.empty()) - m_vSkins.emplace_back(m_DummySkin); - LoadXmasHat(); LoadBotDecoration(); GameClient()->m_Menus.RenderLoading(Localize("Loading DDNet Client"), Localize("Loading skin files"), 0); } +void CSkins7::InitPlaceholderSkinParts() +{ + for(CSkinPart &SkinPart : m_aPlaceholderSkinParts) + { + SkinPart.m_Flags = SKINFLAG_STANDARD; + str_copy(SkinPart.m_aName, "dummy"); + SkinPart.m_OrgTexture.Invalidate(); + SkinPart.m_ColorTexture.Invalidate(); + SkinPart.m_BloodColor = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f); + } +} + void CSkins7::LoadXmasHat() { const char *pFilename = SKINS_DIR "/xmas_hat.png"; @@ -374,24 +363,26 @@ void CSkins7::LoadBotDecoration() } } -void CSkins7::AddSkin(const char *pSkinName, int Dummy) +void CSkins7::AddSkinFromConfigVariables(const char *pName, int Dummy) { - CSkin Skin = m_DummySkin; - Skin.m_Flags = 0; - str_copy(Skin.m_aName, pSkinName, sizeof(Skin.m_aName)); + auto OldSkin = std::find_if(m_vSkins.begin(), m_vSkins.end(), [pName](const CSkin &Skin) { + return str_comp(Skin.m_aName, pName) == 0; + }); + if(OldSkin != m_vSkins.end()) + { + m_vSkins.erase(OldSkin); + } + + CSkin NewSkin; + NewSkin.m_Flags = 0; + str_copy(NewSkin.m_aName, pName); for(int PartIndex = 0; PartIndex < protocol7::NUM_SKINPARTS; ++PartIndex) { - int SkinPart = FindSkinPart(PartIndex, ms_apSkinVariables[Dummy][PartIndex], false); - if(SkinPart > -1) - Skin.m_apParts[PartIndex] = GetSkinPart(PartIndex, SkinPart); - Skin.m_aUseCustomColors[PartIndex] = *ms_apUCCVariables[Dummy][PartIndex]; - Skin.m_aPartColors[PartIndex] = *ms_apColorVariables[Dummy][PartIndex]; + NewSkin.m_apParts[PartIndex] = FindSkinPart(PartIndex, ms_apSkinVariables[Dummy][PartIndex], false); + NewSkin.m_aUseCustomColors[PartIndex] = *ms_apUCCVariables[Dummy][PartIndex]; + NewSkin.m_aPartColors[PartIndex] = *ms_apColorVariables[Dummy][PartIndex]; } - int SkinIndex = Find(Skin.m_aName, false); - if(SkinIndex != -1) - m_vSkins[SkinIndex] = Skin; - else - m_vSkins.emplace_back(Skin); + m_vSkins.insert(std::lower_bound(m_vSkins.begin(), m_vSkins.end(), NewSkin), NewSkin); } bool CSkins7::RemoveSkin(const CSkin *pSkin) @@ -403,53 +394,60 @@ bool CSkins7::RemoveSkin(const CSkin *pSkin) return false; } - auto Position = std::find(m_vSkins.begin(), m_vSkins.end(), *pSkin); - m_vSkins.erase(Position); + auto FoundSkin = std::find(m_vSkins.begin(), m_vSkins.end(), *pSkin); + dbg_assert(FoundSkin != m_vSkins.end(), "Skin not found"); + m_vSkins.erase(FoundSkin); return true; } -int CSkins7::Num() +const std::vector &CSkins7::GetSkins() const { - return m_vSkins.size(); + return m_vSkins; } -int CSkins7::NumSkinPart(int Part) +const std::vector &CSkins7::GetSkinParts(int Part) const { - return m_avSkinParts[Part].size(); + return m_avSkinParts[Part]; } -const CSkins7::CSkin *CSkins7::Get(int Index) +const CSkins7::CSkinPart *CSkins7::FindSkinPartOrNullptr(int Part, const char *pName, bool AllowSpecialPart) const { - return &m_vSkins[maximum((std::size_t)0, Index % m_vSkins.size())]; -} - -int CSkins7::Find(const char *pName, bool AllowSpecialSkin) -{ - for(unsigned int i = 0; i < m_vSkins.size(); i++) + auto FoundPart = std::find_if(m_avSkinParts[Part].begin(), m_avSkinParts[Part].end(), [pName](const CSkinPart &SkinPart) { + return str_comp(SkinPart.m_aName, pName) == 0; + }); + if(FoundPart == m_avSkinParts[Part].end()) { - if(str_comp(m_vSkins[i].m_aName, pName) == 0 && ((m_vSkins[i].m_Flags & SKINFLAG_SPECIAL) == 0 || AllowSpecialSkin)) - return i; + return nullptr; } - return -1; -} - -const CSkins7::CSkinPart *CSkins7::GetSkinPart(int Part, int Index) -{ - int Size = m_avSkinParts[Part].size(); - return &m_avSkinParts[Part][maximum(0, Index % Size)]; -} - -int CSkins7::FindSkinPart(int Part, const char *pName, bool AllowSpecialPart) -{ - for(unsigned int i = 0; i < m_avSkinParts[Part].size(); i++) + if((FoundPart->m_Flags & SKINFLAG_SPECIAL) != 0 && !AllowSpecialPart) { - if(str_comp(m_avSkinParts[Part][i].m_aName, pName) == 0 && ((m_avSkinParts[Part][i].m_Flags & SKINFLAG_SPECIAL) == 0 || AllowSpecialPart)) - return i; + return nullptr; } - return -1; + return &*FoundPart; } -void CSkins7::RandomizeSkin(int Dummy) +const CSkins7::CSkinPart *CSkins7::FindDefaultSkinPart(int Part) const +{ + const char *pDefaultPartName = Part == protocol7::SKINPART_MARKING || Part == protocol7::SKINPART_DECORATION ? "" : "default"; + const CSkinPart *pDefault = FindSkinPartOrNullptr(Part, pDefaultPartName, false); + if(pDefault != nullptr) + { + return pDefault; + } + return &m_aPlaceholderSkinParts[Part]; +} + +const CSkins7::CSkinPart *CSkins7::FindSkinPart(int Part, const char *pName, bool AllowSpecialPart) const +{ + const CSkinPart *pSkinPart = FindSkinPartOrNullptr(Part, pName, AllowSpecialPart); + if(pSkinPart != nullptr) + { + return pSkinPart; + } + return FindDefaultSkinPart(Part); +} + +void CSkins7::RandomizeSkin(int Dummy) const { for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++) { @@ -466,10 +464,23 @@ void CSkins7::RandomizeSkin(int Dummy) for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++) { - const CSkinPart *pSkinPart = GetSkinPart(Part, rand() % NumSkinPart(Part)); - while(pSkinPart->m_Flags & SKINFLAG_SPECIAL) - pSkinPart = GetSkinPart(Part, rand() % NumSkinPart(Part)); - str_copy(ms_apSkinVariables[Dummy][Part], pSkinPart->m_aName, protocol7::MAX_SKIN_ARRAY_SIZE); + std::vector vConsideredSkinParts; + for(const CSkinPart &SkinPart : GetSkinParts(Part)) + { + if((SkinPart.m_Flags & CSkins7::SKINFLAG_SPECIAL) != 0) + continue; + vConsideredSkinParts.push_back(&SkinPart); + } + const CSkins7::CSkinPart *pRandomPart; + if(vConsideredSkinParts.empty()) + { + pRandomPart = FindDefaultSkinPart(Part); + } + else + { + pRandomPart = vConsideredSkinParts[rand() % vConsideredSkinParts.size()]; + } + str_copy(CSkins7::ms_apSkinVariables[Dummy][Part], pRandomPart->m_aName, protocol7::MAX_SKIN_ARRAY_SIZE); } ms_apSkinNameVariables[Dummy][0] = '\0'; @@ -523,10 +534,13 @@ bool CSkins7::ValidateSkinParts(char *apPartNames[protocol7::NUM_SKINPARTS], int return true; } -bool CSkins7::SaveSkinfile(const char *pSaveSkinName, int Dummy) +bool CSkins7::SaveSkinfile(const char *pName, int Dummy) { + const bool SpecialSkin = pName[0] == 'x' && pName[1] == '_'; + dbg_assert(!SpecialSkin, "Cannot save special skins"); + char aBuf[IO_MAX_PATH_LENGTH]; - str_format(aBuf, sizeof(aBuf), SKINS_DIR "/%s.json", pSaveSkinName); + str_format(aBuf, sizeof(aBuf), SKINS_DIR "/%s.json", pName); IOHANDLE File = Storage()->OpenFile(aBuf, IOFLAG_WRITE, IStorage::TYPE_SAVE); if(!File) return false; @@ -573,7 +587,6 @@ bool CSkins7::SaveSkinfile(const char *pSaveSkinName, int Dummy) Writer.EndObject(); Writer.EndObject(); - // add new skin to the skin list - AddSkin(pSaveSkinName, Dummy); + AddSkinFromConfigVariables(pName, Dummy); return true; } diff --git a/src/game/client/components/skins7.h b/src/game/client/components/skins7.h index 3cfd1f74f..4f37630ea 100644 --- a/src/game/client/components/skins7.h +++ b/src/game/client/components/skins7.h @@ -2,16 +2,19 @@ /* If you are missing that file, acquire a complete release at teeworlds.com. */ #ifndef GAME_CLIENT_COMPONENTS_SKINS7_H #define GAME_CLIENT_COMPONENTS_SKINS7_H + #include #include + #include #include -#include -#include +#include #include #include +#include + class CSkins7 : public CComponent { public: @@ -26,8 +29,9 @@ public: HAT_OFFSET_SIDE = 2, }; - struct CSkinPart + class CSkinPart { + public: int m_Flags; char m_aName[24]; IGraphics::CTextureHandle m_OrgTexture; @@ -37,13 +41,14 @@ public: bool operator<(const CSkinPart &Other) { return str_comp_nocase(m_aName, Other.m_aName) < 0; } }; - struct CSkin + class CSkin { + public: int m_Flags; char m_aName[24]; const CSkinPart *m_apParts[protocol7::NUM_SKINPARTS]; - int m_aPartColors[protocol7::NUM_SKINPARTS]; int m_aUseCustomColors[protocol7::NUM_SKINPARTS]; + unsigned m_aPartColors[protocol7::NUM_SKINPARTS]; bool operator<(const CSkin &Other) const { return str_comp_nocase(m_aName, Other.m_aName) < 0; } bool operator==(const CSkin &Other) const { return !str_comp(m_aName, Other.m_aName); } @@ -56,23 +61,17 @@ public: static char *ms_apSkinNameVariables[NUM_DUMMIES]; static char *ms_apSkinVariables[NUM_DUMMIES][protocol7::NUM_SKINPARTS]; static int *ms_apUCCVariables[NUM_DUMMIES][protocol7::NUM_SKINPARTS]; // use custom color - static unsigned int *ms_apColorVariables[NUM_DUMMIES][protocol7::NUM_SKINPARTS]; - IGraphics::CTextureHandle m_XmasHatTexture; - IGraphics::CTextureHandle m_BotTexture; + static unsigned *ms_apColorVariables[NUM_DUMMIES][protocol7::NUM_SKINPARTS]; - int GetInitAmount() const; + int Sizeof() const override { return sizeof(*this); } void OnInit() override; - void AddSkin(const char *pSkinName, int Dummy); - bool RemoveSkin(const CSkin *pSkin); - - int Num(); - int NumSkinPart(int Part); - const CSkin *Get(int Index); - int Find(const char *pName, bool AllowSpecialSkin); - const CSkinPart *GetSkinPart(int Part, int Index); - int FindSkinPart(int Part, const char *pName, bool AllowSpecialPart); - void RandomizeSkin(int Dummy); + const std::vector &GetSkins() const; + const std::vector &GetSkinParts(int Part) const; + const CSkinPart *FindSkinPartOrNullptr(int Part, const char *pName, bool AllowSpecialPart) const; + const CSkinPart *FindDefaultSkinPart(int Part) const; + const CSkinPart *FindSkinPart(int Part, const char *pName, bool AllowSpecialPart) const; + void RandomizeSkin(int Dummy) const; ColorRGBA GetColor(int Value, bool UseAlpha) const; ColorRGBA GetTeamColor(int UseCustomColors, int PartColor, int Team, int Part) const; @@ -80,21 +79,30 @@ public: // returns true if everything was valid and nothing changed bool ValidateSkinParts(char *apPartNames[protocol7::NUM_SKINPARTS], int *pUseCustomColors, int *pPartColors, int GameFlags) const; - bool SaveSkinfile(const char *pSaveSkinName, int Dummy); + bool SaveSkinfile(const char *pName, int Dummy); + bool RemoveSkin(const CSkin *pSkin); - virtual int Sizeof() const override { return sizeof(*this); } + IGraphics::CTextureHandle XmasHatTexture() const { return m_XmasHatTexture; } + IGraphics::CTextureHandle BotDecorationTexture() const { return m_BotTexture; } private: int m_ScanningPart; + std::vector m_avSkinParts[protocol7::NUM_SKINPARTS]; + CSkinPart m_aPlaceholderSkinParts[protocol7::NUM_SKINPARTS]; std::vector m_vSkins; - CSkin m_DummySkin; + + IGraphics::CTextureHandle m_XmasHatTexture; + IGraphics::CTextureHandle m_BotTexture; static int SkinPartScan(const char *pName, int IsDir, int DirType, void *pUser); static int SkinScan(const char *pName, int IsDir, int DirType, void *pUser); + void InitPlaceholderSkinParts(); void LoadXmasHat(); void LoadBotDecoration(); + + void AddSkinFromConfigVariables(const char *pName, int Dummy); }; #endif diff --git a/src/game/client/sixup_translate_game.cpp b/src/game/client/sixup_translate_game.cpp index ae6f8ed5b..6588348ce 100644 --- a/src/game/client/sixup_translate_game.cpp +++ b/src/game/client/sixup_translate_game.cpp @@ -94,7 +94,7 @@ void CGameClient::ApplySkin7InfoFromGameMsg(const T *pMsg, int ClientId, int Con if(time_season() == SEASON_XMAS) { - pClient->m_SkinInfo.m_aSixup[Conn].m_HatTexture = m_Skins7.m_XmasHatTexture; + pClient->m_SkinInfo.m_aSixup[Conn].m_HatTexture = m_Skins7.XmasHatTexture(); pClient->m_SkinInfo.m_aSixup[Conn].m_HatSpriteIndex = ClientId % CSkins7::HAT_NUM; } else @@ -102,8 +102,7 @@ void CGameClient::ApplySkin7InfoFromGameMsg(const T *pMsg, int ClientId, int Con for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++) { - int Id = m_Skins7.FindSkinPart(Part, pClient->m_aSixup[Conn].m_aaSkinPartNames[Part], false); - const CSkins7::CSkinPart *pSkinPart = m_Skins7.GetSkinPart(Part, Id); + const CSkins7::CSkinPart *pSkinPart = m_Skins7.FindSkinPart(Part, pClient->m_aSixup[Conn].m_aaSkinPartNames[Part], false); if(pClient->m_aSixup[Conn].m_aUseCustomColors[Part]) { pClient->m_SkinInfo.m_aSixup[Conn].m_aTextures[Part] = pSkinPart->m_ColorTexture;