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.
This commit is contained in:
Robert Müller 2024-09-13 21:19:40 +02:00
parent 15cd607a2d
commit f0c9faf654
4 changed files with 224 additions and 217 deletions

View file

@ -116,8 +116,7 @@ void CMenus::RenderSettingsTee7(CUIRect MainView)
OwnSkinInfo.m_Size = 50.0f; OwnSkinInfo.m_Size = 50.0f;
for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++) 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.FindSkinPart(Part, apSkinPartsPtr[Part], false);
const CSkins7::CSkinPart *pSkinPart = m_pClient->m_Skins7.GetSkinPart(Part, SkinPart);
if(aUCCVars[Part]) if(aUCCVars[Part])
{ {
OwnSkinInfo.m_aSixup[g_Config.m_ClDummy].m_aTextures[Part] = pSkinPart->m_ColorTexture; 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); 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++) 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.FindSkinPart(Part, apSkinPartsPtr[Part], false);
const CSkins7::CSkinPart *pSkinPart = m_pClient->m_Skins7.GetSkinPart(Part, SkinPart); TeamSkinInfo.m_aSixup[g_Config.m_ClDummy].m_aTextures[Part] = aUCCVars[Part] ? pSkinPart->m_ColorTexture : pSkinPart->m_OrgTexture;
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);
}
} }
for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++) 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 float s_LastSelectionTime = -10.0f;
static std::vector<const CSkins7::CSkin *> s_vpSkinList; static std::vector<const CSkins7::CSkin *> s_vpSkinList;
static CListBox s_ListBox; static CListBox s_ListBox;
static int s_SkinCount = 0; static size_t s_SkinCount = 0;
if(m_SkinListNeedsUpdate || m_pClient->m_Skins7.Num() != s_SkinCount)
const std::vector<CSkins7::CSkin> &vCurrentSkins = GameClient()->m_Skins7.GetSkins();
if(m_SkinListNeedsUpdate || vCurrentSkins.size() != s_SkinCount)
{ {
s_vpSkinList.clear(); s_vpSkinList.clear();
s_SkinCount = m_pClient->m_Skins7.Num(); s_SkinCount = vCurrentSkins.size();
for(int i = 0; i < s_SkinCount; ++i) for(const CSkins7::CSkin &Skin : vCurrentSkins)
{ {
const CSkins7::CSkin *pSkin = m_pClient->m_Skins7.Get(i); if((Skin.m_Flags & CSkins7::SKINFLAG_SPECIAL) != 0)
if(g_Config.m_ClSkinFilterString[0] != '\0' && !str_utf8_find_nocase(pSkin->m_aName, g_Config.m_ClSkinFilterString)) continue;
if(g_Config.m_ClSkinFilterString[0] != '\0' && !str_utf8_find_nocase(Skin.m_aName, g_Config.m_ClSkinFilterString))
continue; continue;
// no special skins s_vpSkinList.emplace_back(&Skin);
if((pSkin->m_Flags & CSkins7::SKINFLAG_SPECIAL) == 0)
{
s_vpSkinList.emplace_back(pSkin);
}
} }
m_SkinListNeedsUpdate = false; m_SkinListNeedsUpdate = false;
} }
@ -416,24 +405,23 @@ void CMenus::RenderSkinPartSelection7(CUIRect MainView)
{ {
static std::vector<const CSkins7::CSkinPart *> s_paList[protocol7::NUM_SKINPARTS]; static std::vector<const CSkins7::CSkinPart *> s_paList[protocol7::NUM_SKINPARTS];
static CListBox s_ListBox; 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++) for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
{ {
if(m_SkinPartListNeedsUpdate || m_pClient->m_Skins7.NumSkinPart(Part) != s_aSkinPartCount[Part]) const std::vector<CSkins7::CSkinPart> &vCurrentSkinParts = GameClient()->m_Skins7.GetSkinParts(Part);
if(m_SkinPartListNeedsUpdate || vCurrentSkinParts.size() != s_aSkinPartCount[Part])
{ {
s_paList[Part].clear(); s_paList[Part].clear();
s_aSkinPartCount[Part] = m_pClient->m_Skins7.NumSkinPart(Part); s_aSkinPartCount[Part] = vCurrentSkinParts.size();
for(int i = 0; i < s_aSkinPartCount[Part]; ++i) for(const CSkins7::CSkinPart &SkinPart : vCurrentSkinParts)
{ {
const CSkins7::CSkinPart *pPart = m_pClient->m_Skins7.GetSkinPart(Part, i); if((SkinPart.m_Flags & CSkins7::SKINFLAG_SPECIAL) != 0)
if(g_Config.m_ClSkinFilterString[0] != '\0' && !str_utf8_find_nocase(pPart->m_aName, g_Config.m_ClSkinFilterString))
continue; continue;
// no special skins if(g_Config.m_ClSkinFilterString[0] != '\0' && !str_utf8_find_nocase(SkinPart.m_aName, g_Config.m_ClSkinFilterString))
if((pPart->m_Flags & CSkins7::SKINFLAG_SPECIAL) == 0) continue;
{
s_paList[Part].emplace_back(pPart); 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); Item.m_Rect.HSplitBottom(12.0f, &Item.m_Rect, &Label);
CTeeRenderInfo Info; 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.FindSkinPart(Part, CSkins7::ms_apSkinVariables[(int)m_Dummy][Part], false);
const CSkins7::CSkinPart *pSkinPart = m_pClient->m_Skins7.GetSkinPart(j, SkinPart); if(*CSkins7::ms_apUCCVariables[(int)m_Dummy][Part])
if(*CSkins7::ms_apUCCVariables[(int)m_Dummy][j])
{ {
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_aTextures[Part] = m_TeePartSelected == Part ? 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_aColors[Part] = m_pClient->m_Skins7.GetColor(*CSkins7::ms_apColorVariables[(int)m_Dummy][Part], Part == protocol7::SKINPART_MARKING);
} }
else else
{ {
Info.m_aSixup[g_Config.m_ClDummy].m_aTextures[j] = m_TeePartSelected == j ? pPart->m_OrgTexture : pSkinPart->m_OrgTexture; Info.m_aSixup[g_Config.m_ClDummy].m_aTextures[Part] = m_TeePartSelected == Part ? 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_aColors[Part] = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f);
} }
} }
Info.m_Size = 50.0f; Info.m_Size = 50.0f;

View file

@ -48,11 +48,6 @@ int CSkins7::SkinPartScan(const char *pName, int IsDir, int DirType, void *pUser
return 0; return 0;
} }
CSkinPart Part;
str_copy(Part.m_aName, pName, minimum<int>(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]; char aFilename[IO_MAX_PATH_LENGTH];
str_format(aFilename, sizeof(aFilename), SKINS_DIR "/%s/%s", CSkins7::ms_apSkinPartNames[pSelf->m_ScanningPart], pName); str_format(aFilename, sizeof(aFilename), SKINS_DIR "/%s/%s", CSkins7::ms_apSkinPartNames[pSelf->m_ScanningPart], pName);
CImageInfo Info; CImageInfo Info;
@ -68,6 +63,8 @@ int CSkins7::SkinPartScan(const char *pName, int IsDir, int DirType, void *pUser
return 0; return 0;
} }
CSkinPart Part;
str_copy(Part.m_aName, pName, minimum<int>(PartNameSize + 1, sizeof(Part.m_aName)));
Part.m_OrgTexture = pSelf->Graphics()->LoadTextureRaw(Info, 0, aFilename); Part.m_OrgTexture = pSelf->Graphics()->LoadTextureRaw(Info, 0, aFilename);
Part.m_BloodColor = ColorRGBA(1.0f, 1.0f, 1.0f); 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 // init
CSkin Skin = pSelf->m_DummySkin; CSkin Skin;
int NameLength = str_length(pName); str_copy(Skin.m_aName, pName, 1 + str_length(pName) - str_length(".json"));
str_copy(Skin.m_aName, pName, 1 + NameLength - str_length(".json")); const bool SpecialSkin = pName[0] == 'x' && pName[1] == '_';
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 // parse json data
json_settings JsonSettings; json_settings JsonSettings{};
mem_zero(&JsonSettings, sizeof(JsonSettings));
char aError[256]; char aError[256];
json_value *pJsonData = json_parse_ex(&JsonSettings, static_cast<const json_char *>(pFileData), JsonFileSize, aError); json_value *pJsonData = json_parse_ex(&JsonSettings, static_cast<const json_char *>(pFileData), JsonFileSize, aError);
free(pFileData); free(pFileData);
@ -151,21 +149,42 @@ int CSkins7::SkinScan(const char *pName, int IsDir, int DirType, void *pUser)
// extract data // extract data
const json_value &Start = (*pJsonData)["skin"]; const json_value &Start = (*pJsonData)["skin"];
if(Start.type == json_object) if(Start.type != json_object)
{ {
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) 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]]; const json_value &Part = Start[(const char *)ms_apSkinPartNames[PartIndex]];
if(Part.type != json_object) if(Part.type == json_none)
{
Skin.m_apParts[PartIndex] = pSelf->FindDefaultSkinPart(PartIndex);
continue; 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 // filename
const json_value &Filename = Part["filename"]; const json_value &Filename = Part["filename"];
if(Filename.type == json_string) if(Filename.type == json_string)
{ {
int SkinPart = pSelf->FindSkinPart(PartIndex, (const char *)Filename, SpecialSkin); Skin.m_apParts[PartIndex] = pSelf->FindSkinPart(PartIndex, (const char *)Filename, SpecialSkin);
if(SkinPart > -1) }
Skin.m_apParts[PartIndex] = pSelf->GetSkinPart(PartIndex, SkinPart); 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 // use custom colors
@ -191,24 +210,17 @@ int CSkins7::SkinScan(const char *pName, int IsDir, int DirType, void *pUser)
{ {
switch(i) switch(i)
{ {
case 0: Skin.m_aPartColors[PartIndex] = (Skin.m_aPartColors[PartIndex] & 0xFF00FFFF) | (Component.u.integer << 16); 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] & 0xFFFF00FF) | (Component.u.integer << 8); 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] & 0xFFFFFF00) | Component.u.integer; 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] & 0x00FFFFFF) | (Component.u.integer << 24); break; case 3: Skin.m_aPartColors[PartIndex] = (Skin.m_aPartColors[PartIndex] & 0x00FFFFFFu) | (Component.u.integer << 24); break;
}
} }
} }
} }
} }
// clean up
json_value_free(pJsonData); 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) if(pSelf->Config()->m_Debug)
{ {
log_trace("skins7", "Loaded skin '%s'", Skin.m_aName); 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; return 0;
} }
int CSkins7::GetInitAmount() const
{
return protocol7::NUM_SKINPARTS * 5 + 8;
}
void CSkins7::OnInit() void CSkins7::OnInit()
{ {
int Dummy = 0; int Dummy = 0;
@ -267,6 +274,8 @@ void CSkins7::OnInit()
ms_apColorVariables[Dummy][protocol7::SKINPART_FEET] = &Config()->m_ClDummy7ColorFeet; ms_apColorVariables[Dummy][protocol7::SKINPART_FEET] = &Config()->m_ClDummy7ColorFeet;
ms_apColorVariables[Dummy][protocol7::SKINPART_EYES] = &Config()->m_ClDummy7ColorEyes; ms_apColorVariables[Dummy][protocol7::SKINPART_EYES] = &Config()->m_ClDummy7ColorEyes;
InitPlaceholderSkinParts();
for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++) for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
{ {
m_avSkinParts[Part].clear(); m_avSkinParts[Part].clear();
@ -282,56 +291,36 @@ void CSkins7::OnInit()
} }
// load skin parts // load skin parts
char aBuf[64]; char aBuf[IO_MAX_PATH_LENGTH];
str_format(aBuf, sizeof(aBuf), SKINS_DIR "/%s", ms_apSkinPartNames[Part]); str_format(aBuf, sizeof(aBuf), SKINS_DIR "/%s", ms_apSkinPartNames[Part]);
m_ScanningPart = Part; m_ScanningPart = Part;
Storage()->ListDirectory(IStorage::TYPE_ALL, aBuf, SkinPartScan, this); 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); 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 // load skins
m_vSkins.clear(); m_vSkins.clear();
Storage()->ListDirectory(IStorage::TYPE_ALL, SKINS_DIR, SkinScan, this); Storage()->ListDirectory(IStorage::TYPE_ALL, SKINS_DIR, SkinScan, this);
GameClient()->m_Menus.RenderLoading(Localize("Loading DDNet Client"), Localize("Loading skin files"), 0); 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(); LoadXmasHat();
LoadBotDecoration(); LoadBotDecoration();
GameClient()->m_Menus.RenderLoading(Localize("Loading DDNet Client"), Localize("Loading skin files"), 0); 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() void CSkins7::LoadXmasHat()
{ {
const char *pFilename = SKINS_DIR "/xmas_hat.png"; 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; auto OldSkin = std::find_if(m_vSkins.begin(), m_vSkins.end(), [pName](const CSkin &Skin) {
Skin.m_Flags = 0; return str_comp(Skin.m_aName, pName) == 0;
str_copy(Skin.m_aName, pSkinName, sizeof(Skin.m_aName)); });
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) for(int PartIndex = 0; PartIndex < protocol7::NUM_SKINPARTS; ++PartIndex)
{ {
int SkinPart = FindSkinPart(PartIndex, ms_apSkinVariables[Dummy][PartIndex], false); NewSkin.m_apParts[PartIndex] = FindSkinPart(PartIndex, ms_apSkinVariables[Dummy][PartIndex], false);
if(SkinPart > -1) NewSkin.m_aUseCustomColors[PartIndex] = *ms_apUCCVariables[Dummy][PartIndex];
Skin.m_apParts[PartIndex] = GetSkinPart(PartIndex, SkinPart); NewSkin.m_aPartColors[PartIndex] = *ms_apColorVariables[Dummy][PartIndex];
Skin.m_aUseCustomColors[PartIndex] = *ms_apUCCVariables[Dummy][PartIndex];
Skin.m_aPartColors[PartIndex] = *ms_apColorVariables[Dummy][PartIndex];
} }
int SkinIndex = Find(Skin.m_aName, false); m_vSkins.insert(std::lower_bound(m_vSkins.begin(), m_vSkins.end(), NewSkin), NewSkin);
if(SkinIndex != -1)
m_vSkins[SkinIndex] = Skin;
else
m_vSkins.emplace_back(Skin);
} }
bool CSkins7::RemoveSkin(const CSkin *pSkin) bool CSkins7::RemoveSkin(const CSkin *pSkin)
@ -403,53 +394,60 @@ bool CSkins7::RemoveSkin(const CSkin *pSkin)
return false; return false;
} }
auto Position = std::find(m_vSkins.begin(), m_vSkins.end(), *pSkin); auto FoundSkin = std::find(m_vSkins.begin(), m_vSkins.end(), *pSkin);
m_vSkins.erase(Position); dbg_assert(FoundSkin != m_vSkins.end(), "Skin not found");
m_vSkins.erase(FoundSkin);
return true; return true;
} }
int CSkins7::Num() const std::vector<CSkins7::CSkin> &CSkins7::GetSkins() const
{ {
return m_vSkins.size(); return m_vSkins;
} }
int CSkins7::NumSkinPart(int Part) const std::vector<CSkins7::CSkinPart> &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())]; 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())
{
return nullptr;
}
if((FoundPart->m_Flags & SKINFLAG_SPECIAL) != 0 && !AllowSpecialPart)
{
return nullptr;
}
return &*FoundPart;
} }
int CSkins7::Find(const char *pName, bool AllowSpecialSkin) const CSkins7::CSkinPart *CSkins7::FindDefaultSkinPart(int Part) const
{ {
for(unsigned int i = 0; i < m_vSkins.size(); i++) const char *pDefaultPartName = Part == protocol7::SKINPART_MARKING || Part == protocol7::SKINPART_DECORATION ? "" : "default";
const CSkinPart *pDefault = FindSkinPartOrNullptr(Part, pDefaultPartName, false);
if(pDefault != nullptr)
{ {
if(str_comp(m_vSkins[i].m_aName, pName) == 0 && ((m_vSkins[i].m_Flags & SKINFLAG_SPECIAL) == 0 || AllowSpecialSkin)) return pDefault;
return i;
} }
return -1; return &m_aPlaceholderSkinParts[Part];
} }
const CSkins7::CSkinPart *CSkins7::GetSkinPart(int Part, int Index) const CSkins7::CSkinPart *CSkins7::FindSkinPart(int Part, const char *pName, bool AllowSpecialPart) const
{ {
int Size = m_avSkinParts[Part].size(); const CSkinPart *pSkinPart = FindSkinPartOrNullptr(Part, pName, AllowSpecialPart);
return &m_avSkinParts[Part][maximum(0, Index % Size)]; if(pSkinPart != nullptr)
{
return pSkinPart;
}
return FindDefaultSkinPart(Part);
} }
int CSkins7::FindSkinPart(int Part, const char *pName, bool AllowSpecialPart) void CSkins7::RandomizeSkin(int Dummy) const
{
for(unsigned int i = 0; i < m_avSkinParts[Part].size(); i++)
{
if(str_comp(m_avSkinParts[Part][i].m_aName, pName) == 0 && ((m_avSkinParts[Part][i].m_Flags & SKINFLAG_SPECIAL) == 0 || AllowSpecialPart))
return i;
}
return -1;
}
void CSkins7::RandomizeSkin(int Dummy)
{ {
for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++) 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++) for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
{ {
const CSkinPart *pSkinPart = GetSkinPart(Part, rand() % NumSkinPart(Part)); std::vector<const CSkins7::CSkinPart *> vConsideredSkinParts;
while(pSkinPart->m_Flags & SKINFLAG_SPECIAL) for(const CSkinPart &SkinPart : GetSkinParts(Part))
pSkinPart = GetSkinPart(Part, rand() % NumSkinPart(Part)); {
str_copy(ms_apSkinVariables[Dummy][Part], pSkinPart->m_aName, protocol7::MAX_SKIN_ARRAY_SIZE); 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'; ms_apSkinNameVariables[Dummy][0] = '\0';
@ -523,10 +534,13 @@ bool CSkins7::ValidateSkinParts(char *apPartNames[protocol7::NUM_SKINPARTS], int
return true; 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]; 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); IOHANDLE File = Storage()->OpenFile(aBuf, IOFLAG_WRITE, IStorage::TYPE_SAVE);
if(!File) if(!File)
return false; return false;
@ -573,7 +587,6 @@ bool CSkins7::SaveSkinfile(const char *pSaveSkinName, int Dummy)
Writer.EndObject(); Writer.EndObject();
Writer.EndObject(); Writer.EndObject();
// add new skin to the skin list AddSkinFromConfigVariables(pName, Dummy);
AddSkin(pSaveSkinName, Dummy);
return true; return true;
} }

View file

@ -2,16 +2,19 @@
/* If you are missing that file, acquire a complete release at teeworlds.com. */ /* If you are missing that file, acquire a complete release at teeworlds.com. */
#ifndef GAME_CLIENT_COMPONENTS_SKINS7_H #ifndef GAME_CLIENT_COMPONENTS_SKINS7_H
#define GAME_CLIENT_COMPONENTS_SKINS7_H #define GAME_CLIENT_COMPONENTS_SKINS7_H
#include <base/color.h> #include <base/color.h>
#include <base/vmath.h> #include <base/vmath.h>
#include <engine/client/enums.h> #include <engine/client/enums.h>
#include <engine/graphics.h> #include <engine/graphics.h>
#include <game/client/component.h>
#include <vector>
#include <game/client/component.h>
#include <game/generated/protocol.h> #include <game/generated/protocol.h>
#include <game/generated/protocol7.h> #include <game/generated/protocol7.h>
#include <vector>
class CSkins7 : public CComponent class CSkins7 : public CComponent
{ {
public: public:
@ -26,8 +29,9 @@ public:
HAT_OFFSET_SIDE = 2, HAT_OFFSET_SIDE = 2,
}; };
struct CSkinPart class CSkinPart
{ {
public:
int m_Flags; int m_Flags;
char m_aName[24]; char m_aName[24];
IGraphics::CTextureHandle m_OrgTexture; IGraphics::CTextureHandle m_OrgTexture;
@ -37,13 +41,14 @@ public:
bool operator<(const CSkinPart &Other) { return str_comp_nocase(m_aName, Other.m_aName) < 0; } bool operator<(const CSkinPart &Other) { return str_comp_nocase(m_aName, Other.m_aName) < 0; }
}; };
struct CSkin class CSkin
{ {
public:
int m_Flags; int m_Flags;
char m_aName[24]; char m_aName[24];
const CSkinPart *m_apParts[protocol7::NUM_SKINPARTS]; const CSkinPart *m_apParts[protocol7::NUM_SKINPARTS];
int m_aPartColors[protocol7::NUM_SKINPARTS];
int m_aUseCustomColors[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_nocase(m_aName, Other.m_aName) < 0; }
bool operator==(const CSkin &Other) const { return !str_comp(m_aName, Other.m_aName); } 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_apSkinNameVariables[NUM_DUMMIES];
static char *ms_apSkinVariables[NUM_DUMMIES][protocol7::NUM_SKINPARTS]; static char *ms_apSkinVariables[NUM_DUMMIES][protocol7::NUM_SKINPARTS];
static int *ms_apUCCVariables[NUM_DUMMIES][protocol7::NUM_SKINPARTS]; // use custom color static int *ms_apUCCVariables[NUM_DUMMIES][protocol7::NUM_SKINPARTS]; // use custom color
static unsigned int *ms_apColorVariables[NUM_DUMMIES][protocol7::NUM_SKINPARTS]; static unsigned *ms_apColorVariables[NUM_DUMMIES][protocol7::NUM_SKINPARTS];
IGraphics::CTextureHandle m_XmasHatTexture;
IGraphics::CTextureHandle m_BotTexture;
int GetInitAmount() const; int Sizeof() const override { return sizeof(*this); }
void OnInit() override; void OnInit() override;
void AddSkin(const char *pSkinName, int Dummy); const std::vector<CSkin> &GetSkins() const;
bool RemoveSkin(const CSkin *pSkin); const std::vector<CSkinPart> &GetSkinParts(int Part) const;
const CSkinPart *FindSkinPartOrNullptr(int Part, const char *pName, bool AllowSpecialPart) const;
int Num(); const CSkinPart *FindDefaultSkinPart(int Part) const;
int NumSkinPart(int Part); const CSkinPart *FindSkinPart(int Part, const char *pName, bool AllowSpecialPart) const;
const CSkin *Get(int Index); void RandomizeSkin(int Dummy) const;
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);
ColorRGBA GetColor(int Value, bool UseAlpha) const; ColorRGBA GetColor(int Value, bool UseAlpha) const;
ColorRGBA GetTeamColor(int UseCustomColors, int PartColor, int Team, int Part) 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 // returns true if everything was valid and nothing changed
bool ValidateSkinParts(char *apPartNames[protocol7::NUM_SKINPARTS], int *pUseCustomColors, int *pPartColors, int GameFlags) const; 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: private:
int m_ScanningPart; int m_ScanningPart;
std::vector<CSkinPart> m_avSkinParts[protocol7::NUM_SKINPARTS]; std::vector<CSkinPart> m_avSkinParts[protocol7::NUM_SKINPARTS];
CSkinPart m_aPlaceholderSkinParts[protocol7::NUM_SKINPARTS];
std::vector<CSkin> m_vSkins; std::vector<CSkin> 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 SkinPartScan(const char *pName, int IsDir, int DirType, void *pUser);
static int SkinScan(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 LoadXmasHat();
void LoadBotDecoration(); void LoadBotDecoration();
void AddSkinFromConfigVariables(const char *pName, int Dummy);
}; };
#endif #endif

View file

@ -94,7 +94,7 @@ void CGameClient::ApplySkin7InfoFromGameMsg(const T *pMsg, int ClientId, int Con
if(time_season() == SEASON_XMAS) 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; pClient->m_SkinInfo.m_aSixup[Conn].m_HatSpriteIndex = ClientId % CSkins7::HAT_NUM;
} }
else else
@ -102,8 +102,7 @@ void CGameClient::ApplySkin7InfoFromGameMsg(const T *pMsg, int ClientId, int Con
for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++) 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.FindSkinPart(Part, pClient->m_aSixup[Conn].m_aaSkinPartNames[Part], false);
const CSkins7::CSkinPart *pSkinPart = m_Skins7.GetSkinPart(Part, Id);
if(pClient->m_aSixup[Conn].m_aUseCustomColors[Part]) if(pClient->m_aSixup[Conn].m_aUseCustomColors[Part])
{ {
pClient->m_SkinInfo.m_aSixup[Conn].m_aTextures[Part] = pSkinPart->m_ColorTexture; pClient->m_SkinInfo.m_aSixup[Conn].m_aTextures[Part] = pSkinPart->m_ColorTexture;