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. */
|
2008-08-14 17:19:13 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
#include <base/math.h>
|
2020-09-26 19:41:58 +00:00
|
|
|
#include <base/system.h>
|
2019-09-30 13:03:37 +00:00
|
|
|
#include <ctime>
|
2008-08-14 17:19:13 +00:00
|
|
|
|
2020-09-20 00:19:24 +00:00
|
|
|
#include <engine/engine.h>
|
2010-05-29 07:25:38 +00:00
|
|
|
#include <engine/graphics.h>
|
2020-09-19 23:49:26 +00:00
|
|
|
#include <engine/shared/config.h>
|
2020-09-20 00:19:32 +00:00
|
|
|
#include <engine/storage.h>
|
2008-10-21 18:05:06 +00:00
|
|
|
|
2021-07-12 09:29:59 +00:00
|
|
|
#include <game/generated/client_data.h>
|
|
|
|
|
|
|
|
#include <game/client/gameclient.h>
|
2022-06-23 17:59:38 +00:00
|
|
|
#include <game/localization.h>
|
2021-07-12 09:29:59 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
#include "skins.h"
|
2008-10-21 18:05:06 +00:00
|
|
|
|
2023-03-24 22:14:17 +00:00
|
|
|
bool CSkins::IsVanillaSkin(const char *pName)
|
2007-11-18 12:03:59 +00:00
|
|
|
{
|
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
|
|
|
}
|
2014-08-17 03:34:16 +00:00
|
|
|
|
2020-12-12 11:37:43 +00:00
|
|
|
int CSkins::CGetPngFile::OnCompletion(int State)
|
|
|
|
{
|
2022-05-05 12:49:25 +00:00
|
|
|
State = CHttpRequest::OnCompletion(State);
|
2020-12-12 11:37:43 +00:00
|
|
|
|
2022-05-05 12:49:25 +00:00
|
|
|
if(State != HTTP_ERROR && State != HTTP_ABORTED && !m_pSkins->LoadSkinPNG(m_Info, Dest(), Dest(), IStorage::TYPE_SAVE))
|
2020-12-12 11:37:43 +00:00
|
|
|
{
|
|
|
|
State = HTTP_ERROR;
|
|
|
|
}
|
|
|
|
return State;
|
|
|
|
}
|
|
|
|
|
2022-05-05 12:49:25 +00:00
|
|
|
CSkins::CGetPngFile::CGetPngFile(CSkins *pSkins, const char *pUrl, IStorage *pStorage, const char *pDest) :
|
|
|
|
CHttpRequest(pUrl),
|
|
|
|
m_pSkins(pSkins)
|
2020-12-12 11:37:43 +00:00
|
|
|
{
|
2022-05-05 12:49:25 +00:00
|
|
|
WriteToFile(pStorage, pDest, IStorage::TYPE_SAVE);
|
2022-05-28 22:13:59 +00:00
|
|
|
Timeout(CTimeout{0, 0, 0, 0});
|
2022-05-05 12:49:25 +00:00
|
|
|
LogProgress(HTTPLOG::NONE);
|
2020-12-12 11:37:43 +00:00
|
|
|
}
|
|
|
|
|
2022-04-02 11:37:59 +00:00
|
|
|
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)
|
|
|
|
{
|
2022-04-02 11:37:59 +00:00
|
|
|
auto *pUserReal = (SSkinScanUser *)pUser;
|
|
|
|
CSkins *pSelf = pUserReal->m_pThis;
|
2014-11-25 19:13:40 +00:00
|
|
|
|
2018-07-25 08:29:05 +00:00
|
|
|
if(IsDir || !str_endswith(pName, ".png"))
|
2011-02-21 10:23:30 +00:00
|
|
|
return 0;
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2018-07-25 08:29:05 +00:00
|
|
|
char aNameWithoutPng[128];
|
2022-07-09 16:14:56 +00:00
|
|
|
str_copy(aNameWithoutPng, pName);
|
2018-07-25 08:29:05 +00:00
|
|
|
aNameWithoutPng[str_length(aNameWithoutPng) - 4] = 0;
|
2018-07-24 15:26:39 +00:00
|
|
|
|
2022-08-20 12:39:25 +00:00
|
|
|
if(g_Config.m_ClVanillaSkinsOnly && !IsVanillaSkin(aNameWithoutPng))
|
|
|
|
return 0;
|
|
|
|
|
2014-11-25 19:13:40 +00:00
|
|
|
// Don't add duplicate skins (one from user's config directory, other from
|
|
|
|
// client itself)
|
2022-10-12 17:25:59 +00:00
|
|
|
if(pSelf->m_Skins.find(aNameWithoutPng) != pSelf->m_Skins.end())
|
|
|
|
return 0;
|
2014-11-25 19:13:40 +00:00
|
|
|
|
2021-09-13 08:06:34 +00:00
|
|
|
char aBuf[IO_MAX_PATH_LENGTH];
|
2020-09-19 23:51:23 +00:00
|
|
|
str_format(aBuf, sizeof(aBuf), "skins/%s", pName);
|
2022-10-12 17:25:59 +00:00
|
|
|
pSelf->LoadSkin(aNameWithoutPng, aBuf, DirType);
|
|
|
|
pUserReal->m_SkinLoadedFunc((int)pSelf->m_Skins.size());
|
|
|
|
return 0;
|
2020-09-20 00:19:24 +00:00
|
|
|
}
|
|
|
|
|
Mark parameters as `const` when possible
According to cppchecker's `constParameter` error:
```
src\engine\gfx\image_manipulation.cpp:7:58: style: Parameter 'pSrc' can be declared as pointer to const [constParameter]
static void Dilate(int w, int h, int BPP, unsigned char *pSrc, unsigned char *pDest, unsigned char AlphaThreshold = TW_DILATE_ALPHA_THRESHOLD)
^
src\engine\gfx\image_manipulation.cpp:58:67: style: Parameter 'pSrc' can be declared as pointer to const [constParameter]
static void CopyColorValues(int w, int h, int BPP, unsigned char *pSrc, unsigned char *pDest)
^
src\engine\shared\network_conn.cpp:241:42: style: Parameter 'Addr' can be declared as reference to const [constParameter]
void CNetConnection::DirectInit(NETADDR &Addr, SECURITY_TOKEN SecurityToken, SECURITY_TOKEN Token, bool Sixup)
^
src\base\system.cpp:4060:71: style: Parameter 'random' can be declared as pointer to const [constParameter]
void generate_password(char *buffer, unsigned length, unsigned short *random, unsigned random_length)
^
src\engine\client\backend\vulkan\backend_vulkan.cpp:263:38: style: Parameter 'AllocatedMemory' can be declared as reference to const [constParameter]
void Free(SMemoryHeapQueueElement &AllocatedMemory)
^
src\engine\client\backend\vulkan\backend_vulkan.cpp:1708:47: style: Parameter 'ImgExtent' can be declared as reference to const [constParameter]
static size_t ImageMipLevelCount(VkExtent3D &ImgExtent)
^
src\engine\client\backend\vulkan\backend_vulkan.cpp:2801:29: style: Parameter 'Image' can be declared as reference to const [constParameter]
void ImageBarrier(VkImage &Image, size_t MipMapBase, size_t MipMapCount, size_t LayerBase, size_t LayerCount, VkFormat Format, VkImageLayout OldLayout, VkImageLayout NewLayout)
^
src\engine\client\backend\vulkan\backend_vulkan.cpp:6495:46: style: Parameter 'ExecBuffer' can be declared as reference to const [constParameter]
void Cmd_Clear(SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand_Clear *pCommand)
^
src\game\client\components\skins.cpp:83:72: style: Parameter 'pImg' can be declared as pointer to const [constParameter]
static void CheckMetrics(CSkin::SSkinMetricVariable &Metrics, uint8_t *pImg, int ImgWidth, int ImgX, int ImgY, int CheckWidth, int CheckHeight)
^
src\game\client\prediction\entities\character.h:106:37: style: Parameter 'pNewInput' can be declared as pointer to const [constParameter]
void SetInput(CNetObj_PlayerInput *pNewInput)
^
src\game\client\prediction\gameworld.cpp:245:106: style: Parameter 'pNotThis' can be declared as pointer to const [constParameter]
CCharacter *CGameWorld::IntersectCharacter(vec2 Pos0, vec2 Pos1, float Radius, vec2 &NewPos, CCharacter *pNotThis, int CollideWith, class CCharacter *pThisOnly)
^
src\game\client\prediction\gameworld.cpp:245:151: style: Parameter 'pThisOnly' can be declared as pointer to const [constParameter]
CCharacter *CGameWorld::IntersectCharacter(vec2 Pos0, vec2 Pos1, float Radius, vec2 &NewPos, CCharacter *pNotThis, int CollideWith, class CCharacter *pThisOnly)
^
src\game\client\prediction\gameworld.cpp:283:116: style: Parameter 'pNotThis' can be declared as pointer to const [constParameter]
std::list<class CCharacter *> CGameWorld::IntersectedCharacters(vec2 Pos0, vec2 Pos1, float Radius, class CEntity *pNotThis)
^
src\game\client\ui.cpp:522:180: style: Parameter 'pReadCursor' can be declared as pointer to const [constParameter]
void CUI::DoLabel(CUIElement::SUIElementRect &RectEl, const CUIRect *pRect, const char *pText, float Size, int Align, const SLabelProperties &LabelProps, int StrLen, CTextCursor *pReadCursor)
^
src\game\client\ui_scrollregion.cpp:23:86: style: Parameter 'pParams' can be declared as pointer to const [constParameter]
void CScrollRegion::Begin(CUIRect *pClipRect, vec2 *pOutOffset, CScrollRegionParams *pParams)
^
src\game\server\scoreworker.h:239:29: style: Parameter 'aTimeCp' can be declared as const array [constParameter]
void Set(float Time, float aTimeCp[NUM_CHECKPOINTS])
^
src\game\server\score.cpp:135:80: style: Parameter 'aTimeCp' can be declared as const array [constParameter]
void CScore::SaveScore(int ClientID, float Time, const char *pTimestamp, float aTimeCp[NUM_CHECKPOINTS], bool NotEligible)
^
src\game\server\teeinfo.cpp:40:57: style: Parameter 'pUseCustomColors' can be declared as pointer to const [constParameter]
CTeeInfo::CTeeInfo(const char *apSkinPartNames[6], int *pUseCustomColors, int *pSkinPartColors)
^
src\game\server\teeinfo.cpp:40:80: style: Parameter 'pSkinPartColors' can be declared as pointer to const [constParameter]
CTeeInfo::CTeeInfo(const char *apSkinPartNames[6], int *pUseCustomColors, int *pSkinPartColors)
^
```
2022-11-13 14:32:53 +00:00
|
|
|
static void CheckMetrics(CSkin::SSkinMetricVariable &Metrics, const uint8_t *pImg, int ImgWidth, int ImgX, int ImgY, int CheckWidth, int CheckHeight)
|
2020-11-12 07:35:07 +00:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2022-10-12 17:25:59 +00:00
|
|
|
const CSkin *CSkins::LoadSkin(const char *pName, const char *pPath, int DirType)
|
2020-09-20 00:19:24 +00:00
|
|
|
{
|
2010-05-29 07:25:38 +00:00
|
|
|
CImageInfo Info;
|
2020-12-12 11:37:43 +00:00
|
|
|
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))
|
2007-11-18 12:03:59 +00:00
|
|
|
{
|
2010-08-17 22:06:00 +00:00
|
|
|
str_format(aBuf, sizeof(aBuf), "failed to load skin from %s", pName);
|
2020-09-20 00:19:24 +00:00
|
|
|
Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "game", aBuf);
|
2020-12-12 11:37:43 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-10-12 17:25:59 +00:00
|
|
|
const CSkin *CSkins::LoadSkin(const char *pName, CImageInfo &Info)
|
2020-12-12 11:37:43 +00:00
|
|
|
{
|
|
|
|
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);
|
2022-10-12 17:25:59 +00:00
|
|
|
return nullptr;
|
2007-11-18 12:03:59 +00:00
|
|
|
}
|
2022-02-22 23:39:31 +00:00
|
|
|
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);
|
2022-10-12 17:25:59 +00:00
|
|
|
return nullptr;
|
2022-02-22 18:43:14 +00:00
|
|
|
}
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2022-10-12 17:25:59 +00:00
|
|
|
CSkin Skin{pName};
|
2020-10-09 07:07:05 +00:00
|
|
|
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)
|
2022-06-30 22:36:32 +00:00
|
|
|
Skin.m_OriginalSkin.m_aEyes[i] = Graphics()->LoadSpriteTexture(Info, &g_pData->m_aSprites[SPRITE_TEE_EYE_NORMAL + i]);
|
2011-04-13 18:37:12 +00:00
|
|
|
|
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);
|
2021-05-19 10:10:58 +00:00
|
|
|
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;
|
|
|
|
|
2021-05-19 10:10:58 +00:00
|
|
|
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)
|
2022-10-12 17:25:59 +00:00
|
|
|
return nullptr;
|
2022-06-30 22:36:32 +00:00
|
|
|
unsigned char *pData = (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};
|
2021-05-19 10:10:58 +00:00
|
|
|
for(int y = 0; y < BodyHeight; y++)
|
|
|
|
for(int x = 0; x < BodyWidth; x++)
|
2008-03-17 01:41:11 +00:00
|
|
|
{
|
2022-06-30 22:36:32 +00:00
|
|
|
uint8_t AlphaValue = pData[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-06-30 22:36:32 +00:00
|
|
|
aColors[0] += pData[y * Pitch + x * PixelStep + 0];
|
|
|
|
aColors[1] += pData[y * Pitch + x * PixelStep + 1];
|
|
|
|
aColors[2] += pData[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
|
|
|
}
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2022-06-30 22:36:32 +00:00
|
|
|
CheckMetrics(Skin.m_Metrics.m_Body, pData, Pitch, 0, 0, BodyWidth, BodyHeight);
|
2020-11-08 05:39:16 +00:00
|
|
|
|
2020-11-12 07:35:07 +00:00
|
|
|
// body outline metrics
|
2022-06-30 22:36:32 +00:00
|
|
|
CheckMetrics(Skin.m_Metrics.m_Body, pData, Pitch, BodyOutlineOffsetX, BodyOutlineOffsetY, BodyOutlineWidth, BodyOutlineHeight);
|
2020-11-08 05:39:16 +00:00
|
|
|
|
|
|
|
// get feet size
|
2022-06-30 22:36:32 +00:00
|
|
|
CheckMetrics(Skin.m_Metrics.m_Feet, pData, 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
|
2022-06-30 22:36:32 +00:00
|
|
|
CheckMetrics(Skin.m_Metrics.m_Feet, pData, Pitch, FeetOutlineOffsetX, FeetOutlineOffsetY, FeetOutlineWidth, FeetOutlineHeight);
|
2020-11-08 05:39:16 +00:00
|
|
|
|
2008-03-23 12:36:24 +00:00
|
|
|
// make the texture gray scale
|
2020-09-26 19:41:58 +00:00
|
|
|
for(int i = 0; i < Info.m_Width * Info.m_Height; i++)
|
2007-11-18 12:03:59 +00:00
|
|
|
{
|
2022-06-30 22:36:32 +00:00
|
|
|
int v = (pData[i * PixelStep] + pData[i * PixelStep + 1] + pData[i * PixelStep + 2]) / 3;
|
|
|
|
pData[i * PixelStep] = v;
|
|
|
|
pData[i * PixelStep + 1] = v;
|
|
|
|
pData[i * PixelStep + 2] = v;
|
2007-11-18 12:03:59 +00:00
|
|
|
}
|
2008-03-17 01:41:11 +00:00
|
|
|
|
2022-06-30 22:36:32 +00:00
|
|
|
int aFreq[256] = {0};
|
2011-02-13 12:58:59 +00:00
|
|
|
int OrgWeight = 0;
|
|
|
|
int NewWeight = 192;
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2011-02-13 12:58:59 +00:00
|
|
|
// find most common frequence
|
2021-05-19 10:10:58 +00:00
|
|
|
for(int y = 0; y < BodyHeight; y++)
|
|
|
|
for(int x = 0; x < BodyWidth; x++)
|
2007-11-26 22:26:33 +00:00
|
|
|
{
|
2022-06-30 22:36:32 +00:00
|
|
|
if(pData[y * Pitch + x * PixelStep + 3] > 128)
|
|
|
|
aFreq[pData[y * Pitch + x * PixelStep]]++;
|
2007-11-26 22:26:33 +00:00
|
|
|
}
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2011-02-13 12:58:59 +00:00
|
|
|
for(int i = 1; i < 256; i++)
|
|
|
|
{
|
2022-06-30 22:36:32 +00:00
|
|
|
if(aFreq[OrgWeight] < aFreq[i])
|
2011-02-13 12:58:59 +00:00
|
|
|
OrgWeight = i;
|
2007-11-26 22:26:33 +00:00
|
|
|
}
|
2011-02-13 12:58:59 +00:00
|
|
|
|
|
|
|
// reorder
|
2020-09-26 19:41:58 +00:00
|
|
|
int InvOrgWeight = 255 - OrgWeight;
|
|
|
|
int InvNewWeight = 255 - NewWeight;
|
2021-05-19 10:10:58 +00:00
|
|
|
for(int y = 0; y < BodyHeight; y++)
|
|
|
|
for(int x = 0; x < BodyWidth; x++)
|
2011-02-13 12:58:59 +00:00
|
|
|
{
|
2022-06-30 22:36:32 +00:00
|
|
|
int v = pData[y * Pitch + x * PixelStep];
|
2020-10-12 10:31:43 +00:00
|
|
|
if(v <= OrgWeight && OrgWeight == 0)
|
Don't divide by 0 in LoadSkin
src/game/client/components/skins.cpp:142:14: runtime error: -nan is outside the range of representable values of type 'int'
#0 0x55b6bd0f49e1 in CSkins::LoadSkin(char const*, char const*, int, int*) /media/ddnet/src/game/client/components/skins.cpp:142:14
#1 0x55b6bd0f0942 in CSkins::SkinScan(char const*, int, int, void*) /media/ddnet/src/game/client/components/skins.cpp:55:16
#2 0x55b6bcb33928 in fs_listdir /media/ddnet/src/base/system.c:2033:6
#3 0x55b6bcb04024 in CStorage::ListDirectory(int, char const*, int (*)(char const*, int, int, void*), void*) /media/ddnet/src/engine/shared/storage.cpp:316:5
#4 0x55b6bd0f9e2f in CSkins::Refresh() /media/ddnet/src/game/client/components/skins.cpp:222:13
#5 0x55b6bd0f6e33 in CSkins::OnInit() /media/ddnet/src/game/client/components/skins.cpp:194:2
#6 0x55b6bd14c63f in CGameClient::OnInit() /media/ddnet/src/game/client/gameclient.cpp:322:28
#7 0x55b6bcc5f9f8 in CClient::Run() /media/ddnet/src/engine/client/client.cpp:3089:16
#8 0x55b6bcc84b7e in main /media/ddnet/src/engine/client/client.cpp:4341:11
#9 0x7f1144ded151 in __libc_start_main (/usr/lib/libc.so.6+0x28151)
#10 0x55b6bc9d1e0d in _start (/media/ddnet/DDNet+0x705e0d)
2020-10-10 10:14:07 +00:00
|
|
|
v = 0;
|
|
|
|
else if(v <= OrgWeight)
|
2020-09-26 19:41:58 +00:00
|
|
|
v = (int)(((v / (float)OrgWeight) * NewWeight));
|
Don't divide by 0 in LoadSkin
src/game/client/components/skins.cpp:142:14: runtime error: -nan is outside the range of representable values of type 'int'
#0 0x55b6bd0f49e1 in CSkins::LoadSkin(char const*, char const*, int, int*) /media/ddnet/src/game/client/components/skins.cpp:142:14
#1 0x55b6bd0f0942 in CSkins::SkinScan(char const*, int, int, void*) /media/ddnet/src/game/client/components/skins.cpp:55:16
#2 0x55b6bcb33928 in fs_listdir /media/ddnet/src/base/system.c:2033:6
#3 0x55b6bcb04024 in CStorage::ListDirectory(int, char const*, int (*)(char const*, int, int, void*), void*) /media/ddnet/src/engine/shared/storage.cpp:316:5
#4 0x55b6bd0f9e2f in CSkins::Refresh() /media/ddnet/src/game/client/components/skins.cpp:222:13
#5 0x55b6bd0f6e33 in CSkins::OnInit() /media/ddnet/src/game/client/components/skins.cpp:194:2
#6 0x55b6bd14c63f in CGameClient::OnInit() /media/ddnet/src/game/client/gameclient.cpp:322:28
#7 0x55b6bcc5f9f8 in CClient::Run() /media/ddnet/src/engine/client/client.cpp:3089:16
#8 0x55b6bcc84b7e in main /media/ddnet/src/engine/client/client.cpp:4341:11
#9 0x7f1144ded151 in __libc_start_main (/usr/lib/libc.so.6+0x28151)
#10 0x55b6bc9d1e0d in _start (/media/ddnet/DDNet+0x705e0d)
2020-10-10 10:14:07 +00:00
|
|
|
else if(InvOrgWeight == 0)
|
|
|
|
v = NewWeight;
|
2011-02-13 12:58:59 +00:00
|
|
|
else
|
2020-09-26 19:41:58 +00:00
|
|
|
v = (int)(((v - OrgWeight) / (float)InvOrgWeight) * InvNewWeight + NewWeight);
|
2022-06-30 22:36:32 +00:00
|
|
|
pData[y * Pitch + x * PixelStep] = v;
|
|
|
|
pData[y * Pitch + x * PixelStep + 1] = v;
|
|
|
|
pData[y * Pitch + x * PixelStep + 2] = v;
|
2011-02-13 12:58:59 +00:00
|
|
|
}
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2020-10-09 07:07:05 +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)
|
2022-06-30 22:36:32 +00:00
|
|
|
Skin.m_ColorableSkin.m_aEyes[i] = Graphics()->LoadSpriteTexture(Info, &g_pData->m_aSprites[SPRITE_TEE_EYE_NORMAL + i]);
|
2020-10-09 07:07:05 +00:00
|
|
|
|
2020-11-25 12:05:53 +00:00
|
|
|
Graphics()->FreePNG(&Info);
|
2007-11-18 12:03:59 +00:00
|
|
|
|
2011-04-13 18:37:12 +00:00
|
|
|
// set skin data
|
2011-12-10 17:23:29 +00:00
|
|
|
if(g_Config.m_Debug)
|
|
|
|
{
|
2022-10-12 17:25:59 +00:00
|
|
|
str_format(aBuf, sizeof(aBuf), "load skin %s", Skin.GetName());
|
2020-09-20 00:19:24 +00:00
|
|
|
Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "game", aBuf);
|
2011-12-10 17:23:29 +00:00
|
|
|
}
|
2011-02-21 10:23:30 +00:00
|
|
|
|
2022-10-12 17:25:59 +00:00
|
|
|
auto &&pSkin = std::make_unique<CSkin>(std::move(Skin));
|
|
|
|
const auto SkinInsertIt = m_Skins.insert({pSkin->GetName(), std::move(pSkin)});
|
2020-10-09 07:07:05 +00:00
|
|
|
|
2022-10-12 17:25:59 +00:00
|
|
|
return SkinInsertIt.first->second.get();
|
2007-11-18 12:03:59 +00:00
|
|
|
}
|
|
|
|
|
2011-02-13 12:58:59 +00:00
|
|
|
void CSkins::OnInit()
|
2007-11-18 12:03:59 +00:00
|
|
|
{
|
2022-06-30 22:36:32 +00:00
|
|
|
m_aEventSkinPrefix[0] = '\0';
|
2018-12-23 21:53:10 +00:00
|
|
|
|
|
|
|
if(g_Config.m_Events)
|
|
|
|
{
|
2022-06-30 22:36:32 +00:00
|
|
|
time_t RawTime;
|
|
|
|
struct tm *pTimeInfo;
|
|
|
|
std::time(&RawTime);
|
|
|
|
pTimeInfo = localtime(&RawTime);
|
|
|
|
if(pTimeInfo->tm_mon == 11 && pTimeInfo->tm_mday >= 24 && pTimeInfo->tm_mday <= 26)
|
2018-12-23 21:53:10 +00:00
|
|
|
{ // Christmas
|
2022-07-09 16:14:56 +00:00
|
|
|
str_copy(m_aEventSkinPrefix, "santa");
|
2018-12-23 21:53:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-02 11:37:59 +00:00
|
|
|
// load skins;
|
2022-10-12 17:25:59 +00:00
|
|
|
Refresh([this](int SkinCounter) {
|
2022-06-23 17:59:38 +00:00
|
|
|
GameClient()->m_Menus.RenderLoading(Localize("Loading DDNet Client"), Localize("Loading skin files"), 0);
|
2022-04-02 11:37:59 +00:00
|
|
|
});
|
2020-10-06 14:00:18 +00:00
|
|
|
}
|
|
|
|
|
2022-04-02 11:37:59 +00:00
|
|
|
void CSkins::Refresh(TSkinLoadedCBFunc &&SkinLoadedFunc)
|
2020-10-06 14:00:18 +00:00
|
|
|
{
|
2022-10-12 17:25:59 +00:00
|
|
|
for(const auto &SkinIt : m_Skins)
|
2020-10-06 14:00:18 +00:00
|
|
|
{
|
2022-10-12 17:25:59 +00:00
|
|
|
const auto &pSkin = SkinIt.second;
|
|
|
|
Graphics()->UnloadTexture(&pSkin->m_OriginalSkin.m_Body);
|
|
|
|
Graphics()->UnloadTexture(&pSkin->m_OriginalSkin.m_BodyOutline);
|
|
|
|
Graphics()->UnloadTexture(&pSkin->m_OriginalSkin.m_Feet);
|
|
|
|
Graphics()->UnloadTexture(&pSkin->m_OriginalSkin.m_FeetOutline);
|
|
|
|
Graphics()->UnloadTexture(&pSkin->m_OriginalSkin.m_Hands);
|
|
|
|
Graphics()->UnloadTexture(&pSkin->m_OriginalSkin.m_HandsOutline);
|
|
|
|
for(auto &Eye : pSkin->m_OriginalSkin.m_aEyes)
|
2021-09-15 11:19:54 +00:00
|
|
|
Graphics()->UnloadTexture(&Eye);
|
|
|
|
|
2022-10-12 17:25:59 +00:00
|
|
|
Graphics()->UnloadTexture(&pSkin->m_ColorableSkin.m_Body);
|
|
|
|
Graphics()->UnloadTexture(&pSkin->m_ColorableSkin.m_BodyOutline);
|
|
|
|
Graphics()->UnloadTexture(&pSkin->m_ColorableSkin.m_Feet);
|
|
|
|
Graphics()->UnloadTexture(&pSkin->m_ColorableSkin.m_FeetOutline);
|
|
|
|
Graphics()->UnloadTexture(&pSkin->m_ColorableSkin.m_Hands);
|
|
|
|
Graphics()->UnloadTexture(&pSkin->m_ColorableSkin.m_HandsOutline);
|
|
|
|
for(auto &Eye : pSkin->m_ColorableSkin.m_aEyes)
|
2021-09-15 11:19:54 +00:00
|
|
|
Graphics()->UnloadTexture(&Eye);
|
2020-10-06 14:00:18 +00:00
|
|
|
}
|
|
|
|
|
2022-10-12 17:25:59 +00:00
|
|
|
m_Skins.clear();
|
|
|
|
m_DownloadSkins.clear();
|
2022-10-04 16:40:24 +00:00
|
|
|
m_DownloadingSkins = 0;
|
2022-04-02 11:37:59 +00:00
|
|
|
SSkinScanUser SkinScanUser;
|
|
|
|
SkinScanUser.m_pThis = this;
|
|
|
|
SkinScanUser.m_SkinLoadedFunc = SkinLoadedFunc;
|
|
|
|
Storage()->ListDirectory(IStorage::TYPE_ALL, "skins", SkinScan, &SkinScanUser);
|
2022-10-12 17:25:59 +00:00
|
|
|
if(m_Skins.empty())
|
2011-01-06 21:55:57 +00:00
|
|
|
{
|
|
|
|
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "gameclient", "failed to load skins. folder='skins/'");
|
2022-10-12 17:25:59 +00:00
|
|
|
CSkin DummySkin{"dummy"};
|
2019-04-26 12:06:32 +00:00
|
|
|
DummySkin.m_BloodColor = ColorRGBA(1.0f, 1.0f, 1.0f);
|
2022-10-12 17:25:59 +00:00
|
|
|
auto &&pDummySkin = std::make_unique<CSkin>(std::move(DummySkin));
|
|
|
|
m_Skins.insert({pDummySkin->GetName(), std::move(pDummySkin)});
|
2011-01-06 21:55:57 +00:00
|
|
|
}
|
2007-11-18 12:03:59 +00:00
|
|
|
}
|
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
int CSkins::Num()
|
2007-11-18 12:03:59 +00:00
|
|
|
{
|
2022-10-12 17:25:59 +00:00
|
|
|
return m_Skins.size();
|
2007-11-18 12:03:59 +00:00
|
|
|
}
|
|
|
|
|
2022-10-12 17:25:59 +00:00
|
|
|
const CSkin *CSkins::Find(const char *pName)
|
2007-11-18 12:03:59 +00:00
|
|
|
{
|
2022-10-12 17:25:59 +00:00
|
|
|
const auto *pSkin = FindOrNullptr(pName);
|
|
|
|
if(pSkin == nullptr)
|
2018-07-25 20:41:24 +00:00
|
|
|
{
|
2022-10-12 17:25:59 +00:00
|
|
|
pSkin = FindOrNullptr("default");
|
|
|
|
if(pSkin == nullptr)
|
|
|
|
return m_Skins.begin()->second.get();
|
|
|
|
else
|
|
|
|
return pSkin;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return pSkin;
|
2018-07-25 20:41:24 +00:00
|
|
|
}
|
2007-11-18 12:03:59 +00:00
|
|
|
}
|
|
|
|
|
2022-10-12 17:25:59 +00:00
|
|
|
const CSkin *CSkins::FindOrNullptr(const char *pName)
|
2007-11-18 12:03:59 +00:00
|
|
|
{
|
2022-06-30 22:36:32 +00:00
|
|
|
const char *pSkinPrefix = m_aEventSkinPrefix[0] ? m_aEventSkinPrefix : g_Config.m_ClSkinPrefix;
|
2018-08-21 16:27:34 +00:00
|
|
|
if(g_Config.m_ClVanillaSkinsOnly && !IsVanillaSkin(pName))
|
2017-07-24 22:11:25 +00:00
|
|
|
{
|
2022-10-12 17:25:59 +00:00
|
|
|
return nullptr;
|
2018-08-21 16:27:34 +00:00
|
|
|
}
|
2020-09-20 00:19:24 +00:00
|
|
|
else if(pSkinPrefix && pSkinPrefix[0])
|
2018-08-21 16:27:34 +00:00
|
|
|
{
|
2020-09-20 00:19:02 +00:00
|
|
|
char aBuf[24];
|
2018-12-23 21:53:10 +00:00
|
|
|
str_format(aBuf, sizeof(aBuf), "%s_%s", pSkinPrefix, pName);
|
2018-08-21 16:27:34 +00:00
|
|
|
// If we find something, use it, otherwise fall back to normal skins.
|
2022-10-12 17:25:59 +00:00
|
|
|
const auto *pResult = FindImpl(aBuf);
|
|
|
|
if(pResult != nullptr)
|
2018-08-13 18:54:01 +00:00
|
|
|
{
|
2022-10-12 17:25:59 +00:00
|
|
|
return pResult;
|
2017-07-24 22:11:25 +00:00
|
|
|
}
|
|
|
|
}
|
2018-07-24 15:26:39 +00:00
|
|
|
return FindImpl(pName);
|
|
|
|
}
|
|
|
|
|
2022-10-12 17:25:59 +00:00
|
|
|
const CSkin *CSkins::FindImpl(const char *pName)
|
2018-07-24 15:26:39 +00:00
|
|
|
{
|
2022-10-12 17:25:59 +00:00
|
|
|
auto SkinIt = m_Skins.find(pName);
|
|
|
|
if(SkinIt != m_Skins.end())
|
|
|
|
return SkinIt->second.get();
|
2020-09-20 00:19:24 +00:00
|
|
|
|
|
|
|
if(str_comp(pName, "default") == 0)
|
2022-10-12 17:25:59 +00:00
|
|
|
return nullptr;
|
2020-09-04 14:40:35 +00:00
|
|
|
|
2020-09-20 00:19:24 +00:00
|
|
|
if(!g_Config.m_ClDownloadSkins)
|
2022-10-12 17:25:59 +00:00
|
|
|
return nullptr;
|
2020-09-20 00:19:24 +00:00
|
|
|
|
|
|
|
if(str_find(pName, "/") != 0)
|
2022-10-12 17:25:59 +00:00
|
|
|
return nullptr;
|
2020-09-20 00:19:24 +00:00
|
|
|
|
2022-10-12 17:25:59 +00:00
|
|
|
const auto SkinDownloadIt = m_DownloadSkins.find(pName);
|
|
|
|
if(SkinDownloadIt != m_DownloadSkins.end())
|
2020-09-20 00:19:24 +00:00
|
|
|
{
|
2022-10-12 17:25:59 +00:00
|
|
|
if(SkinDownloadIt->second->m_pTask && SkinDownloadIt->second->m_pTask->State() == HTTP_DONE)
|
2020-09-20 00:19:24 +00:00
|
|
|
{
|
2021-09-13 08:06:34 +00:00
|
|
|
char aPath[IO_MAX_PATH_LENGTH];
|
2022-10-12 17:25:59 +00:00
|
|
|
str_format(aPath, sizeof(aPath), "downloadedskins/%s.png", SkinDownloadIt->second->GetName());
|
|
|
|
Storage()->RenameFile(SkinDownloadIt->second->m_aPath, aPath, IStorage::TYPE_SAVE);
|
|
|
|
const auto *pSkin = LoadSkin(SkinDownloadIt->second->GetName(), SkinDownloadIt->second->m_pTask->m_Info);
|
|
|
|
SkinDownloadIt->second->m_pTask = nullptr;
|
2022-10-04 16:40:24 +00:00
|
|
|
--m_DownloadingSkins;
|
2022-10-12 17:25:59 +00:00
|
|
|
return pSkin;
|
2020-09-20 00:19:24 +00:00
|
|
|
}
|
2022-10-12 17:25:59 +00:00
|
|
|
if(SkinDownloadIt->second->m_pTask && (SkinDownloadIt->second->m_pTask->State() == HTTP_ERROR || SkinDownloadIt->second->m_pTask->State() == HTTP_ABORTED))
|
2020-09-20 00:19:24 +00:00
|
|
|
{
|
2022-10-12 17:25:59 +00:00
|
|
|
SkinDownloadIt->second->m_pTask = nullptr;
|
2022-10-04 16:40:24 +00:00
|
|
|
--m_DownloadingSkins;
|
2020-09-20 00:19:24 +00:00
|
|
|
}
|
2022-10-12 17:25:59 +00:00
|
|
|
return nullptr;
|
2020-09-20 00:19:24 +00:00
|
|
|
}
|
|
|
|
|
2022-10-12 17:25:59 +00:00
|
|
|
CDownloadSkin Skin{pName};
|
2020-09-20 00:19:24 +00:00
|
|
|
|
2021-12-20 14:00:21 +00:00
|
|
|
char aUrl[IO_MAX_PATH_LENGTH];
|
2021-11-07 16:01:48 +00:00
|
|
|
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);
|
2021-12-20 14:00:21 +00:00
|
|
|
char aBuf[IO_MAX_PATH_LENGTH];
|
|
|
|
str_format(Skin.m_aPath, sizeof(Skin.m_aPath), "downloadedskins/%s", IStorage::FormatTmpPath(aBuf, sizeof(aBuf), pName));
|
2022-05-05 12:49:25 +00:00
|
|
|
Skin.m_pTask = std::make_shared<CGetPngFile>(this, aUrl, Storage(), Skin.m_aPath);
|
2020-09-20 00:19:24 +00:00
|
|
|
m_pClient->Engine()->AddJob(Skin.m_pTask);
|
2022-10-12 17:25:59 +00:00
|
|
|
auto &&pDownloadSkin = std::make_unique<CDownloadSkin>(std::move(Skin));
|
|
|
|
m_DownloadSkins.insert({pDownloadSkin->GetName(), std::move(pDownloadSkin)});
|
2022-10-04 16:40:24 +00:00
|
|
|
++m_DownloadingSkins;
|
2022-10-12 17:25:59 +00:00
|
|
|
return nullptr;
|
2007-11-18 12:03:59 +00:00
|
|
|
}
|