1#include <base/system.h>
2
3#include <engine/shared/config.h>
4#include <engine/storage.h>
5#include <engine/textrender.h>
6
7#include <game/client/gameclient.h>
8#include <game/client/ui_listbox.h>
9#include <game/localization.h>
10
11#include "menus.h"
12
13#include <chrono>
14
15using namespace FontIcons;
16using namespace std::chrono_literals;
17
18typedef std::function<void()> TMenuAssetScanLoadedFunc;
19
20struct SMenuAssetScanUser
21{
22 void *m_pUser;
23 TMenuAssetScanLoadedFunc m_LoadedFunc;
24};
25
26// IDs of the tabs in the Assets menu
27enum
28{
29 ASSETS_TAB_ENTITIES = 0,
30 ASSETS_TAB_GAME = 1,
31 ASSETS_TAB_EMOTICONS = 2,
32 ASSETS_TAB_PARTICLES = 3,
33 ASSETS_TAB_HUD = 4,
34 ASSETS_TAB_EXTRAS = 5,
35 NUMBER_OF_ASSETS_TABS = 6,
36};
37
38void CMenus::LoadEntities(SCustomEntities *pEntitiesItem, void *pUser)
39{
40 auto *pRealUser = (SMenuAssetScanUser *)pUser;
41 auto *pThis = (CMenus *)pRealUser->m_pUser;
42
43 char aPath[IO_MAX_PATH_LENGTH];
44 if(str_comp(a: pEntitiesItem->m_aName, b: "default") == 0)
45 {
46 for(int i = 0; i < MAP_IMAGE_MOD_TYPE_COUNT; ++i)
47 {
48 str_format(buffer: aPath, buffer_size: sizeof(aPath), format: "editor/entities_clear/%s.png", gs_apModEntitiesNames[i]);
49 pEntitiesItem->m_aImages[i].m_Texture = pThis->Graphics()->LoadTexture(pFilename: aPath, StorageType: IStorage::TYPE_ALL);
50 if(!pEntitiesItem->m_RenderTexture.IsValid() || pEntitiesItem->m_RenderTexture.IsNullTexture())
51 pEntitiesItem->m_RenderTexture = pEntitiesItem->m_aImages[i].m_Texture;
52 }
53 }
54 else
55 {
56 for(int i = 0; i < MAP_IMAGE_MOD_TYPE_COUNT; ++i)
57 {
58 str_format(buffer: aPath, buffer_size: sizeof(aPath), format: "assets/entities/%s/%s.png", pEntitiesItem->m_aName, gs_apModEntitiesNames[i]);
59 pEntitiesItem->m_aImages[i].m_Texture = pThis->Graphics()->LoadTexture(pFilename: aPath, StorageType: IStorage::TYPE_ALL);
60 if(pEntitiesItem->m_aImages[i].m_Texture.IsNullTexture())
61 {
62 str_format(buffer: aPath, buffer_size: sizeof(aPath), format: "assets/entities/%s.png", pEntitiesItem->m_aName);
63 pEntitiesItem->m_aImages[i].m_Texture = pThis->Graphics()->LoadTexture(pFilename: aPath, StorageType: IStorage::TYPE_ALL);
64 }
65 if(!pEntitiesItem->m_RenderTexture.IsValid() || pEntitiesItem->m_RenderTexture.IsNullTexture())
66 pEntitiesItem->m_RenderTexture = pEntitiesItem->m_aImages[i].m_Texture;
67 }
68 }
69}
70
71int CMenus::EntitiesScan(const char *pName, int IsDir, int DirType, void *pUser)
72{
73 auto *pRealUser = (SMenuAssetScanUser *)pUser;
74 auto *pThis = (CMenus *)pRealUser->m_pUser;
75 if(IsDir)
76 {
77 if(pName[0] == '.')
78 return 0;
79
80 // default is reserved
81 if(str_comp(a: pName, b: "default") == 0)
82 return 0;
83
84 SCustomEntities EntitiesItem;
85 str_copy(dst&: EntitiesItem.m_aName, src: pName);
86 CMenus::LoadEntities(pEntitiesItem: &EntitiesItem, pUser);
87 pThis->m_vEntitiesList.push_back(x: EntitiesItem);
88 }
89 else
90 {
91 if(str_endswith(str: pName, suffix: ".png"))
92 {
93 char aName[IO_MAX_PATH_LENGTH];
94 str_truncate(dst: aName, dst_size: sizeof(aName), src: pName, truncation_len: str_length(str: pName) - 4);
95 // default is reserved
96 if(str_comp(a: aName, b: "default") == 0)
97 return 0;
98
99 SCustomEntities EntitiesItem;
100 str_copy(dst&: EntitiesItem.m_aName, src: aName);
101 CMenus::LoadEntities(pEntitiesItem: &EntitiesItem, pUser);
102 pThis->m_vEntitiesList.push_back(x: EntitiesItem);
103 }
104 }
105
106 pRealUser->m_LoadedFunc();
107
108 return 0;
109}
110
111template<typename TName>
112static void LoadAsset(TName *pAssetItem, const char *pAssetName, IGraphics *pGraphics)
113{
114 char aPath[IO_MAX_PATH_LENGTH];
115 if(str_comp(pAssetItem->m_aName, "default") == 0)
116 {
117 str_format(buffer: aPath, buffer_size: sizeof(aPath), format: "%s.png", pAssetName);
118 pAssetItem->m_RenderTexture = pGraphics->LoadTexture(pFilename: aPath, StorageType: IStorage::TYPE_ALL);
119 }
120 else
121 {
122 str_format(aPath, sizeof(aPath), "assets/%s/%s.png", pAssetName, pAssetItem->m_aName);
123 pAssetItem->m_RenderTexture = pGraphics->LoadTexture(pFilename: aPath, StorageType: IStorage::TYPE_ALL);
124 if(pAssetItem->m_RenderTexture.IsNullTexture())
125 {
126 str_format(aPath, sizeof(aPath), "assets/%s/%s/%s.png", pAssetName, pAssetItem->m_aName, pAssetName);
127 pAssetItem->m_RenderTexture = pGraphics->LoadTexture(pFilename: aPath, StorageType: IStorage::TYPE_ALL);
128 }
129 }
130}
131
132template<typename TName>
133static int AssetScan(const char *pName, int IsDir, int DirType, std::vector<TName> &vAssetList, const char *pAssetName, IGraphics *pGraphics, void *pUser)
134{
135 auto *pRealUser = (SMenuAssetScanUser *)pUser;
136 if(IsDir)
137 {
138 if(pName[0] == '.')
139 return 0;
140
141 // default is reserved
142 if(str_comp(a: pName, b: "default") == 0)
143 return 0;
144
145 TName AssetItem;
146 str_copy(AssetItem.m_aName, pName);
147 LoadAsset(&AssetItem, pAssetName, pGraphics);
148 vAssetList.push_back(AssetItem);
149 }
150 else
151 {
152 if(str_endswith(str: pName, suffix: ".png"))
153 {
154 char aName[IO_MAX_PATH_LENGTH];
155 str_truncate(dst: aName, dst_size: sizeof(aName), src: pName, truncation_len: str_length(str: pName) - 4);
156 // default is reserved
157 if(str_comp(a: aName, b: "default") == 0)
158 return 0;
159
160 TName AssetItem;
161 str_copy(AssetItem.m_aName, aName);
162 LoadAsset(&AssetItem, pAssetName, pGraphics);
163 vAssetList.push_back(AssetItem);
164 }
165 }
166
167 pRealUser->m_LoadedFunc();
168
169 return 0;
170}
171
172int CMenus::GameScan(const char *pName, int IsDir, int DirType, void *pUser)
173{
174 auto *pRealUser = (SMenuAssetScanUser *)pUser;
175 auto *pThis = (CMenus *)pRealUser->m_pUser;
176 IGraphics *pGraphics = pThis->Graphics();
177 return AssetScan(pName, IsDir, DirType, vAssetList&: pThis->m_vGameList, pAssetName: "game", pGraphics, pUser);
178}
179
180int CMenus::EmoticonsScan(const char *pName, int IsDir, int DirType, void *pUser)
181{
182 auto *pRealUser = (SMenuAssetScanUser *)pUser;
183 auto *pThis = (CMenus *)pRealUser->m_pUser;
184 IGraphics *pGraphics = pThis->Graphics();
185 return AssetScan(pName, IsDir, DirType, vAssetList&: pThis->m_vEmoticonList, pAssetName: "emoticons", pGraphics, pUser);
186}
187
188int CMenus::ParticlesScan(const char *pName, int IsDir, int DirType, void *pUser)
189{
190 auto *pRealUser = (SMenuAssetScanUser *)pUser;
191 auto *pThis = (CMenus *)pRealUser->m_pUser;
192 IGraphics *pGraphics = pThis->Graphics();
193 return AssetScan(pName, IsDir, DirType, vAssetList&: pThis->m_vParticlesList, pAssetName: "particles", pGraphics, pUser);
194}
195
196int CMenus::HudScan(const char *pName, int IsDir, int DirType, void *pUser)
197{
198 auto *pRealUser = (SMenuAssetScanUser *)pUser;
199 auto *pThis = (CMenus *)pRealUser->m_pUser;
200 IGraphics *pGraphics = pThis->Graphics();
201 return AssetScan(pName, IsDir, DirType, vAssetList&: pThis->m_vHudList, pAssetName: "hud", pGraphics, pUser);
202}
203
204int CMenus::ExtrasScan(const char *pName, int IsDir, int DirType, void *pUser)
205{
206 auto *pRealUser = (SMenuAssetScanUser *)pUser;
207 auto *pThis = (CMenus *)pRealUser->m_pUser;
208 IGraphics *pGraphics = pThis->Graphics();
209 return AssetScan(pName, IsDir, DirType, vAssetList&: pThis->m_vExtrasList, pAssetName: "extras", pGraphics, pUser);
210}
211
212static std::vector<const CMenus::SCustomEntities *> gs_vpSearchEntitiesList;
213static std::vector<const CMenus::SCustomGame *> gs_vpSearchGamesList;
214static std::vector<const CMenus::SCustomEmoticon *> gs_vpSearchEmoticonsList;
215static std::vector<const CMenus::SCustomParticle *> gs_vpSearchParticlesList;
216static std::vector<const CMenus::SCustomHud *> gs_vpSearchHudList;
217static std::vector<const CMenus::SCustomExtras *> gs_vpSearchExtrasList;
218
219static bool gs_aInitCustomList[NUMBER_OF_ASSETS_TABS] = {
220 true,
221};
222
223static size_t gs_aCustomListSize[NUMBER_OF_ASSETS_TABS] = {
224 0,
225};
226
227static CLineInputBuffered<64> s_aFilterInputs[NUMBER_OF_ASSETS_TABS];
228
229static int s_CurCustomTab = ASSETS_TAB_ENTITIES;
230
231static const CMenus::SCustomItem *GetCustomItem(int CurTab, size_t Index)
232{
233 if(CurTab == ASSETS_TAB_ENTITIES)
234 return gs_vpSearchEntitiesList[Index];
235 else if(CurTab == ASSETS_TAB_GAME)
236 return gs_vpSearchGamesList[Index];
237 else if(CurTab == ASSETS_TAB_EMOTICONS)
238 return gs_vpSearchEmoticonsList[Index];
239 else if(CurTab == ASSETS_TAB_PARTICLES)
240 return gs_vpSearchParticlesList[Index];
241 else if(CurTab == ASSETS_TAB_HUD)
242 return gs_vpSearchHudList[Index];
243 else if(CurTab == ASSETS_TAB_EXTRAS)
244 return gs_vpSearchExtrasList[Index];
245
246 return NULL;
247}
248
249template<typename TName>
250void ClearAssetList(std::vector<TName> &vList, IGraphics *pGraphics)
251{
252 for(size_t i = 0; i < vList.size(); ++i)
253 {
254 if(vList[i].m_RenderTexture.IsValid())
255 pGraphics->UnloadTexture(pIndex: &(vList[i].m_RenderTexture));
256 vList[i].m_RenderTexture = IGraphics::CTextureHandle();
257 }
258 vList.clear();
259}
260
261void CMenus::ClearCustomItems(int CurTab)
262{
263 if(CurTab == ASSETS_TAB_ENTITIES)
264 {
265 for(auto &Entity : m_vEntitiesList)
266 {
267 for(auto &Image : Entity.m_aImages)
268 {
269 if(Image.m_Texture.IsValid())
270 Graphics()->UnloadTexture(pIndex: &Image.m_Texture);
271 Image.m_Texture = IGraphics::CTextureHandle();
272 }
273 }
274 m_vEntitiesList.clear();
275
276 // reload current entities
277 m_pClient->m_MapImages.ChangeEntitiesPath(pPath: g_Config.m_ClAssetsEntities);
278 }
279 else if(CurTab == ASSETS_TAB_GAME)
280 {
281 ClearAssetList(vList&: m_vGameList, pGraphics: Graphics());
282
283 // reload current game skin
284 GameClient()->LoadGameSkin(pPath: g_Config.m_ClAssetGame);
285 }
286 else if(CurTab == ASSETS_TAB_EMOTICONS)
287 {
288 ClearAssetList(vList&: m_vEmoticonList, pGraphics: Graphics());
289
290 // reload current emoticons skin
291 GameClient()->LoadEmoticonsSkin(pPath: g_Config.m_ClAssetEmoticons);
292 }
293 else if(CurTab == ASSETS_TAB_PARTICLES)
294 {
295 ClearAssetList(vList&: m_vParticlesList, pGraphics: Graphics());
296
297 // reload current particles skin
298 GameClient()->LoadParticlesSkin(pPath: g_Config.m_ClAssetParticles);
299 }
300 else if(CurTab == ASSETS_TAB_HUD)
301 {
302 ClearAssetList(vList&: m_vHudList, pGraphics: Graphics());
303
304 // reload current hud skin
305 GameClient()->LoadHudSkin(pPath: g_Config.m_ClAssetHud);
306 }
307 else if(CurTab == ASSETS_TAB_EXTRAS)
308 {
309 ClearAssetList(vList&: m_vExtrasList, pGraphics: Graphics());
310
311 // reload current DDNet particles skin
312 GameClient()->LoadExtrasSkin(pPath: g_Config.m_ClAssetExtras);
313 }
314 gs_aInitCustomList[CurTab] = true;
315}
316
317template<typename TName, typename TCaller>
318void InitAssetList(std::vector<TName> &vAssetList, const char *pAssetPath, const char *pAssetName, FS_LISTDIR_CALLBACK pfnCallback, IGraphics *pGraphics, IStorage *pStorage, TCaller Caller)
319{
320 if(vAssetList.empty())
321 {
322 TName AssetItem;
323 str_copy(AssetItem.m_aName, "default");
324 LoadAsset(&AssetItem, pAssetName, pGraphics);
325 vAssetList.push_back(AssetItem);
326
327 // load assets
328 pStorage->ListDirectory(Type: IStorage::TYPE_ALL, pPath: pAssetPath, pfnCallback, pUser: Caller);
329 std::sort(vAssetList.begin(), vAssetList.end());
330 }
331 if(vAssetList.size() != gs_aCustomListSize[s_CurCustomTab])
332 gs_aInitCustomList[s_CurCustomTab] = true;
333}
334
335template<typename TName>
336int InitSearchList(std::vector<const TName *> &vpSearchList, std::vector<TName> &vAssetList)
337{
338 vpSearchList.clear();
339 int ListSize = vAssetList.size();
340 for(int i = 0; i < ListSize; ++i)
341 {
342 const TName *pAsset = &vAssetList[i];
343
344 // filter quick search
345 if(!s_aFilterInputs[s_CurCustomTab].IsEmpty() && !str_utf8_find_nocase(pAsset->m_aName, s_aFilterInputs[s_CurCustomTab].GetString()))
346 continue;
347
348 vpSearchList.push_back(pAsset);
349 }
350 return vAssetList.size();
351}
352
353void CMenus::RenderSettingsCustom(CUIRect MainView)
354{
355 CUIRect TabBar, CustomList, QuickSearch, QuickSearchClearButton, DirectoryButton, ReloadButton;
356
357 MainView.HSplitTop(Cut: 20.0f, pTop: &TabBar, pBottom: &MainView);
358 const float TabWidth = TabBar.w / NUMBER_OF_ASSETS_TABS;
359 static CButtonContainer s_aPageTabs[NUMBER_OF_ASSETS_TABS] = {};
360 const char *apTabNames[NUMBER_OF_ASSETS_TABS] = {
361 Localize(pStr: "Entities"),
362 Localize(pStr: "Game"),
363 Localize(pStr: "Emoticons"),
364 Localize(pStr: "Particles"),
365 Localize(pStr: "HUD"),
366 Localize(pStr: "Extras")};
367
368 for(int Tab = ASSETS_TAB_ENTITIES; Tab < NUMBER_OF_ASSETS_TABS; ++Tab)
369 {
370 CUIRect Button;
371 TabBar.VSplitLeft(Cut: TabWidth, pLeft: &Button, pRight: &TabBar);
372 const int Corners = Tab == ASSETS_TAB_ENTITIES ? IGraphics::CORNER_L : Tab == NUMBER_OF_ASSETS_TABS - 1 ? IGraphics::CORNER_R : IGraphics::CORNER_NONE;
373 if(DoButton_MenuTab(pButtonContainer: &s_aPageTabs[Tab], pText: apTabNames[Tab], Checked: s_CurCustomTab == Tab, pRect: &Button, Corners, pAnimator: nullptr, pDefaultColor: nullptr, pActiveColor: nullptr, pHoverColor: nullptr, EdgeRounding: 4.0f))
374 {
375 s_CurCustomTab = Tab;
376 }
377 }
378
379 auto LoadStartTime = time_get_nanoseconds();
380 SMenuAssetScanUser User;
381 User.m_pUser = this;
382 User.m_LoadedFunc = [&]() {
383 if(time_get_nanoseconds() - LoadStartTime > 500ms)
384 RenderLoading(pCaption: Localize(pStr: "Loading assets"), pContent: "", IncreaseCounter: 0, RenderLoadingBar: false);
385 };
386 if(s_CurCustomTab == ASSETS_TAB_ENTITIES)
387 {
388 if(m_vEntitiesList.empty())
389 {
390 SCustomEntities EntitiesItem;
391 str_copy(dst&: EntitiesItem.m_aName, src: "default");
392 LoadEntities(pEntitiesItem: &EntitiesItem, pUser: &User);
393 m_vEntitiesList.push_back(x: EntitiesItem);
394
395 // load entities
396 Storage()->ListDirectory(Type: IStorage::TYPE_ALL, pPath: "assets/entities", pfnCallback: EntitiesScan, pUser: &User);
397 std::sort(first: m_vEntitiesList.begin(), last: m_vEntitiesList.end());
398 }
399 if(m_vEntitiesList.size() != gs_aCustomListSize[s_CurCustomTab])
400 gs_aInitCustomList[s_CurCustomTab] = true;
401 }
402 else if(s_CurCustomTab == ASSETS_TAB_GAME)
403 {
404 InitAssetList(vAssetList&: m_vGameList, pAssetPath: "assets/game", pAssetName: "game", pfnCallback: GameScan, pGraphics: Graphics(), pStorage: Storage(), Caller: &User);
405 }
406 else if(s_CurCustomTab == ASSETS_TAB_EMOTICONS)
407 {
408 InitAssetList(vAssetList&: m_vEmoticonList, pAssetPath: "assets/emoticons", pAssetName: "emoticons", pfnCallback: EmoticonsScan, pGraphics: Graphics(), pStorage: Storage(), Caller: &User);
409 }
410 else if(s_CurCustomTab == ASSETS_TAB_PARTICLES)
411 {
412 InitAssetList(vAssetList&: m_vParticlesList, pAssetPath: "assets/particles", pAssetName: "particles", pfnCallback: ParticlesScan, pGraphics: Graphics(), pStorage: Storage(), Caller: &User);
413 }
414 else if(s_CurCustomTab == ASSETS_TAB_HUD)
415 {
416 InitAssetList(vAssetList&: m_vHudList, pAssetPath: "assets/hud", pAssetName: "hud", pfnCallback: HudScan, pGraphics: Graphics(), pStorage: Storage(), Caller: &User);
417 }
418 else if(s_CurCustomTab == ASSETS_TAB_EXTRAS)
419 {
420 InitAssetList(vAssetList&: m_vExtrasList, pAssetPath: "assets/extras", pAssetName: "extras", pfnCallback: ExtrasScan, pGraphics: Graphics(), pStorage: Storage(), Caller: &User);
421 }
422
423 MainView.HSplitTop(Cut: 10.0f, pTop: 0, pBottom: &MainView);
424
425 // skin selector
426 MainView.HSplitTop(Cut: MainView.h - 10.0f - ms_ButtonHeight, pTop: &CustomList, pBottom: &MainView);
427 if(gs_aInitCustomList[s_CurCustomTab])
428 {
429 int ListSize = 0;
430 if(s_CurCustomTab == ASSETS_TAB_ENTITIES)
431 {
432 gs_vpSearchEntitiesList.clear();
433 ListSize = m_vEntitiesList.size();
434 for(int i = 0; i < ListSize; ++i)
435 {
436 const SCustomEntities *pEntity = &m_vEntitiesList[i];
437
438 // filter quick search
439 if(!s_aFilterInputs[s_CurCustomTab].IsEmpty() && !str_utf8_find_nocase(haystack: pEntity->m_aName, needle: s_aFilterInputs[s_CurCustomTab].GetString()))
440 continue;
441
442 gs_vpSearchEntitiesList.push_back(x: pEntity);
443 }
444 }
445 else if(s_CurCustomTab == ASSETS_TAB_GAME)
446 {
447 ListSize = InitSearchList(vpSearchList&: gs_vpSearchGamesList, vAssetList&: m_vGameList);
448 }
449 else if(s_CurCustomTab == ASSETS_TAB_EMOTICONS)
450 {
451 ListSize = InitSearchList(vpSearchList&: gs_vpSearchEmoticonsList, vAssetList&: m_vEmoticonList);
452 }
453 else if(s_CurCustomTab == ASSETS_TAB_PARTICLES)
454 {
455 ListSize = InitSearchList(vpSearchList&: gs_vpSearchParticlesList, vAssetList&: m_vParticlesList);
456 }
457 else if(s_CurCustomTab == ASSETS_TAB_HUD)
458 {
459 ListSize = InitSearchList(vpSearchList&: gs_vpSearchHudList, vAssetList&: m_vHudList);
460 }
461 else if(s_CurCustomTab == ASSETS_TAB_EXTRAS)
462 {
463 ListSize = InitSearchList(vpSearchList&: gs_vpSearchExtrasList, vAssetList&: m_vExtrasList);
464 }
465 gs_aInitCustomList[s_CurCustomTab] = false;
466 gs_aCustomListSize[s_CurCustomTab] = ListSize;
467 }
468
469 int OldSelected = -1;
470 float Margin = 10;
471 float TextureWidth = 150;
472 float TextureHeight = 150;
473
474 size_t SearchListSize = 0;
475
476 if(s_CurCustomTab == ASSETS_TAB_ENTITIES)
477 {
478 SearchListSize = gs_vpSearchEntitiesList.size();
479 }
480 else if(s_CurCustomTab == ASSETS_TAB_GAME)
481 {
482 SearchListSize = gs_vpSearchGamesList.size();
483 TextureHeight = 75;
484 }
485 else if(s_CurCustomTab == ASSETS_TAB_EMOTICONS)
486 {
487 SearchListSize = gs_vpSearchEmoticonsList.size();
488 }
489 else if(s_CurCustomTab == ASSETS_TAB_PARTICLES)
490 {
491 SearchListSize = gs_vpSearchParticlesList.size();
492 }
493 else if(s_CurCustomTab == ASSETS_TAB_HUD)
494 {
495 SearchListSize = gs_vpSearchHudList.size();
496 }
497 else if(s_CurCustomTab == ASSETS_TAB_EXTRAS)
498 {
499 SearchListSize = gs_vpSearchExtrasList.size();
500 }
501
502 static CListBox s_ListBox;
503 s_ListBox.DoStart(RowHeight: TextureHeight + 15.0f + 10.0f + Margin, NumItems: SearchListSize, ItemsPerRow: CustomList.w / (Margin + TextureWidth), RowsPerScroll: 1, SelectedIndex: OldSelected, pRect: &CustomList, Background: false);
504 for(size_t i = 0; i < SearchListSize; ++i)
505 {
506 const SCustomItem *pItem = GetCustomItem(CurTab: s_CurCustomTab, Index: i);
507 if(pItem == NULL)
508 continue;
509
510 if(s_CurCustomTab == ASSETS_TAB_ENTITIES)
511 {
512 if(str_comp(a: pItem->m_aName, b: g_Config.m_ClAssetsEntities) == 0)
513 OldSelected = i;
514 }
515 else if(s_CurCustomTab == ASSETS_TAB_GAME)
516 {
517 if(str_comp(a: pItem->m_aName, b: g_Config.m_ClAssetGame) == 0)
518 OldSelected = i;
519 }
520 else if(s_CurCustomTab == ASSETS_TAB_EMOTICONS)
521 {
522 if(str_comp(a: pItem->m_aName, b: g_Config.m_ClAssetEmoticons) == 0)
523 OldSelected = i;
524 }
525 else if(s_CurCustomTab == ASSETS_TAB_PARTICLES)
526 {
527 if(str_comp(a: pItem->m_aName, b: g_Config.m_ClAssetParticles) == 0)
528 OldSelected = i;
529 }
530 else if(s_CurCustomTab == ASSETS_TAB_HUD)
531 {
532 if(str_comp(a: pItem->m_aName, b: g_Config.m_ClAssetHud) == 0)
533 OldSelected = i;
534 }
535 else if(s_CurCustomTab == ASSETS_TAB_EXTRAS)
536 {
537 if(str_comp(a: pItem->m_aName, b: g_Config.m_ClAssetExtras) == 0)
538 OldSelected = i;
539 }
540
541 const CListboxItem Item = s_ListBox.DoNextItem(pId: pItem, Selected: OldSelected >= 0 && (size_t)OldSelected == i);
542 CUIRect ItemRect = Item.m_Rect;
543 ItemRect.Margin(Cut: Margin / 2, pOtherRect: &ItemRect);
544 if(!Item.m_Visible)
545 continue;
546
547 CUIRect TextureRect;
548 ItemRect.HSplitTop(Cut: 15, pTop: &ItemRect, pBottom: &TextureRect);
549 TextureRect.HSplitTop(Cut: 10, NULL, pBottom: &TextureRect);
550 Ui()->DoLabel(pRect: &ItemRect, pText: pItem->m_aName, Size: ItemRect.h - 2, Align: TEXTALIGN_MC);
551 if(pItem->m_RenderTexture.IsValid())
552 {
553 Graphics()->WrapClamp();
554 Graphics()->TextureSet(Texture: pItem->m_RenderTexture);
555 Graphics()->QuadsBegin();
556 Graphics()->SetColor(r: 1, g: 1, b: 1, a: 1);
557 IGraphics::CQuadItem QuadItem(TextureRect.x + (TextureRect.w - TextureWidth) / 2, TextureRect.y + (TextureRect.h - TextureHeight) / 2, TextureWidth, TextureHeight);
558 Graphics()->QuadsDrawTL(pArray: &QuadItem, Num: 1);
559 Graphics()->QuadsEnd();
560 Graphics()->WrapNormal();
561 }
562 }
563
564 const int NewSelected = s_ListBox.DoEnd();
565 if(OldSelected != NewSelected)
566 {
567 if(GetCustomItem(CurTab: s_CurCustomTab, Index: NewSelected)->m_aName[0] != '\0')
568 {
569 if(s_CurCustomTab == ASSETS_TAB_ENTITIES)
570 {
571 str_copy(dst&: g_Config.m_ClAssetsEntities, src: GetCustomItem(CurTab: s_CurCustomTab, Index: NewSelected)->m_aName);
572 m_pClient->m_MapImages.ChangeEntitiesPath(pPath: GetCustomItem(CurTab: s_CurCustomTab, Index: NewSelected)->m_aName);
573 }
574 else if(s_CurCustomTab == ASSETS_TAB_GAME)
575 {
576 str_copy(dst&: g_Config.m_ClAssetGame, src: GetCustomItem(CurTab: s_CurCustomTab, Index: NewSelected)->m_aName);
577 GameClient()->LoadGameSkin(pPath: g_Config.m_ClAssetGame);
578 }
579 else if(s_CurCustomTab == ASSETS_TAB_EMOTICONS)
580 {
581 str_copy(dst&: g_Config.m_ClAssetEmoticons, src: GetCustomItem(CurTab: s_CurCustomTab, Index: NewSelected)->m_aName);
582 GameClient()->LoadEmoticonsSkin(pPath: g_Config.m_ClAssetEmoticons);
583 }
584 else if(s_CurCustomTab == ASSETS_TAB_PARTICLES)
585 {
586 str_copy(dst&: g_Config.m_ClAssetParticles, src: GetCustomItem(CurTab: s_CurCustomTab, Index: NewSelected)->m_aName);
587 GameClient()->LoadParticlesSkin(pPath: g_Config.m_ClAssetParticles);
588 }
589 else if(s_CurCustomTab == ASSETS_TAB_HUD)
590 {
591 str_copy(dst&: g_Config.m_ClAssetHud, src: GetCustomItem(CurTab: s_CurCustomTab, Index: NewSelected)->m_aName);
592 GameClient()->LoadHudSkin(pPath: g_Config.m_ClAssetHud);
593 }
594 else if(s_CurCustomTab == ASSETS_TAB_EXTRAS)
595 {
596 str_copy(dst&: g_Config.m_ClAssetExtras, src: GetCustomItem(CurTab: s_CurCustomTab, Index: NewSelected)->m_aName);
597 GameClient()->LoadExtrasSkin(pPath: g_Config.m_ClAssetExtras);
598 }
599 }
600 }
601
602 // render quick search
603 {
604 MainView.HSplitBottom(Cut: ms_ButtonHeight, pTop: &MainView, pBottom: &QuickSearch);
605 QuickSearch.VSplitLeft(Cut: 240.0f, pLeft: &QuickSearch, pRight: &DirectoryButton);
606 QuickSearch.HSplitTop(Cut: 5.0f, pTop: 0, pBottom: &QuickSearch);
607 TextRender()->SetFontPreset(EFontPreset::ICON_FONT);
608 TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE);
609
610 Ui()->DoLabel(pRect: &QuickSearch, pText: FONT_ICON_MAGNIFYING_GLASS, Size: 14.0f, Align: TEXTALIGN_ML);
611 float SearchWidth = TextRender()->TextWidth(Size: 14.0f, pText: FONT_ICON_MAGNIFYING_GLASS, StrLength: -1, LineWidth: -1.0f);
612 TextRender()->SetRenderFlags(0);
613 TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT);
614 QuickSearch.VSplitLeft(Cut: SearchWidth, pLeft: 0, pRight: &QuickSearch);
615 QuickSearch.VSplitLeft(Cut: 5.0f, pLeft: 0, pRight: &QuickSearch);
616 QuickSearch.VSplitLeft(Cut: QuickSearch.w - 10.0f, pLeft: &QuickSearch, pRight: &QuickSearchClearButton);
617 if(Input()->KeyPress(Key: KEY_F) && Input()->ModifierIsPressed())
618 {
619 Ui()->SetActiveItem(&s_aFilterInputs[s_CurCustomTab]);
620 s_aFilterInputs[s_CurCustomTab].SelectAll();
621 }
622 s_aFilterInputs[s_CurCustomTab].SetEmptyText(Localize(pStr: "Search"));
623 if(Ui()->DoClearableEditBox(pLineInput: &s_aFilterInputs[s_CurCustomTab], pRect: &QuickSearch, FontSize: 14.0f))
624 gs_aInitCustomList[s_CurCustomTab] = true;
625 }
626
627 DirectoryButton.HSplitTop(Cut: 5.0f, pTop: 0, pBottom: &DirectoryButton);
628 DirectoryButton.VSplitRight(Cut: 175.0f, pLeft: 0, pRight: &DirectoryButton);
629 DirectoryButton.VSplitRight(Cut: 25.0f, pLeft: &DirectoryButton, pRight: &ReloadButton);
630 DirectoryButton.VSplitRight(Cut: 10.0f, pLeft: &DirectoryButton, pRight: 0);
631 static CButtonContainer s_AssetsDirId;
632 if(DoButton_Menu(pButtonContainer: &s_AssetsDirId, pText: Localize(pStr: "Assets directory"), Checked: 0, pRect: &DirectoryButton))
633 {
634 char aBuf[IO_MAX_PATH_LENGTH];
635 char aBufFull[IO_MAX_PATH_LENGTH + 7];
636 if(s_CurCustomTab == ASSETS_TAB_ENTITIES)
637 str_copy(dst&: aBufFull, src: "assets/entities");
638 else if(s_CurCustomTab == ASSETS_TAB_GAME)
639 str_copy(dst&: aBufFull, src: "assets/game");
640 else if(s_CurCustomTab == ASSETS_TAB_EMOTICONS)
641 str_copy(dst&: aBufFull, src: "assets/emoticons");
642 else if(s_CurCustomTab == ASSETS_TAB_PARTICLES)
643 str_copy(dst&: aBufFull, src: "assets/particles");
644 else if(s_CurCustomTab == ASSETS_TAB_HUD)
645 str_copy(dst&: aBufFull, src: "assets/hud");
646 else if(s_CurCustomTab == ASSETS_TAB_EXTRAS)
647 str_copy(dst&: aBufFull, src: "assets/extras");
648 Storage()->GetCompletePath(Type: IStorage::TYPE_SAVE, pDir: aBufFull, pBuffer: aBuf, BufferSize: sizeof(aBuf));
649 Storage()->CreateFolder(pFoldername: "assets", Type: IStorage::TYPE_SAVE);
650 Storage()->CreateFolder(pFoldername: aBufFull, Type: IStorage::TYPE_SAVE);
651 Client()->ViewFile(pFilename: aBuf);
652 }
653 GameClient()->m_Tooltips.DoToolTip(pId: &s_AssetsDirId, pNearRect: &DirectoryButton, pText: Localize(pStr: "Open the directory to add custom assets"));
654
655 TextRender()->SetFontPreset(EFontPreset::ICON_FONT);
656 TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE);
657 static CButtonContainer s_AssetsReloadBtnId;
658 if(DoButton_Menu(pButtonContainer: &s_AssetsReloadBtnId, pText: FONT_ICON_ARROW_ROTATE_RIGHT, Checked: 0, pRect: &ReloadButton) || Input()->KeyPress(Key: KEY_F5) || (Input()->KeyPress(Key: KEY_R) && Input()->ModifierIsPressed()))
659 {
660 ClearCustomItems(CurTab: s_CurCustomTab);
661 }
662 TextRender()->SetRenderFlags(0);
663 TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT);
664}
665
666void CMenus::ConchainAssetsEntities(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
667{
668 CMenus *pThis = (CMenus *)pUserData;
669 if(pResult->NumArguments() == 1)
670 {
671 const char *pArg = pResult->GetString(Index: 0);
672 if(str_comp(a: pArg, b: g_Config.m_ClAssetsEntities) != 0)
673 {
674 pThis->m_pClient->m_MapImages.ChangeEntitiesPath(pPath: pArg);
675 }
676 }
677
678 pfnCallback(pResult, pCallbackUserData);
679}
680
681void CMenus::ConchainAssetGame(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
682{
683 CMenus *pThis = (CMenus *)pUserData;
684 if(pResult->NumArguments() == 1)
685 {
686 const char *pArg = pResult->GetString(Index: 0);
687 if(str_comp(a: pArg, b: g_Config.m_ClAssetGame) != 0)
688 {
689 pThis->GameClient()->LoadGameSkin(pPath: pArg);
690 }
691 }
692
693 pfnCallback(pResult, pCallbackUserData);
694}
695
696void CMenus::ConchainAssetParticles(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
697{
698 CMenus *pThis = (CMenus *)pUserData;
699 if(pResult->NumArguments() == 1)
700 {
701 const char *pArg = pResult->GetString(Index: 0);
702 if(str_comp(a: pArg, b: g_Config.m_ClAssetParticles) != 0)
703 {
704 pThis->GameClient()->LoadParticlesSkin(pPath: pArg);
705 }
706 }
707
708 pfnCallback(pResult, pCallbackUserData);
709}
710
711void CMenus::ConchainAssetEmoticons(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
712{
713 CMenus *pThis = (CMenus *)pUserData;
714 if(pResult->NumArguments() == 1)
715 {
716 const char *pArg = pResult->GetString(Index: 0);
717 if(str_comp(a: pArg, b: g_Config.m_ClAssetEmoticons) != 0)
718 {
719 pThis->GameClient()->LoadEmoticonsSkin(pPath: pArg);
720 }
721 }
722
723 pfnCallback(pResult, pCallbackUserData);
724}
725
726void CMenus::ConchainAssetHud(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
727{
728 CMenus *pThis = (CMenus *)pUserData;
729 if(pResult->NumArguments() == 1)
730 {
731 const char *pArg = pResult->GetString(Index: 0);
732 if(str_comp(a: pArg, b: g_Config.m_ClAssetHud) != 0)
733 {
734 pThis->GameClient()->LoadHudSkin(pPath: pArg);
735 }
736 }
737
738 pfnCallback(pResult, pCallbackUserData);
739}
740
741void CMenus::ConchainAssetExtras(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
742{
743 CMenus *pThis = (CMenus *)pUserData;
744 if(pResult->NumArguments() == 1)
745 {
746 const char *pArg = pResult->GetString(Index: 0);
747 if(str_comp(a: pArg, b: g_Config.m_ClAssetExtras) != 0)
748 {
749 pThis->GameClient()->LoadExtrasSkin(pPath: pArg);
750 }
751 }
752
753 pfnCallback(pResult, pCallbackUserData);
754}
755