ddnet/src/game/client/components/skins.cpp

455 lines
16 KiB
C++
Raw Normal View History

2010-11-20 10:37:14 +00:00
/* (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. */
2010-05-29 07:25:38 +00:00
#include <base/math.h>
#include <base/system.h>
2019-09-30 13:03:37 +00:00
#include <ctime>
#include <engine/engine.h>
2010-05-29 07:25:38 +00:00
#include <engine/graphics.h>
#include <engine/shared/config.h>
#include <engine/storage.h>
2021-07-12 09:29:59 +00:00
#include <game/generated/client_data.h>
#include <game/client/gameclient.h>
2010-05-29 07:25:38 +00:00
#include "skins.h"
2018-07-24 15:26:39 +00:00
static const char *VANILLA_SKINS[] = {"bluekitty", "bluestripe", "brownbear",
"cammo", "cammostripes", "coala", "default", "limekitty",
"pinky", "redbopp", "redstripe", "saddo", "toptri",
2020-06-21 15:48:33 +00:00
"twinbop", "twintri", "warpaint", "x_ninja", "x_spec"};
2018-07-24 15:26:39 +00:00
static bool IsVanillaSkin(const char *pName)
{
2022-01-23 17:56:37 +00:00
return std::any_of(std::begin(VANILLA_SKINS), std::end(VANILLA_SKINS), [pName](const char *pVanillaSkin) { return str_comp(pName, pVanillaSkin) == 0; });
2018-07-24 15:26:39 +00:00
}
int CSkins::CGetPngFile::OnCompletion(int State)
{
State = CHttpRequest::OnCompletion(State);
if(State != HTTP_ERROR && State != HTTP_ABORTED && !m_pSkins->LoadSkinPNG(m_Info, Dest(), Dest(), IStorage::TYPE_SAVE))
{
State = HTTP_ERROR;
}
return State;
}
CSkins::CGetPngFile::CGetPngFile(CSkins *pSkins, const char *pUrl, IStorage *pStorage, const char *pDest) :
CHttpRequest(pUrl),
m_pSkins(pSkins)
{
WriteToFile(pStorage, pDest, IStorage::TYPE_SAVE);
Timeout(CTimeout{0, 0, 0, 0});
LogProgress(HTTPLOG::NONE);
}
struct SSkinScanUser
{
CSkins *m_pThis;
CSkins::TSkinLoadedCBFunc m_SkinLoadedFunc;
};
2018-07-24 15:26:39 +00:00
int CSkins::SkinScan(const char *pName, int IsDir, int DirType, void *pUser)
{
auto *pUserReal = (SSkinScanUser *)pUser;
CSkins *pSelf = pUserReal->m_pThis;
2014-11-25 19:13:40 +00:00
if(IsDir || !str_endswith(pName, ".png"))
return 0;
char aNameWithoutPng[128];
str_copy(aNameWithoutPng, pName, sizeof(aNameWithoutPng));
aNameWithoutPng[str_length(aNameWithoutPng) - 4] = 0;
2018-07-24 15:26:39 +00:00
2014-11-25 19:13:40 +00:00
// Don't add duplicate skins (one from user's config directory, other from
// client itself)
for(int i = 0; i < pSelf->Num(); i++)
{
2018-07-24 15:26:39 +00:00
const char *pExName = pSelf->Get(i)->m_aName;
if(str_comp(pExName, aNameWithoutPng) == 0)
2014-11-25 19:13:40 +00:00
return 0;
}
char aBuf[IO_MAX_PATH_LENGTH];
str_format(aBuf, sizeof(aBuf), "skins/%s", pName);
auto SkinID = pSelf->LoadSkin(aNameWithoutPng, aBuf, DirType);
pUserReal->m_SkinLoadedFunc(SkinID);
return SkinID;
}
2020-11-12 07:35:07 +00:00
static void CheckMetrics(CSkin::SSkinMetricVariable &Metrics, uint8_t *pImg, int ImgWidth, int ImgX, int ImgY, int CheckWidth, int CheckHeight)
{
int MaxY = -1;
int MinY = CheckHeight + 1;
int MaxX = -1;
int MinX = CheckWidth + 1;
for(int y = 0; y < CheckHeight; y++)
{
for(int x = 0; x < CheckWidth; x++)
{
int OffsetAlpha = (y + ImgY) * ImgWidth + (x + ImgX) * 4 + 3;
uint8_t AlphaValue = pImg[OffsetAlpha];
if(AlphaValue > 0)
{
if(MaxY < y)
MaxY = y;
if(MinY > y)
MinY = y;
if(MaxX < x)
MaxX = x;
if(MinX > x)
MinX = x;
}
}
}
Metrics.m_Width = clamp((MaxX - MinX) + 1, 1, CheckWidth);
Metrics.m_Height = clamp((MaxY - MinY) + 1, 1, CheckHeight);
Metrics.m_OffsetX = clamp(MinX, 0, CheckWidth - 1);
Metrics.m_OffsetY = clamp(MinY, 0, CheckHeight - 1);
Metrics.m_MaxWidth = CheckWidth;
Metrics.m_MaxHeight = CheckHeight;
}
2020-11-02 20:21:57 +00:00
int CSkins::LoadSkin(const char *pName, const char *pPath, int DirType)
{
2010-05-29 07:25:38 +00:00
CImageInfo Info;
if(!LoadSkinPNG(Info, pName, pPath, DirType))
return 0;
return LoadSkin(pName, Info);
}
bool CSkins::LoadSkinPNG(CImageInfo &Info, const char *pName, const char *pPath, int DirType)
{
char aBuf[512];
if(!Graphics()->LoadPNG(&Info, pPath, DirType))
{
str_format(aBuf, sizeof(aBuf), "failed to load skin from %s", pName);
Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "game", aBuf);
return false;
}
return true;
}
int CSkins::LoadSkin(const char *pName, CImageInfo &Info)
{
char aBuf[512];
if(!Graphics()->CheckImageDivisibility(pName, Info, g_pData->m_aSprites[SPRITE_TEE_BODY].m_pSet->m_Gridx, g_pData->m_aSprites[SPRITE_TEE_BODY].m_pSet->m_Gridy, true))
{
str_format(aBuf, sizeof(aBuf), "skin failed image divisibility: %s", pName);
Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "game", aBuf);
return 0;
}
if(!Graphics()->IsImageFormatRGBA(pName, Info))
2022-02-22 18:43:14 +00:00
{
str_format(aBuf, sizeof(aBuf), "skin format is not RGBA: %s", pName);
Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "game", aBuf);
return 0;
}
CSkin Skin;
Skin.m_IsVanilla = IsVanillaSkin(pName);
Skin.m_OriginalSkin.m_Body = Graphics()->LoadSpriteTexture(Info, &g_pData->m_aSprites[SPRITE_TEE_BODY]);
Skin.m_OriginalSkin.m_BodyOutline = Graphics()->LoadSpriteTexture(Info, &g_pData->m_aSprites[SPRITE_TEE_BODY_OUTLINE]);
Skin.m_OriginalSkin.m_Feet = Graphics()->LoadSpriteTexture(Info, &g_pData->m_aSprites[SPRITE_TEE_FOOT]);
Skin.m_OriginalSkin.m_FeetOutline = Graphics()->LoadSpriteTexture(Info, &g_pData->m_aSprites[SPRITE_TEE_FOOT_OUTLINE]);
Skin.m_OriginalSkin.m_Hands = Graphics()->LoadSpriteTexture(Info, &g_pData->m_aSprites[SPRITE_TEE_HAND]);
Skin.m_OriginalSkin.m_HandsOutline = Graphics()->LoadSpriteTexture(Info, &g_pData->m_aSprites[SPRITE_TEE_HAND_OUTLINE]);
for(int i = 0; i < 6; ++i)
Skin.m_OriginalSkin.m_Eyes[i] = Graphics()->LoadSpriteTexture(Info, &g_pData->m_aSprites[SPRITE_TEE_EYE_NORMAL + i]);
2020-11-08 05:39:16 +00:00
int FeetGridPixelsWidth = (Info.m_Width / g_pData->m_aSprites[SPRITE_TEE_FOOT].m_pSet->m_Gridx);
int FeetGridPixelsHeight = (Info.m_Height / g_pData->m_aSprites[SPRITE_TEE_FOOT].m_pSet->m_Gridy);
int FeetWidth = g_pData->m_aSprites[SPRITE_TEE_FOOT].m_W * FeetGridPixelsWidth;
int FeetHeight = g_pData->m_aSprites[SPRITE_TEE_FOOT].m_H * FeetGridPixelsHeight;
int FeetOffsetX = g_pData->m_aSprites[SPRITE_TEE_FOOT].m_X * FeetGridPixelsWidth;
int FeetOffsetY = g_pData->m_aSprites[SPRITE_TEE_FOOT].m_Y * FeetGridPixelsHeight;
2020-11-12 07:35:07 +00:00
int FeetOutlineGridPixelsWidth = (Info.m_Width / g_pData->m_aSprites[SPRITE_TEE_FOOT_OUTLINE].m_pSet->m_Gridx);
int FeetOutlineGridPixelsHeight = (Info.m_Height / g_pData->m_aSprites[SPRITE_TEE_FOOT_OUTLINE].m_pSet->m_Gridy);
int FeetOutlineWidth = g_pData->m_aSprites[SPRITE_TEE_FOOT_OUTLINE].m_W * FeetOutlineGridPixelsWidth;
int FeetOutlineHeight = g_pData->m_aSprites[SPRITE_TEE_FOOT_OUTLINE].m_H * FeetOutlineGridPixelsHeight;
int FeetOutlineOffsetX = g_pData->m_aSprites[SPRITE_TEE_FOOT_OUTLINE].m_X * FeetOutlineGridPixelsWidth;
int FeetOutlineOffsetY = g_pData->m_aSprites[SPRITE_TEE_FOOT_OUTLINE].m_Y * FeetOutlineGridPixelsHeight;
int BodyOutlineGridPixelsWidth = (Info.m_Width / g_pData->m_aSprites[SPRITE_TEE_BODY_OUTLINE].m_pSet->m_Gridx);
int BodyOutlineGridPixelsHeight = (Info.m_Height / g_pData->m_aSprites[SPRITE_TEE_BODY_OUTLINE].m_pSet->m_Gridy);
int BodyOutlineWidth = g_pData->m_aSprites[SPRITE_TEE_BODY_OUTLINE].m_W * BodyOutlineGridPixelsWidth;
int BodyOutlineHeight = g_pData->m_aSprites[SPRITE_TEE_BODY_OUTLINE].m_H * BodyOutlineGridPixelsHeight;
2020-11-12 07:35:07 +00:00
int BodyOutlineOffsetX = g_pData->m_aSprites[SPRITE_TEE_BODY_OUTLINE].m_X * BodyOutlineGridPixelsWidth;
int BodyOutlineOffsetY = g_pData->m_aSprites[SPRITE_TEE_BODY_OUTLINE].m_Y * BodyOutlineGridPixelsHeight;
int BodyWidth = g_pData->m_aSprites[SPRITE_TEE_BODY].m_W * (Info.m_Width / g_pData->m_aSprites[SPRITE_TEE_BODY].m_pSet->m_Gridx); // body width
int BodyHeight = g_pData->m_aSprites[SPRITE_TEE_BODY].m_H * (Info.m_Height / g_pData->m_aSprites[SPRITE_TEE_BODY].m_pSet->m_Gridy); // body height
if(BodyWidth > Info.m_Width || BodyHeight > Info.m_Height)
return 0;
2010-05-29 07:25:38 +00:00
unsigned char *d = (unsigned char *)Info.m_pData;
2022-02-22 18:43:14 +00:00
const int PixelStep = 4;
int Pitch = Info.m_Width * PixelStep;
2008-03-17 01:41:11 +00:00
// dig out blood color
{
2010-05-29 07:25:38 +00:00
int aColors[3] = {0};
for(int y = 0; y < BodyHeight; y++)
for(int x = 0; x < BodyWidth; x++)
2008-03-17 01:41:11 +00:00
{
2022-02-22 18:43:14 +00:00
uint8_t AlphaValue = d[y * Pitch + x * PixelStep + 3];
2020-11-08 05:39:16 +00:00
if(AlphaValue > 128)
2008-03-17 01:41:11 +00:00
{
2022-02-22 18:43:14 +00:00
aColors[0] += d[y * Pitch + x * PixelStep + 0];
aColors[1] += d[y * Pitch + x * PixelStep + 1];
aColors[2] += d[y * Pitch + x * PixelStep + 2];
2008-03-17 01:41:11 +00:00
}
}
2020-10-17 15:59:18 +00:00
if(aColors[0] != 0 && aColors[1] != 0 && aColors[2] != 0)
Skin.m_BloodColor = ColorRGBA(normalize(vec3(aColors[0], aColors[1], aColors[2])));
else
Skin.m_BloodColor = ColorRGBA(0, 0, 0, 1);
2008-03-17 01:41:11 +00:00
}
CheckMetrics(Skin.m_Metrics.m_Body, d, Pitch, 0, 0, BodyWidth, BodyHeight);
2020-11-08 05:39:16 +00:00
2020-11-12 07:35:07 +00:00
// body outline metrics
CheckMetrics(Skin.m_Metrics.m_Body, d, Pitch, BodyOutlineOffsetX, BodyOutlineOffsetY, BodyOutlineWidth, BodyOutlineHeight);
2020-11-08 05:39:16 +00:00
// get feet size
2020-11-12 07:35:07 +00:00
CheckMetrics(Skin.m_Metrics.m_Feet, d, Pitch, FeetOffsetX, FeetOffsetY, FeetWidth, FeetHeight);
2020-11-08 05:39:16 +00:00
2020-11-12 07:35:07 +00:00
// get feet outline size
CheckMetrics(Skin.m_Metrics.m_Feet, d, Pitch, FeetOutlineOffsetX, FeetOutlineOffsetY, FeetOutlineWidth, FeetOutlineHeight);
2020-11-08 05:39:16 +00:00
// make the texture gray scale
for(int i = 0; i < Info.m_Width * Info.m_Height; i++)
{
2022-02-22 18:43:14 +00:00
int v = (d[i * PixelStep] + d[i * PixelStep + 1] + d[i * PixelStep + 2]) / 3;
d[i * PixelStep] = v;
d[i * PixelStep + 1] = v;
d[i * PixelStep + 2] = v;
}
2008-03-17 01:41:11 +00:00
2011-02-13 12:58:59 +00:00
int Freq[256] = {0};
int OrgWeight = 0;
int NewWeight = 192;
2011-02-13 12:58:59 +00:00
// find most common frequence
for(int y = 0; y < BodyHeight; y++)
for(int x = 0; x < BodyWidth; x++)
2007-11-26 22:26:33 +00:00
{
2022-02-22 18:43:14 +00:00
if(d[y * Pitch + x * PixelStep + 3] > 128)
Freq[d[y * Pitch + x * PixelStep]]++;
2007-11-26 22:26:33 +00:00
}
2011-02-13 12:58:59 +00:00
for(int i = 1; i < 256; i++)
{
if(Freq[OrgWeight] < Freq[i])
OrgWeight = i;
2007-11-26 22:26:33 +00:00
}
2011-02-13 12:58:59 +00:00
// reorder
int InvOrgWeight = 255 - OrgWeight;
int InvNewWeight = 255 - NewWeight;
for(int y = 0; y < BodyHeight; y++)
for(int x = 0; x < BodyWidth; x++)
2011-02-13 12:58:59 +00:00
{
2022-02-22 18:43:14 +00:00
int v = d[y * Pitch + x * PixelStep];
2020-10-12 10:31:43 +00:00
if(v <= OrgWeight && OrgWeight == 0)
v = 0;
else if(v <= OrgWeight)
v = (int)(((v / (float)OrgWeight) * NewWeight));
else if(InvOrgWeight == 0)
v = NewWeight;
2011-02-13 12:58:59 +00:00
else
v = (int)(((v - OrgWeight) / (float)InvOrgWeight) * InvNewWeight + NewWeight);
2022-02-22 18:43:14 +00:00
d[y * Pitch + x * PixelStep] = v;
d[y * Pitch + x * PixelStep + 1] = v;
d[y * Pitch + x * PixelStep + 2] = v;
2011-02-13 12:58:59 +00:00
}
Skin.m_ColorableSkin.m_Body = Graphics()->LoadSpriteTexture(Info, &g_pData->m_aSprites[SPRITE_TEE_BODY]);
Skin.m_ColorableSkin.m_BodyOutline = Graphics()->LoadSpriteTexture(Info, &g_pData->m_aSprites[SPRITE_TEE_BODY_OUTLINE]);
Skin.m_ColorableSkin.m_Feet = Graphics()->LoadSpriteTexture(Info, &g_pData->m_aSprites[SPRITE_TEE_FOOT]);
Skin.m_ColorableSkin.m_FeetOutline = Graphics()->LoadSpriteTexture(Info, &g_pData->m_aSprites[SPRITE_TEE_FOOT_OUTLINE]);
Skin.m_ColorableSkin.m_Hands = Graphics()->LoadSpriteTexture(Info, &g_pData->m_aSprites[SPRITE_TEE_HAND]);
Skin.m_ColorableSkin.m_HandsOutline = Graphics()->LoadSpriteTexture(Info, &g_pData->m_aSprites[SPRITE_TEE_HAND_OUTLINE]);
for(int i = 0; i < 6; ++i)
Skin.m_ColorableSkin.m_Eyes[i] = Graphics()->LoadSpriteTexture(Info, &g_pData->m_aSprites[SPRITE_TEE_EYE_NORMAL + i]);
2020-11-25 12:05:53 +00:00
Graphics()->FreePNG(&Info);
// set skin data
str_copy(Skin.m_aName, pName, sizeof(Skin.m_aName));
if(g_Config.m_Debug)
{
str_format(aBuf, sizeof(aBuf), "load skin %s", Skin.m_aName);
Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "game", aBuf);
}
m_vSkins.insert(std::lower_bound(m_vSkins.begin(), m_vSkins.end(), Skin), Skin);
return 0;
}
2011-02-13 12:58:59 +00:00
void CSkins::OnInit()
{
2018-12-23 21:53:10 +00:00
m_EventSkinPrefix[0] = '\0';
if(g_Config.m_Events)
{
time_t rawtime;
struct tm *timeinfo;
2019-09-30 13:03:37 +00:00
std::time(&rawtime);
2018-12-23 21:53:10 +00:00
timeinfo = localtime(&rawtime);
if(timeinfo->tm_mon == 11 && timeinfo->tm_mday >= 24 && timeinfo->tm_mday <= 26)
{ // Christmas
str_copy(m_EventSkinPrefix, "santa", sizeof(m_EventSkinPrefix));
}
}
// load skins;
Refresh([this](int SkinID) {
GameClient()->m_Menus.RenderLoading(false);
});
}
void CSkins::Refresh(TSkinLoadedCBFunc &&SkinLoadedFunc)
{
for(auto &Skin : m_vSkins)
{
Graphics()->UnloadTexture(&Skin.m_OriginalSkin.m_Body);
Graphics()->UnloadTexture(&Skin.m_OriginalSkin.m_BodyOutline);
Graphics()->UnloadTexture(&Skin.m_OriginalSkin.m_Feet);
Graphics()->UnloadTexture(&Skin.m_OriginalSkin.m_FeetOutline);
Graphics()->UnloadTexture(&Skin.m_OriginalSkin.m_Hands);
Graphics()->UnloadTexture(&Skin.m_OriginalSkin.m_HandsOutline);
for(auto &Eye : Skin.m_OriginalSkin.m_Eyes)
Graphics()->UnloadTexture(&Eye);
Graphics()->UnloadTexture(&Skin.m_ColorableSkin.m_Body);
Graphics()->UnloadTexture(&Skin.m_ColorableSkin.m_BodyOutline);
Graphics()->UnloadTexture(&Skin.m_ColorableSkin.m_Feet);
Graphics()->UnloadTexture(&Skin.m_ColorableSkin.m_FeetOutline);
Graphics()->UnloadTexture(&Skin.m_ColorableSkin.m_Hands);
Graphics()->UnloadTexture(&Skin.m_ColorableSkin.m_HandsOutline);
for(auto &Eye : Skin.m_ColorableSkin.m_Eyes)
Graphics()->UnloadTexture(&Eye);
}
m_vSkins.clear();
m_vDownloadSkins.clear();
SSkinScanUser SkinScanUser;
SkinScanUser.m_pThis = this;
SkinScanUser.m_SkinLoadedFunc = SkinLoadedFunc;
Storage()->ListDirectory(IStorage::TYPE_ALL, "skins", SkinScan, &SkinScanUser);
if(m_vSkins.empty())
{
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "gameclient", "failed to load skins. folder='skins/'");
CSkin DummySkin;
DummySkin.m_IsVanilla = true;
str_copy(DummySkin.m_aName, "dummy", sizeof(DummySkin.m_aName));
2019-04-26 12:06:32 +00:00
DummySkin.m_BloodColor = ColorRGBA(1.0f, 1.0f, 1.0f);
m_vSkins.push_back(DummySkin);
}
}
2010-05-29 07:25:38 +00:00
int CSkins::Num()
{
return m_vSkins.size();
}
const CSkin *CSkins::Get(int Index)
{
2018-07-25 20:41:24 +00:00
if(Index < 0)
{
Index = Find("default");
if(Index < 0)
2018-07-25 20:41:24 +00:00
Index = 0;
}
return &m_vSkins[Index % m_vSkins.size()];
}
2020-09-04 14:40:35 +00:00
int CSkins::Find(const char *pName)
{
2018-12-23 21:53:10 +00:00
const char *pSkinPrefix = m_EventSkinPrefix[0] ? m_EventSkinPrefix : g_Config.m_ClSkinPrefix;
if(g_Config.m_ClVanillaSkinsOnly && !IsVanillaSkin(pName))
{
return -1;
}
else if(pSkinPrefix && pSkinPrefix[0])
{
char aBuf[24];
2018-12-23 21:53:10 +00:00
str_format(aBuf, sizeof(aBuf), "%s_%s", pSkinPrefix, pName);
// If we find something, use it, otherwise fall back to normal skins.
int Result = FindImpl(aBuf);
2018-08-21 16:29:25 +00:00
if(Result != -1)
2018-08-13 18:54:01 +00:00
{
return Result;
}
}
2018-07-24 15:26:39 +00:00
return FindImpl(pName);
}
2020-09-04 14:40:35 +00:00
int CSkins::FindImpl(const char *pName)
2018-07-24 15:26:39 +00:00
{
CSkin Needle;
mem_zero(&Needle, sizeof(Needle));
str_copy(Needle.m_aName, pName, sizeof(Needle.m_aName));
auto Range = std::equal_range(m_vSkins.begin(), m_vSkins.end(), Needle);
if(std::distance(Range.first, Range.second) == 1)
return Range.first - m_vSkins.begin();
if(str_comp(pName, "default") == 0)
2020-09-04 14:40:35 +00:00
return -1;
if(!g_Config.m_ClDownloadSkins)
return -1;
if(str_find(pName, "/") != 0)
return -1;
CDownloadSkin DownloadNeedle;
mem_zero(&DownloadNeedle, sizeof(DownloadNeedle));
str_copy(DownloadNeedle.m_aName, pName, sizeof(DownloadNeedle.m_aName));
const auto &[RangeBegin, RangeEnd] = std::equal_range(m_vDownloadSkins.begin(), m_vDownloadSkins.end(), DownloadNeedle);
if(std::distance(RangeBegin, RangeEnd) == 1)
{
if(RangeBegin->m_pTask && RangeBegin->m_pTask->State() == HTTP_DONE)
{
char aPath[IO_MAX_PATH_LENGTH];
str_format(aPath, sizeof(aPath), "downloadedskins/%s.png", RangeBegin->m_aName);
Storage()->RenameFile(RangeBegin->m_aPath, aPath, IStorage::TYPE_SAVE);
LoadSkin(RangeBegin->m_aName, RangeBegin->m_pTask->m_Info);
RangeBegin->m_pTask = nullptr;
}
if(RangeBegin->m_pTask && (RangeBegin->m_pTask->State() == HTTP_ERROR || RangeBegin->m_pTask->State() == HTTP_ABORTED))
{
RangeBegin->m_pTask = nullptr;
}
2020-11-02 20:21:57 +00:00
return -1;
}
CDownloadSkin Skin;
str_copy(Skin.m_aName, pName, sizeof(Skin.m_aName));
char aUrl[IO_MAX_PATH_LENGTH];
char aEscapedName[256];
EscapeUrl(aEscapedName, sizeof(aEscapedName), pName);
2022-06-12 11:31:33 +00:00
str_format(aUrl, sizeof(aUrl), "%s%s.png", g_Config.m_ClDownloadCommunitySkins != 0 ? g_Config.m_ClSkinCommunityDownloadUrl : g_Config.m_ClSkinDownloadUrl, aEscapedName);
char aBuf[IO_MAX_PATH_LENGTH];
str_format(Skin.m_aPath, sizeof(Skin.m_aPath), "downloadedskins/%s", IStorage::FormatTmpPath(aBuf, sizeof(aBuf), pName));
Skin.m_pTask = std::make_shared<CGetPngFile>(this, aUrl, Storage(), Skin.m_aPath);
m_pClient->Engine()->AddJob(Skin.m_pTask);
m_vDownloadSkins.insert(std::lower_bound(m_vDownloadSkins.begin(), m_vDownloadSkins.end(), Skin), Skin);
return -1;
}