/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ /* If you are missing that file, acquire a complete release at teeworlds.com. */ #include #include #include #include #include #include #include "skins.h" static const char *VANILLA_SKINS[] = {"bluekitty", "bluestripe", "brownbear", "cammo", "cammostripes", "coala", "default", "limekitty", "pinky", "redbopp", "redstripe", "saddo", "toptri", "twinbop", "twintri", "warpaint", "x_ninja"}; static bool IsVanillaSkin(const char *pName) { for(unsigned int i = 0; i < sizeof(VANILLA_SKINS) / sizeof(VANILLA_SKINS[0]); i++) { if(str_comp(pName, VANILLA_SKINS[i]) == 0) { return true; } } return false; } int CSkins::SkinScan(const char *pName, int IsDir, int DirType, void *pUser) { CSkins *pSelf = (CSkins *)pUser; int l = str_length(pName); if(l < 4 || IsDir || str_comp(pName+l-4, ".png") != 0) return 0; char aFilenameWithoutPng[128]; str_copy(aFilenameWithoutPng, pName, sizeof(aFilenameWithoutPng)); aFilenameWithoutPng[str_length(aFilenameWithoutPng) - 4] = 0; // Don't add duplicate skins (one from user's config directory, other from // client itself) for(int i = 0; i < pSelf->Num(); i++) { const char *pExName = pSelf->Get(i)->m_aName; if(str_comp_num(pExName, pName, l-4) == 0 && str_length(pExName) == l-4) return 0; } char aBuf[512]; str_format(aBuf, sizeof(aBuf), "skins/%s", pName); CImageInfo Info; if(!pSelf->Graphics()->LoadPNG(&Info, aBuf, DirType)) { str_format(aBuf, sizeof(aBuf), "failed to load skin from %s", pName); pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "game", aBuf); return 0; } CSkin Skin; Skin.m_IsVanilla = IsVanillaSkin(aFilenameWithoutPng); Skin.m_OrgTexture = pSelf->Graphics()->LoadTextureRaw(Info.m_Width, Info.m_Height, Info.m_Format, Info.m_pData, Info.m_Format, 0); int BodySize = 96; // body size if (BodySize > Info.m_Height) return 0; unsigned char *d = (unsigned char *)Info.m_pData; int Pitch = Info.m_Width*4; // dig out blood color { int aColors[3] = {0}; for(int y = 0; y < BodySize; y++) for(int x = 0; x < BodySize; x++) { if(d[y*Pitch+x*4+3] > 128) { aColors[0] += d[y*Pitch+x*4+0]; aColors[1] += d[y*Pitch+x*4+1]; aColors[2] += d[y*Pitch+x*4+2]; } } Skin.m_BloodColor = normalize(vec3(aColors[0], aColors[1], aColors[2])); } // create colorless version int Step = Info.m_Format == CImageInfo::FORMAT_RGBA ? 4 : 3; // make the texture gray scale for(int i = 0; i < Info.m_Width*Info.m_Height; i++) { int v = (d[i*Step]+d[i*Step+1]+d[i*Step+2])/3; d[i*Step] = v; d[i*Step+1] = v; d[i*Step+2] = v; } int Freq[256] = {0}; int OrgWeight = 0; int NewWeight = 192; // find most common frequence for(int y = 0; y < BodySize; y++) for(int x = 0; x < BodySize; x++) { if(d[y*Pitch+x*4+3] > 128) Freq[d[y*Pitch+x*4]]++; } for(int i = 1; i < 256; i++) { if(Freq[OrgWeight] < Freq[i]) OrgWeight = i; } // reorder int InvOrgWeight = 255-OrgWeight; int InvNewWeight = 255-NewWeight; for(int y = 0; y < BodySize; y++) for(int x = 0; x < BodySize; x++) { int v = d[y*Pitch+x*4]; if(v <= OrgWeight) v = (int)(((v/(float)OrgWeight) * NewWeight)); else v = (int)(((v-OrgWeight)/(float)InvOrgWeight)*InvNewWeight + NewWeight); d[y*Pitch+x*4] = v; d[y*Pitch+x*4+1] = v; d[y*Pitch+x*4+2] = v; } Skin.m_ColorTexture = pSelf->Graphics()->LoadTextureRaw(Info.m_Width, Info.m_Height, Info.m_Format, Info.m_pData, Info.m_Format, 0); free(Info.m_pData); // set skin data str_copy(Skin.m_aName, pName, min((int)sizeof(Skin.m_aName),l-3)); if(g_Config.m_Debug) { str_format(aBuf, sizeof(aBuf), "load skin %s", Skin.m_aName); pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "game", aBuf); } pSelf->m_aSkins.add(Skin); return 0; } void CSkins::OnInit() { // load skins m_aSkins.clear(); Storage()->ListDirectory(IStorage::TYPE_ALL, "skins", SkinScan, this); if(!m_aSkins.size()) { Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "gameclient", "failed to load skins. folder='skins/'"); CSkin DummySkin; DummySkin.m_IsVanilla = true; DummySkin.m_OrgTexture = -1; DummySkin.m_ColorTexture = -1; str_copy(DummySkin.m_aName, "dummy", sizeof(DummySkin.m_aName)); DummySkin.m_BloodColor = vec3(1.0f, 1.0f, 1.0f); m_aSkins.add(DummySkin); } } int CSkins::Num() { return m_aSkins.size(); } const CSkins::CSkin *CSkins::Get(int Index) { if(Index < 0) { Index = Find("default"); if (Index < 0) Index = 0; } return &m_aSkins[Index % m_aSkins.size()]; } int CSkins::Find(const char *pName) const { if(g_Config.m_ClSkinPrefix[0]) { char aBuf[64]; str_format(aBuf, sizeof(aBuf), "%s_%s", g_Config.m_ClSkinPrefix, pName); // If we find something, use it, otherwise fall back to normal // skins. int Result = FindImpl(aBuf); if(Result != -1) { return Result; } } return FindImpl(pName); } int CSkins::FindImpl(const char *pName) const { for(int i = 0; i < m_aSkins.size(); i++) { if(str_comp(m_aSkins[i].m_aName, pName) == 0) { if(g_Config.m_ClVanillaSkinsOnly && !m_aSkins[i].m_IsVanilla) { return -1; } else { return i; } } } return -1; } vec3 CSkins::GetColorV3(int v) { return HslToRgb(vec3(((v>>16)&0xff)/255.0f, ((v>>8)&0xff)/255.0f, 0.5f+(v&0xff)/255.0f*0.5f)); } vec4 CSkins::GetColorV4(int v) { vec3 r = GetColorV3(v); return vec4(r.r, r.g, r.b, 1.0f); }