1 | /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ |
2 | /* If you are missing that file, acquire a complete release at teeworlds.com. */ |
3 | |
4 | #include <base/log.h> |
5 | #include <base/math.h> |
6 | #include <base/system.h> |
7 | |
8 | #include <engine/engine.h> |
9 | #include <engine/graphics.h> |
10 | #include <engine/shared/config.h> |
11 | #include <engine/storage.h> |
12 | |
13 | #include <game/generated/client_data.h> |
14 | |
15 | #include <game/client/gameclient.h> |
16 | #include <game/localization.h> |
17 | |
18 | #include "skins.h" |
19 | |
20 | CSkins::CSkins() : |
21 | m_PlaceholderSkin("dummy" ) |
22 | { |
23 | m_PlaceholderSkin.m_OriginalSkin.Reset(); |
24 | m_PlaceholderSkin.m_ColorableSkin.Reset(); |
25 | m_PlaceholderSkin.m_BloodColor = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f); |
26 | m_PlaceholderSkin.m_Metrics.m_Body.m_Width = 64; |
27 | m_PlaceholderSkin.m_Metrics.m_Body.m_Height = 64; |
28 | m_PlaceholderSkin.m_Metrics.m_Body.m_OffsetX = 16; |
29 | m_PlaceholderSkin.m_Metrics.m_Body.m_OffsetY = 16; |
30 | m_PlaceholderSkin.m_Metrics.m_Body.m_MaxWidth = 96; |
31 | m_PlaceholderSkin.m_Metrics.m_Body.m_MaxHeight = 96; |
32 | m_PlaceholderSkin.m_Metrics.m_Feet.m_Width = 32; |
33 | m_PlaceholderSkin.m_Metrics.m_Feet.m_Height = 16; |
34 | m_PlaceholderSkin.m_Metrics.m_Feet.m_OffsetX = 16; |
35 | m_PlaceholderSkin.m_Metrics.m_Feet.m_OffsetY = 8; |
36 | m_PlaceholderSkin.m_Metrics.m_Feet.m_MaxWidth = 64; |
37 | m_PlaceholderSkin.m_Metrics.m_Feet.m_MaxHeight = 32; |
38 | } |
39 | |
40 | bool CSkins::IsVanillaSkin(const char *pName) |
41 | { |
42 | return std::any_of(first: std::begin(arr: VANILLA_SKINS), last: std::end(arr: VANILLA_SKINS), pred: [pName](const char *pVanillaSkin) { return str_comp(a: pName, b: pVanillaSkin) == 0; }); |
43 | } |
44 | |
45 | void CSkins::CGetPngFile::OnCompletion(EHttpState State) |
46 | { |
47 | // Maybe this should start another thread to load the png in instead of stalling the curl thread |
48 | if(State == EHttpState::DONE) |
49 | { |
50 | m_pSkins->LoadSkinPng(Info&: m_Info, pName: Dest(), pPath: Dest(), DirType: IStorage::TYPE_SAVE); |
51 | } |
52 | } |
53 | |
54 | CSkins::CGetPngFile::CGetPngFile(CSkins *pSkins, const char *pUrl, IStorage *pStorage, const char *pDest) : |
55 | CHttpRequest(pUrl), |
56 | m_pSkins(pSkins) |
57 | { |
58 | WriteToFile(pStorage, pDest, StorageType: IStorage::TYPE_SAVE); |
59 | Timeout(Timeout: CTimeout{.ConnectTimeoutMs: 0, .TimeoutMs: 0, .LowSpeedLimit: 0, .LowSpeedTime: 0}); |
60 | LogProgress(LogProgress: HTTPLOG::NONE); |
61 | } |
62 | |
63 | struct SSkinScanUser |
64 | { |
65 | CSkins *m_pThis; |
66 | CSkins::TSkinLoadedCBFunc m_SkinLoadedFunc; |
67 | }; |
68 | |
69 | int CSkins::SkinScan(const char *pName, int IsDir, int DirType, void *pUser) |
70 | { |
71 | auto *pUserReal = static_cast<SSkinScanUser *>(pUser); |
72 | CSkins *pSelf = pUserReal->m_pThis; |
73 | |
74 | if(IsDir) |
75 | return 0; |
76 | |
77 | const char *pSuffix = str_endswith(str: pName, suffix: ".png" ); |
78 | if(pSuffix == nullptr) |
79 | return 0; |
80 | |
81 | char aSkinName[IO_MAX_PATH_LENGTH]; |
82 | str_truncate(dst: aSkinName, dst_size: sizeof(aSkinName), src: pName, truncation_len: pSuffix - pName); |
83 | if(!CSkin::IsValidName(pName: aSkinName)) |
84 | { |
85 | log_error("skins" , "Skin name is not valid: %s" , aSkinName); |
86 | log_error("skins" , "%s" , CSkin::m_aSkinNameRestrictions); |
87 | return 0; |
88 | } |
89 | |
90 | if(g_Config.m_ClVanillaSkinsOnly && !IsVanillaSkin(pName: aSkinName)) |
91 | return 0; |
92 | |
93 | char aPath[IO_MAX_PATH_LENGTH]; |
94 | str_format(buffer: aPath, buffer_size: sizeof(aPath), format: "skins/%s" , pName); |
95 | pSelf->LoadSkin(pName: aSkinName, pPath: aPath, DirType); |
96 | pUserReal->m_SkinLoadedFunc((int)pSelf->m_Skins.size()); |
97 | return 0; |
98 | } |
99 | |
100 | static void CheckMetrics(CSkin::SSkinMetricVariable &Metrics, const uint8_t *pImg, int ImgWidth, int ImgX, int ImgY, int CheckWidth, int CheckHeight) |
101 | { |
102 | int MaxY = -1; |
103 | int MinY = CheckHeight + 1; |
104 | int MaxX = -1; |
105 | int MinX = CheckWidth + 1; |
106 | |
107 | for(int y = 0; y < CheckHeight; y++) |
108 | { |
109 | for(int x = 0; x < CheckWidth; x++) |
110 | { |
111 | int OffsetAlpha = (y + ImgY) * ImgWidth + (x + ImgX) * 4 + 3; |
112 | uint8_t AlphaValue = pImg[OffsetAlpha]; |
113 | if(AlphaValue > 0) |
114 | { |
115 | if(MaxY < y) |
116 | MaxY = y; |
117 | if(MinY > y) |
118 | MinY = y; |
119 | if(MaxX < x) |
120 | MaxX = x; |
121 | if(MinX > x) |
122 | MinX = x; |
123 | } |
124 | } |
125 | } |
126 | |
127 | Metrics.m_Width = clamp(val: (MaxX - MinX) + 1, lo: 1, hi: CheckWidth); |
128 | Metrics.m_Height = clamp(val: (MaxY - MinY) + 1, lo: 1, hi: CheckHeight); |
129 | Metrics.m_OffsetX = clamp(val: MinX, lo: 0, hi: CheckWidth - 1); |
130 | Metrics.m_OffsetY = clamp(val: MinY, lo: 0, hi: CheckHeight - 1); |
131 | Metrics.m_MaxWidth = CheckWidth; |
132 | Metrics.m_MaxHeight = CheckHeight; |
133 | } |
134 | |
135 | const CSkin *CSkins::LoadSkin(const char *pName, const char *pPath, int DirType) |
136 | { |
137 | CImageInfo Info; |
138 | if(!LoadSkinPng(Info, pName, pPath, DirType)) |
139 | return nullptr; |
140 | return LoadSkin(pName, Info); |
141 | } |
142 | |
143 | bool CSkins::LoadSkinPng(CImageInfo &Info, const char *pName, const char *pPath, int DirType) |
144 | { |
145 | if(!Graphics()->LoadPng(Image&: Info, pFilename: pPath, StorageType: DirType)) |
146 | { |
147 | log_error("skins" , "Failed to load skin PNG: %s" , pName); |
148 | return false; |
149 | } |
150 | return true; |
151 | } |
152 | |
153 | const CSkin *CSkins::LoadSkin(const char *pName, CImageInfo &Info) |
154 | { |
155 | if(!Graphics()->CheckImageDivisibility(pFileName: pName, Img&: Info, DivX: g_pData->m_aSprites[SPRITE_TEE_BODY].m_pSet->m_Gridx, DivY: g_pData->m_aSprites[SPRITE_TEE_BODY].m_pSet->m_Gridy, AllowResize: true)) |
156 | { |
157 | log_error("skins" , "Skin failed image divisibility: %s" , pName); |
158 | return nullptr; |
159 | } |
160 | if(!Graphics()->IsImageFormatRGBA(pFileName: pName, Img&: Info)) |
161 | { |
162 | log_error("skins" , "Skin format is not RGBA: %s" , pName); |
163 | return nullptr; |
164 | } |
165 | |
166 | CSkin Skin{pName}; |
167 | Skin.m_OriginalSkin.m_Body = Graphics()->LoadSpriteTexture(FromImageInfo: Info, pSprite: &g_pData->m_aSprites[SPRITE_TEE_BODY]); |
168 | Skin.m_OriginalSkin.m_BodyOutline = Graphics()->LoadSpriteTexture(FromImageInfo: Info, pSprite: &g_pData->m_aSprites[SPRITE_TEE_BODY_OUTLINE]); |
169 | Skin.m_OriginalSkin.m_Feet = Graphics()->LoadSpriteTexture(FromImageInfo: Info, pSprite: &g_pData->m_aSprites[SPRITE_TEE_FOOT]); |
170 | Skin.m_OriginalSkin.m_FeetOutline = Graphics()->LoadSpriteTexture(FromImageInfo: Info, pSprite: &g_pData->m_aSprites[SPRITE_TEE_FOOT_OUTLINE]); |
171 | Skin.m_OriginalSkin.m_Hands = Graphics()->LoadSpriteTexture(FromImageInfo: Info, pSprite: &g_pData->m_aSprites[SPRITE_TEE_HAND]); |
172 | Skin.m_OriginalSkin.m_HandsOutline = Graphics()->LoadSpriteTexture(FromImageInfo: Info, pSprite: &g_pData->m_aSprites[SPRITE_TEE_HAND_OUTLINE]); |
173 | |
174 | for(int i = 0; i < 6; ++i) |
175 | Skin.m_OriginalSkin.m_aEyes[i] = Graphics()->LoadSpriteTexture(FromImageInfo: Info, pSprite: &g_pData->m_aSprites[SPRITE_TEE_EYE_NORMAL + i]); |
176 | |
177 | int FeetGridPixelsWidth = (Info.m_Width / g_pData->m_aSprites[SPRITE_TEE_FOOT].m_pSet->m_Gridx); |
178 | int FeetGridPixelsHeight = (Info.m_Height / g_pData->m_aSprites[SPRITE_TEE_FOOT].m_pSet->m_Gridy); |
179 | int FeetWidth = g_pData->m_aSprites[SPRITE_TEE_FOOT].m_W * FeetGridPixelsWidth; |
180 | int FeetHeight = g_pData->m_aSprites[SPRITE_TEE_FOOT].m_H * FeetGridPixelsHeight; |
181 | |
182 | int FeetOffsetX = g_pData->m_aSprites[SPRITE_TEE_FOOT].m_X * FeetGridPixelsWidth; |
183 | int FeetOffsetY = g_pData->m_aSprites[SPRITE_TEE_FOOT].m_Y * FeetGridPixelsHeight; |
184 | |
185 | int FeetOutlineGridPixelsWidth = (Info.m_Width / g_pData->m_aSprites[SPRITE_TEE_FOOT_OUTLINE].m_pSet->m_Gridx); |
186 | int FeetOutlineGridPixelsHeight = (Info.m_Height / g_pData->m_aSprites[SPRITE_TEE_FOOT_OUTLINE].m_pSet->m_Gridy); |
187 | int FeetOutlineWidth = g_pData->m_aSprites[SPRITE_TEE_FOOT_OUTLINE].m_W * FeetOutlineGridPixelsWidth; |
188 | int FeetOutlineHeight = g_pData->m_aSprites[SPRITE_TEE_FOOT_OUTLINE].m_H * FeetOutlineGridPixelsHeight; |
189 | |
190 | int FeetOutlineOffsetX = g_pData->m_aSprites[SPRITE_TEE_FOOT_OUTLINE].m_X * FeetOutlineGridPixelsWidth; |
191 | int FeetOutlineOffsetY = g_pData->m_aSprites[SPRITE_TEE_FOOT_OUTLINE].m_Y * FeetOutlineGridPixelsHeight; |
192 | |
193 | int BodyOutlineGridPixelsWidth = (Info.m_Width / g_pData->m_aSprites[SPRITE_TEE_BODY_OUTLINE].m_pSet->m_Gridx); |
194 | int BodyOutlineGridPixelsHeight = (Info.m_Height / g_pData->m_aSprites[SPRITE_TEE_BODY_OUTLINE].m_pSet->m_Gridy); |
195 | int BodyOutlineWidth = g_pData->m_aSprites[SPRITE_TEE_BODY_OUTLINE].m_W * BodyOutlineGridPixelsWidth; |
196 | int BodyOutlineHeight = g_pData->m_aSprites[SPRITE_TEE_BODY_OUTLINE].m_H * BodyOutlineGridPixelsHeight; |
197 | |
198 | int BodyOutlineOffsetX = g_pData->m_aSprites[SPRITE_TEE_BODY_OUTLINE].m_X * BodyOutlineGridPixelsWidth; |
199 | int BodyOutlineOffsetY = g_pData->m_aSprites[SPRITE_TEE_BODY_OUTLINE].m_Y * BodyOutlineGridPixelsHeight; |
200 | |
201 | size_t 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 |
202 | size_t 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 |
203 | if(BodyWidth > Info.m_Width || BodyHeight > Info.m_Height) |
204 | return nullptr; |
205 | uint8_t *pData = Info.m_pData; |
206 | const int PixelStep = 4; |
207 | int Pitch = Info.m_Width * PixelStep; |
208 | |
209 | // dig out blood color |
210 | { |
211 | int64_t aColors[3] = {0}; |
212 | for(size_t y = 0; y < BodyHeight; y++) |
213 | { |
214 | for(size_t x = 0; x < BodyWidth; x++) |
215 | { |
216 | uint8_t AlphaValue = pData[y * Pitch + x * PixelStep + 3]; |
217 | if(AlphaValue > 128) |
218 | { |
219 | aColors[0] += pData[y * Pitch + x * PixelStep + 0]; |
220 | aColors[1] += pData[y * Pitch + x * PixelStep + 1]; |
221 | aColors[2] += pData[y * Pitch + x * PixelStep + 2]; |
222 | } |
223 | } |
224 | } |
225 | |
226 | Skin.m_BloodColor = ColorRGBA(normalize(v: vec3(aColors[0], aColors[1], aColors[2]))); |
227 | } |
228 | |
229 | CheckMetrics(Metrics&: Skin.m_Metrics.m_Body, pImg: pData, ImgWidth: Pitch, ImgX: 0, ImgY: 0, CheckWidth: BodyWidth, CheckHeight: BodyHeight); |
230 | |
231 | // body outline metrics |
232 | CheckMetrics(Metrics&: Skin.m_Metrics.m_Body, pImg: pData, ImgWidth: Pitch, ImgX: BodyOutlineOffsetX, ImgY: BodyOutlineOffsetY, CheckWidth: BodyOutlineWidth, CheckHeight: BodyOutlineHeight); |
233 | |
234 | // get feet size |
235 | CheckMetrics(Metrics&: Skin.m_Metrics.m_Feet, pImg: pData, ImgWidth: Pitch, ImgX: FeetOffsetX, ImgY: FeetOffsetY, CheckWidth: FeetWidth, CheckHeight: FeetHeight); |
236 | |
237 | // get feet outline size |
238 | CheckMetrics(Metrics&: Skin.m_Metrics.m_Feet, pImg: pData, ImgWidth: Pitch, ImgX: FeetOutlineOffsetX, ImgY: FeetOutlineOffsetY, CheckWidth: FeetOutlineWidth, CheckHeight: FeetOutlineHeight); |
239 | |
240 | // make the texture gray scale |
241 | for(size_t i = 0; i < Info.m_Width * Info.m_Height; i++) |
242 | { |
243 | int v = (pData[i * PixelStep] + pData[i * PixelStep + 1] + pData[i * PixelStep + 2]) / 3; |
244 | pData[i * PixelStep] = v; |
245 | pData[i * PixelStep + 1] = v; |
246 | pData[i * PixelStep + 2] = v; |
247 | } |
248 | |
249 | int aFreq[256] = {0}; |
250 | int OrgWeight = 0; |
251 | int NewWeight = 192; |
252 | |
253 | // find most common frequency |
254 | for(size_t y = 0; y < BodyHeight; y++) |
255 | for(size_t x = 0; x < BodyWidth; x++) |
256 | { |
257 | if(pData[y * Pitch + x * PixelStep + 3] > 128) |
258 | aFreq[pData[y * Pitch + x * PixelStep]]++; |
259 | } |
260 | |
261 | for(int i = 1; i < 256; i++) |
262 | { |
263 | if(aFreq[OrgWeight] < aFreq[i]) |
264 | OrgWeight = i; |
265 | } |
266 | |
267 | // reorder |
268 | int InvOrgWeight = 255 - OrgWeight; |
269 | int InvNewWeight = 255 - NewWeight; |
270 | for(size_t y = 0; y < BodyHeight; y++) |
271 | for(size_t x = 0; x < BodyWidth; x++) |
272 | { |
273 | int v = pData[y * Pitch + x * PixelStep]; |
274 | if(v <= OrgWeight && OrgWeight == 0) |
275 | v = 0; |
276 | else if(v <= OrgWeight) |
277 | v = (int)(((v / (float)OrgWeight) * NewWeight)); |
278 | else if(InvOrgWeight == 0) |
279 | v = NewWeight; |
280 | else |
281 | v = (int)(((v - OrgWeight) / (float)InvOrgWeight) * InvNewWeight + NewWeight); |
282 | pData[y * Pitch + x * PixelStep] = v; |
283 | pData[y * Pitch + x * PixelStep + 1] = v; |
284 | pData[y * Pitch + x * PixelStep + 2] = v; |
285 | } |
286 | |
287 | Skin.m_ColorableSkin.m_Body = Graphics()->LoadSpriteTexture(FromImageInfo: Info, pSprite: &g_pData->m_aSprites[SPRITE_TEE_BODY]); |
288 | Skin.m_ColorableSkin.m_BodyOutline = Graphics()->LoadSpriteTexture(FromImageInfo: Info, pSprite: &g_pData->m_aSprites[SPRITE_TEE_BODY_OUTLINE]); |
289 | Skin.m_ColorableSkin.m_Feet = Graphics()->LoadSpriteTexture(FromImageInfo: Info, pSprite: &g_pData->m_aSprites[SPRITE_TEE_FOOT]); |
290 | Skin.m_ColorableSkin.m_FeetOutline = Graphics()->LoadSpriteTexture(FromImageInfo: Info, pSprite: &g_pData->m_aSprites[SPRITE_TEE_FOOT_OUTLINE]); |
291 | Skin.m_ColorableSkin.m_Hands = Graphics()->LoadSpriteTexture(FromImageInfo: Info, pSprite: &g_pData->m_aSprites[SPRITE_TEE_HAND]); |
292 | Skin.m_ColorableSkin.m_HandsOutline = Graphics()->LoadSpriteTexture(FromImageInfo: Info, pSprite: &g_pData->m_aSprites[SPRITE_TEE_HAND_OUTLINE]); |
293 | |
294 | for(int i = 0; i < 6; ++i) |
295 | Skin.m_ColorableSkin.m_aEyes[i] = Graphics()->LoadSpriteTexture(FromImageInfo: Info, pSprite: &g_pData->m_aSprites[SPRITE_TEE_EYE_NORMAL + i]); |
296 | |
297 | Info.Free(); |
298 | |
299 | if(g_Config.m_Debug) |
300 | { |
301 | log_trace("skins" , "Loaded skin %s" , Skin.GetName()); |
302 | } |
303 | |
304 | auto &&pSkin = std::make_unique<CSkin>(args: std::move(Skin)); |
305 | const auto SkinInsertIt = m_Skins.insert(x: {pSkin->GetName(), std::move(pSkin)}); |
306 | |
307 | return SkinInsertIt.first->second.get(); |
308 | } |
309 | |
310 | void CSkins::OnInit() |
311 | { |
312 | m_aEventSkinPrefix[0] = '\0'; |
313 | |
314 | if(g_Config.m_Events) |
315 | { |
316 | if(time_season() == SEASON_XMAS) |
317 | { |
318 | str_copy(dst&: m_aEventSkinPrefix, src: "santa" ); |
319 | } |
320 | } |
321 | |
322 | // load skins; |
323 | Refresh(SkinLoadedFunc: [this](int SkinCounter) { |
324 | GameClient()->m_Menus.RenderLoading(pCaption: Localize(pStr: "Loading DDNet Client" ), pContent: Localize(pStr: "Loading skin files" ), IncreaseCounter: 0); |
325 | }); |
326 | } |
327 | |
328 | void CSkins::Refresh(TSkinLoadedCBFunc &&SkinLoadedFunc) |
329 | { |
330 | for(const auto &[_, pSkin] : m_Skins) |
331 | { |
332 | pSkin->m_OriginalSkin.Unload(pGraphics: Graphics()); |
333 | pSkin->m_ColorableSkin.Unload(pGraphics: Graphics()); |
334 | } |
335 | |
336 | m_Skins.clear(); |
337 | m_DownloadSkins.clear(); |
338 | m_DownloadingSkins = 0; |
339 | SSkinScanUser SkinScanUser; |
340 | SkinScanUser.m_pThis = this; |
341 | SkinScanUser.m_SkinLoadedFunc = SkinLoadedFunc; |
342 | Storage()->ListDirectory(Type: IStorage::TYPE_ALL, pPath: "skins" , pfnCallback: SkinScan, pUser: &SkinScanUser); |
343 | } |
344 | |
345 | int CSkins::Num() |
346 | { |
347 | return m_Skins.size(); |
348 | } |
349 | |
350 | const CSkin *CSkins::Find(const char *pName) |
351 | { |
352 | const auto *pSkin = FindOrNullptr(pName); |
353 | if(pSkin == nullptr) |
354 | { |
355 | pSkin = FindOrNullptr(pName: "default" ); |
356 | } |
357 | if(pSkin == nullptr) |
358 | { |
359 | pSkin = &m_PlaceholderSkin; |
360 | } |
361 | return pSkin; |
362 | } |
363 | |
364 | const CSkin *CSkins::FindOrNullptr(const char *pName, bool IgnorePrefix) |
365 | { |
366 | if(g_Config.m_ClVanillaSkinsOnly && !IsVanillaSkin(pName)) |
367 | { |
368 | return nullptr; |
369 | } |
370 | |
371 | const char *pSkinPrefix = m_aEventSkinPrefix[0] != '\0' ? m_aEventSkinPrefix : g_Config.m_ClSkinPrefix; |
372 | if(!IgnorePrefix && pSkinPrefix[0] != '\0') |
373 | { |
374 | char aNameWithPrefix[48]; // Larger than skin name length to allow IsValidName to check if it's too long |
375 | str_format(buffer: aNameWithPrefix, buffer_size: sizeof(aNameWithPrefix), format: "%s_%s" , pSkinPrefix, pName); |
376 | // If we find something, use it, otherwise fall back to normal skins. |
377 | const auto *pResult = FindImpl(pName: aNameWithPrefix); |
378 | if(pResult != nullptr) |
379 | { |
380 | return pResult; |
381 | } |
382 | } |
383 | |
384 | return FindImpl(pName); |
385 | } |
386 | |
387 | const CSkin *CSkins::FindImpl(const char *pName) |
388 | { |
389 | auto SkinIt = m_Skins.find(x: pName); |
390 | if(SkinIt != m_Skins.end()) |
391 | return SkinIt->second.get(); |
392 | |
393 | if(str_comp(a: pName, b: "default" ) == 0) |
394 | return nullptr; |
395 | |
396 | if(!g_Config.m_ClDownloadSkins) |
397 | return nullptr; |
398 | |
399 | if(!CSkin::IsValidName(pName)) |
400 | return nullptr; |
401 | |
402 | const auto SkinDownloadIt = m_DownloadSkins.find(x: pName); |
403 | if(SkinDownloadIt != m_DownloadSkins.end()) |
404 | { |
405 | if(SkinDownloadIt->second->m_pTask && SkinDownloadIt->second->m_pTask->State() == EHttpState::DONE && SkinDownloadIt->second->m_pTask->m_Info.m_pData) |
406 | { |
407 | char aPath[IO_MAX_PATH_LENGTH]; |
408 | str_format(buffer: aPath, buffer_size: sizeof(aPath), format: "downloadedskins/%s.png" , SkinDownloadIt->second->GetName()); |
409 | Storage()->RenameFile(pOldFilename: SkinDownloadIt->second->m_aPath, pNewFilename: aPath, Type: IStorage::TYPE_SAVE); |
410 | const auto *pSkin = LoadSkin(pName: SkinDownloadIt->second->GetName(), Info&: SkinDownloadIt->second->m_pTask->m_Info); |
411 | SkinDownloadIt->second->m_pTask = nullptr; |
412 | --m_DownloadingSkins; |
413 | return pSkin; |
414 | } |
415 | if(SkinDownloadIt->second->m_pTask && (SkinDownloadIt->second->m_pTask->State() == EHttpState::ERROR || SkinDownloadIt->second->m_pTask->State() == EHttpState::ABORTED)) |
416 | { |
417 | SkinDownloadIt->second->m_pTask = nullptr; |
418 | --m_DownloadingSkins; |
419 | } |
420 | return nullptr; |
421 | } |
422 | |
423 | CDownloadSkin Skin{pName}; |
424 | |
425 | char aEscapedName[256]; |
426 | EscapeUrl(pBuf: aEscapedName, Size: sizeof(aEscapedName), pStr: pName); |
427 | char aUrl[IO_MAX_PATH_LENGTH]; |
428 | str_format(buffer: aUrl, buffer_size: sizeof(aUrl), format: "%s%s.png" , g_Config.m_ClDownloadCommunitySkins != 0 ? g_Config.m_ClSkinCommunityDownloadUrl : g_Config.m_ClSkinDownloadUrl, aEscapedName); |
429 | |
430 | char aBuf[IO_MAX_PATH_LENGTH]; |
431 | str_format(buffer: Skin.m_aPath, buffer_size: sizeof(Skin.m_aPath), format: "downloadedskins/%s" , IStorage::FormatTmpPath(aBuf, BufSize: sizeof(aBuf), pPath: pName)); |
432 | |
433 | Skin.m_pTask = std::make_shared<CGetPngFile>(args: this, args&: aUrl, args: Storage(), args&: Skin.m_aPath); |
434 | Http()->Run(pRequest: Skin.m_pTask); |
435 | |
436 | auto &&pDownloadSkin = std::make_unique<CDownloadSkin>(args: std::move(Skin)); |
437 | m_DownloadSkins.insert(x: {pDownloadSkin->GetName(), std::move(pDownloadSkin)}); |
438 | ++m_DownloadingSkins; |
439 | |
440 | return nullptr; |
441 | } |
442 | |