1#include "menus.h"
2
3#include <base/system.h>
4
5#include <engine/font_icons.h>
6#include <engine/shared/config.h>
7#include <engine/storage.h>
8#include <engine/textrender.h>
9
10#include <game/client/gameclient.h>
11#include <game/client/ui_listbox.h>
12#include <game/localization.h>
13
14#include <chrono>
15
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 dbg_assert_failed("Invalid CurTab: %d", CurTab);
246}
247
248template<typename TName>
249static void ClearAssetList(std::vector<TName> &vList, IGraphics *pGraphics)
250{
251 for(TName &Asset : vList)
252 {
253 pGraphics->UnloadTexture(pIndex: &Asset.m_RenderTexture);
254 }
255 vList.clear();
256}
257
258void CMenus::ClearCustomItems(int CurTab)
259{
260 if(CurTab == ASSETS_TAB_ENTITIES)
261 {
262 for(auto &Entity : m_vEntitiesList)
263 {
264 for(auto &Image : Entity.m_aImages)
265 {
266 Graphics()->UnloadTexture(pIndex: &Image.m_Texture);
267 }
268 }
269 m_vEntitiesList.clear();
270
271 // reload current entities
272 GameClient()->m_MapImages.ChangeEntitiesPath(pPath: g_Config.m_ClAssetsEntities);
273 }
274 else if(CurTab == ASSETS_TAB_GAME)
275 {
276 ClearAssetList(vList&: m_vGameList, pGraphics: Graphics());
277
278 // reload current game skin
279 GameClient()->LoadGameSkin(pPath: g_Config.m_ClAssetGame);
280 }
281 else if(CurTab == ASSETS_TAB_EMOTICONS)
282 {
283 ClearAssetList(vList&: m_vEmoticonList, pGraphics: Graphics());
284
285 // reload current emoticons skin
286 GameClient()->LoadEmoticonsSkin(pPath: g_Config.m_ClAssetEmoticons);
287 }
288 else if(CurTab == ASSETS_TAB_PARTICLES)
289 {
290 ClearAssetList(vList&: m_vParticlesList, pGraphics: Graphics());
291
292 // reload current particles skin
293 GameClient()->LoadParticlesSkin(pPath: g_Config.m_ClAssetParticles);
294 }
295 else if(CurTab == ASSETS_TAB_HUD)
296 {
297 ClearAssetList(vList&: m_vHudList, pGraphics: Graphics());
298
299 // reload current hud skin
300 GameClient()->LoadHudSkin(pPath: g_Config.m_ClAssetHud);
301 }
302 else if(CurTab == ASSETS_TAB_EXTRAS)
303 {
304 ClearAssetList(vList&: m_vExtrasList, pGraphics: Graphics());
305
306 // reload current DDNet particles skin
307 GameClient()->LoadExtrasSkin(pPath: g_Config.m_ClAssetExtras);
308 }
309 else
310 {
311 dbg_assert_failed("Invalid CurTab: %d", CurTab);
312 }
313 gs_aInitCustomList[CurTab] = true;
314}
315
316template<typename TName, typename TCaller>
317static void InitAssetList(std::vector<TName> &vAssetList, const char *pAssetPath, const char *pAssetName, FS_LISTDIR_CALLBACK pfnCallback, IGraphics *pGraphics, IStorage *pStorage, TCaller Caller)
318{
319 if(vAssetList.empty())
320 {
321 TName AssetItem;
322 str_copy(AssetItem.m_aName, "default");
323 LoadAsset(&AssetItem, pAssetName, pGraphics);
324 vAssetList.push_back(AssetItem);
325
326 // load assets
327 pStorage->ListDirectory(Type: IStorage::TYPE_ALL, pPath: pAssetPath, pfnCallback, pUser: Caller);
328 std::sort(vAssetList.begin(), vAssetList.end());
329 }
330 if(vAssetList.size() != gs_aCustomListSize[s_CurCustomTab])
331 gs_aInitCustomList[s_CurCustomTab] = true;
332}
333
334template<typename TName>
335static int InitSearchList(std::vector<const TName *> &vpSearchList, std::vector<TName> &vAssetList)
336{
337 vpSearchList.clear();
338 int ListSize = vAssetList.size();
339 for(int i = 0; i < ListSize; ++i)
340 {
341 const TName *pAsset = &vAssetList[i];
342
343 // filter quick search
344 if(!s_aFilterInputs[s_CurCustomTab].IsEmpty() && !str_utf8_find_nocase(pAsset->m_aName, s_aFilterInputs[s_CurCustomTab].GetString()))
345 continue;
346
347 vpSearchList.push_back(pAsset);
348 }
349 return vAssetList.size();
350}
351
352void CMenus::RenderSettingsCustom(CUIRect MainView)
353{
354 CUIRect TabBar, CustomList, QuickSearch, DirectoryButton, ReloadButton;
355
356 MainView.HSplitTop(Cut: 20.0f, pTop: &TabBar, pBottom: &MainView);
357 const float TabWidth = TabBar.w / (float)NUMBER_OF_ASSETS_TABS;
358 static CButtonContainer s_aPageTabs[NUMBER_OF_ASSETS_TABS] = {};
359 const char *apTabNames[NUMBER_OF_ASSETS_TABS] = {
360 Localize(pStr: "Entities"),
361 Localize(pStr: "Game"),
362 Localize(pStr: "Emoticons"),
363 Localize(pStr: "Particles"),
364 Localize(pStr: "HUD"),
365 Localize(pStr: "Extras")};
366
367 for(int Tab = ASSETS_TAB_ENTITIES; Tab < NUMBER_OF_ASSETS_TABS; ++Tab)
368 {
369 CUIRect Button;
370 TabBar.VSplitLeft(Cut: TabWidth, pLeft: &Button, pRight: &TabBar);
371 const int Corners = Tab == ASSETS_TAB_ENTITIES ? IGraphics::CORNER_L : (Tab == NUMBER_OF_ASSETS_TABS - 1 ? IGraphics::CORNER_R : IGraphics::CORNER_NONE);
372 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))
373 {
374 s_CurCustomTab = Tab;
375 }
376 }
377
378 auto LoadStartTime = time_get_nanoseconds();
379 SMenuAssetScanUser User;
380 User.m_pUser = this;
381 User.m_LoadedFunc = [&]() {
382 if(time_get_nanoseconds() - LoadStartTime > 500ms)
383 RenderLoading(pCaption: Localize(pStr: "Loading assets"), pContent: "", IncreaseCounter: 0);
384 };
385 if(s_CurCustomTab == ASSETS_TAB_ENTITIES)
386 {
387 if(m_vEntitiesList.empty())
388 {
389 SCustomEntities EntitiesItem;
390 str_copy(dst&: EntitiesItem.m_aName, src: "default");
391 LoadEntities(pEntitiesItem: &EntitiesItem, pUser: &User);
392 m_vEntitiesList.push_back(x: EntitiesItem);
393
394 // load entities
395 Storage()->ListDirectory(Type: IStorage::TYPE_ALL, pPath: "assets/entities", pfnCallback: EntitiesScan, pUser: &User);
396 std::sort(first: m_vEntitiesList.begin(), last: m_vEntitiesList.end());
397 }
398 if(m_vEntitiesList.size() != gs_aCustomListSize[s_CurCustomTab])
399 gs_aInitCustomList[s_CurCustomTab] = true;
400 }
401 else if(s_CurCustomTab == ASSETS_TAB_GAME)
402 {
403 InitAssetList(vAssetList&: m_vGameList, pAssetPath: "assets/game", pAssetName: "game", pfnCallback: GameScan, pGraphics: Graphics(), pStorage: Storage(), Caller: &User);
404 }
405 else if(s_CurCustomTab == ASSETS_TAB_EMOTICONS)
406 {
407 InitAssetList(vAssetList&: m_vEmoticonList, pAssetPath: "assets/emoticons", pAssetName: "emoticons", pfnCallback: EmoticonsScan, pGraphics: Graphics(), pStorage: Storage(), Caller: &User);
408 }
409 else if(s_CurCustomTab == ASSETS_TAB_PARTICLES)
410 {
411 InitAssetList(vAssetList&: m_vParticlesList, pAssetPath: "assets/particles", pAssetName: "particles", pfnCallback: ParticlesScan, pGraphics: Graphics(), pStorage: Storage(), Caller: &User);
412 }
413 else if(s_CurCustomTab == ASSETS_TAB_HUD)
414 {
415 InitAssetList(vAssetList&: m_vHudList, pAssetPath: "assets/hud", pAssetName: "hud", pfnCallback: HudScan, pGraphics: Graphics(), pStorage: Storage(), Caller: &User);
416 }
417 else if(s_CurCustomTab == ASSETS_TAB_EXTRAS)
418 {
419 InitAssetList(vAssetList&: m_vExtrasList, pAssetPath: "assets/extras", pAssetName: "extras", pfnCallback: ExtrasScan, pGraphics: Graphics(), pStorage: Storage(), Caller: &User);
420 }
421 else
422 {
423 dbg_assert_failed("Invalid s_CurCustomTab: %d", s_CurCustomTab);
424 }
425
426 MainView.HSplitTop(Cut: 10.0f, pTop: nullptr, pBottom: &MainView);
427
428 // skin selector
429 MainView.HSplitTop(Cut: MainView.h - 10.0f - ms_ButtonHeight, pTop: &CustomList, pBottom: &MainView);
430 if(gs_aInitCustomList[s_CurCustomTab])
431 {
432 int ListSize = 0;
433 if(s_CurCustomTab == ASSETS_TAB_ENTITIES)
434 {
435 gs_vpSearchEntitiesList.clear();
436 ListSize = m_vEntitiesList.size();
437 for(int i = 0; i < ListSize; ++i)
438 {
439 const SCustomEntities *pEntity = &m_vEntitiesList[i];
440
441 // filter quick search
442 if(!s_aFilterInputs[s_CurCustomTab].IsEmpty() && !str_utf8_find_nocase(haystack: pEntity->m_aName, needle: s_aFilterInputs[s_CurCustomTab].GetString()))
443 continue;
444
445 gs_vpSearchEntitiesList.push_back(x: pEntity);
446 }
447 }
448 else if(s_CurCustomTab == ASSETS_TAB_GAME)
449 {
450 ListSize = InitSearchList(vpSearchList&: gs_vpSearchGamesList, vAssetList&: m_vGameList);
451 }
452 else if(s_CurCustomTab == ASSETS_TAB_EMOTICONS)
453 {
454 ListSize = InitSearchList(vpSearchList&: gs_vpSearchEmoticonsList, vAssetList&: m_vEmoticonList);
455 }
456 else if(s_CurCustomTab == ASSETS_TAB_PARTICLES)
457 {
458 ListSize = InitSearchList(vpSearchList&: gs_vpSearchParticlesList, vAssetList&: m_vParticlesList);
459 }
460 else if(s_CurCustomTab == ASSETS_TAB_HUD)
461 {
462 ListSize = InitSearchList(vpSearchList&: gs_vpSearchHudList, vAssetList&: m_vHudList);
463 }
464 else if(s_CurCustomTab == ASSETS_TAB_EXTRAS)
465 {
466 ListSize = InitSearchList(vpSearchList&: gs_vpSearchExtrasList, vAssetList&: m_vExtrasList);
467 }
468 gs_aInitCustomList[s_CurCustomTab] = false;
469 gs_aCustomListSize[s_CurCustomTab] = ListSize;
470 }
471
472 int OldSelected = -1;
473 float Margin = 10;
474 float TextureWidth = 150;
475 float TextureHeight = 150;
476
477 size_t SearchListSize = 0;
478
479 if(s_CurCustomTab == ASSETS_TAB_ENTITIES)
480 {
481 SearchListSize = gs_vpSearchEntitiesList.size();
482 }
483 else if(s_CurCustomTab == ASSETS_TAB_GAME)
484 {
485 SearchListSize = gs_vpSearchGamesList.size();
486 TextureHeight = 75;
487 }
488 else if(s_CurCustomTab == ASSETS_TAB_EMOTICONS)
489 {
490 SearchListSize = gs_vpSearchEmoticonsList.size();
491 }
492 else if(s_CurCustomTab == ASSETS_TAB_PARTICLES)
493 {
494 SearchListSize = gs_vpSearchParticlesList.size();
495 }
496 else if(s_CurCustomTab == ASSETS_TAB_HUD)
497 {
498 SearchListSize = gs_vpSearchHudList.size();
499 }
500 else if(s_CurCustomTab == ASSETS_TAB_EXTRAS)
501 {
502 SearchListSize = gs_vpSearchExtrasList.size();
503 }
504
505 static CListBox s_ListBox;
506 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);
507 for(size_t i = 0; i < SearchListSize; ++i)
508 {
509 const SCustomItem *pItem = GetCustomItem(CurTab: s_CurCustomTab, Index: i);
510 if(pItem == nullptr)
511 continue;
512
513 if(s_CurCustomTab == ASSETS_TAB_ENTITIES)
514 {
515 if(str_comp(a: pItem->m_aName, b: g_Config.m_ClAssetsEntities) == 0)
516 OldSelected = i;
517 }
518 else if(s_CurCustomTab == ASSETS_TAB_GAME)
519 {
520 if(str_comp(a: pItem->m_aName, b: g_Config.m_ClAssetGame) == 0)
521 OldSelected = i;
522 }
523 else if(s_CurCustomTab == ASSETS_TAB_EMOTICONS)
524 {
525 if(str_comp(a: pItem->m_aName, b: g_Config.m_ClAssetEmoticons) == 0)
526 OldSelected = i;
527 }
528 else if(s_CurCustomTab == ASSETS_TAB_PARTICLES)
529 {
530 if(str_comp(a: pItem->m_aName, b: g_Config.m_ClAssetParticles) == 0)
531 OldSelected = i;
532 }
533 else if(s_CurCustomTab == ASSETS_TAB_HUD)
534 {
535 if(str_comp(a: pItem->m_aName, b: g_Config.m_ClAssetHud) == 0)
536 OldSelected = i;
537 }
538 else if(s_CurCustomTab == ASSETS_TAB_EXTRAS)
539 {
540 if(str_comp(a: pItem->m_aName, b: g_Config.m_ClAssetExtras) == 0)
541 OldSelected = i;
542 }
543
544 const CListboxItem Item = s_ListBox.DoNextItem(pId: pItem, Selected: OldSelected >= 0 && (size_t)OldSelected == i);
545 CUIRect ItemRect = Item.m_Rect;
546 ItemRect.Margin(Cut: Margin / 2, pOtherRect: &ItemRect);
547 if(!Item.m_Visible)
548 continue;
549
550 CUIRect TextureRect;
551 ItemRect.HSplitTop(Cut: 15, pTop: &ItemRect, pBottom: &TextureRect);
552 TextureRect.HSplitTop(Cut: 10, pTop: nullptr, pBottom: &TextureRect);
553 Ui()->DoLabel(pRect: &ItemRect, pText: pItem->m_aName, Size: ItemRect.h - 2, Align: TEXTALIGN_MC);
554 if(pItem->m_RenderTexture.IsValid())
555 {
556 Graphics()->WrapClamp();
557 Graphics()->TextureSet(Texture: pItem->m_RenderTexture);
558 Graphics()->QuadsBegin();
559 Graphics()->SetColor(r: 1, g: 1, b: 1, a: 1);
560 IGraphics::CQuadItem QuadItem(TextureRect.x + (TextureRect.w - TextureWidth) / 2, TextureRect.y + (TextureRect.h - TextureHeight) / 2, TextureWidth, TextureHeight);
561 Graphics()->QuadsDrawTL(pArray: &QuadItem, Num: 1);
562 Graphics()->QuadsEnd();
563 Graphics()->WrapNormal();
564 }
565 }
566
567 const int NewSelected = s_ListBox.DoEnd();
568 if(OldSelected != NewSelected)
569 {
570 if(GetCustomItem(CurTab: s_CurCustomTab, Index: NewSelected)->m_aName[0] != '\0')
571 {
572 if(s_CurCustomTab == ASSETS_TAB_ENTITIES)
573 {
574 str_copy(dst&: g_Config.m_ClAssetsEntities, src: GetCustomItem(CurTab: s_CurCustomTab, Index: NewSelected)->m_aName);
575 GameClient()->m_MapImages.ChangeEntitiesPath(pPath: GetCustomItem(CurTab: s_CurCustomTab, Index: NewSelected)->m_aName);
576 }
577 else if(s_CurCustomTab == ASSETS_TAB_GAME)
578 {
579 str_copy(dst&: g_Config.m_ClAssetGame, src: GetCustomItem(CurTab: s_CurCustomTab, Index: NewSelected)->m_aName);
580 GameClient()->LoadGameSkin(pPath: g_Config.m_ClAssetGame);
581 }
582 else if(s_CurCustomTab == ASSETS_TAB_EMOTICONS)
583 {
584 str_copy(dst&: g_Config.m_ClAssetEmoticons, src: GetCustomItem(CurTab: s_CurCustomTab, Index: NewSelected)->m_aName);
585 GameClient()->LoadEmoticonsSkin(pPath: g_Config.m_ClAssetEmoticons);
586 }
587 else if(s_CurCustomTab == ASSETS_TAB_PARTICLES)
588 {
589 str_copy(dst&: g_Config.m_ClAssetParticles, src: GetCustomItem(CurTab: s_CurCustomTab, Index: NewSelected)->m_aName);
590 GameClient()->LoadParticlesSkin(pPath: g_Config.m_ClAssetParticles);
591 }
592 else if(s_CurCustomTab == ASSETS_TAB_HUD)
593 {
594 str_copy(dst&: g_Config.m_ClAssetHud, src: GetCustomItem(CurTab: s_CurCustomTab, Index: NewSelected)->m_aName);
595 GameClient()->LoadHudSkin(pPath: g_Config.m_ClAssetHud);
596 }
597 else if(s_CurCustomTab == ASSETS_TAB_EXTRAS)
598 {
599 str_copy(dst&: g_Config.m_ClAssetExtras, src: GetCustomItem(CurTab: s_CurCustomTab, Index: NewSelected)->m_aName);
600 GameClient()->LoadExtrasSkin(pPath: g_Config.m_ClAssetExtras);
601 }
602 }
603 }
604
605 // Quick search
606 MainView.HSplitBottom(Cut: ms_ButtonHeight, pTop: &MainView, pBottom: &QuickSearch);
607 QuickSearch.VSplitLeft(Cut: 220.0f, pLeft: &QuickSearch, pRight: &DirectoryButton);
608 QuickSearch.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &QuickSearch);
609 if(Ui()->DoEditBox_Search(pLineInput: &s_aFilterInputs[s_CurCustomTab], pRect: &QuickSearch, FontSize: 14.0f, HotkeyEnabled: !Ui()->IsPopupOpen() && !GameClient()->m_GameConsole.IsActive()))
610 {
611 gs_aInitCustomList[s_CurCustomTab] = true;
612 }
613
614 DirectoryButton.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &DirectoryButton);
615 DirectoryButton.VSplitRight(Cut: 175.0f, pLeft: nullptr, pRight: &DirectoryButton);
616 DirectoryButton.VSplitRight(Cut: 25.0f, pLeft: &DirectoryButton, pRight: &ReloadButton);
617 DirectoryButton.VSplitRight(Cut: 10.0f, pLeft: &DirectoryButton, pRight: nullptr);
618 static CButtonContainer s_AssetsDirId;
619 if(DoButton_Menu(pButtonContainer: &s_AssetsDirId, pText: Localize(pStr: "Assets directory"), Checked: 0, pRect: &DirectoryButton))
620 {
621 char aBuf[IO_MAX_PATH_LENGTH];
622 char aBufFull[IO_MAX_PATH_LENGTH + 7];
623 if(s_CurCustomTab == ASSETS_TAB_ENTITIES)
624 str_copy(dst&: aBufFull, src: "assets/entities");
625 else if(s_CurCustomTab == ASSETS_TAB_GAME)
626 str_copy(dst&: aBufFull, src: "assets/game");
627 else if(s_CurCustomTab == ASSETS_TAB_EMOTICONS)
628 str_copy(dst&: aBufFull, src: "assets/emoticons");
629 else if(s_CurCustomTab == ASSETS_TAB_PARTICLES)
630 str_copy(dst&: aBufFull, src: "assets/particles");
631 else if(s_CurCustomTab == ASSETS_TAB_HUD)
632 str_copy(dst&: aBufFull, src: "assets/hud");
633 else if(s_CurCustomTab == ASSETS_TAB_EXTRAS)
634 str_copy(dst&: aBufFull, src: "assets/extras");
635 Storage()->GetCompletePath(Type: IStorage::TYPE_SAVE, pDir: aBufFull, pBuffer: aBuf, BufferSize: sizeof(aBuf));
636 Storage()->CreateFolder(pFoldername: "assets", Type: IStorage::TYPE_SAVE);
637 Storage()->CreateFolder(pFoldername: aBufFull, Type: IStorage::TYPE_SAVE);
638 Client()->ViewFile(pFilename: aBuf);
639 }
640 GameClient()->m_Tooltips.DoToolTip(pId: &s_AssetsDirId, pNearRect: &DirectoryButton, pText: Localize(pStr: "Open the directory to add custom assets"));
641
642 TextRender()->SetFontPreset(EFontPreset::ICON_FONT);
643 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_ALIGNMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE);
644 static CButtonContainer s_AssetsReloadBtnId;
645 if(DoButton_Menu(pButtonContainer: &s_AssetsReloadBtnId, pText: FontIcon::ARROW_ROTATE_RIGHT, Checked: 0, pRect: &ReloadButton) || Input()->KeyPress(Key: KEY_F5) || (Input()->KeyPress(Key: KEY_R) && Input()->ModifierIsPressed()))
646 {
647 ClearCustomItems(CurTab: s_CurCustomTab);
648 }
649 TextRender()->SetRenderFlags(0);
650 TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT);
651}
652
653void CMenus::ConchainAssetsEntities(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
654{
655 CMenus *pThis = (CMenus *)pUserData;
656 if(pResult->NumArguments() == 1)
657 {
658 const char *pArg = pResult->GetString(Index: 0);
659 if(str_comp(a: pArg, b: g_Config.m_ClAssetsEntities) != 0)
660 {
661 pThis->GameClient()->m_MapImages.ChangeEntitiesPath(pPath: pArg);
662 }
663 }
664
665 pfnCallback(pResult, pCallbackUserData);
666}
667
668void CMenus::ConchainAssetGame(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
669{
670 CMenus *pThis = (CMenus *)pUserData;
671 if(pResult->NumArguments() == 1)
672 {
673 const char *pArg = pResult->GetString(Index: 0);
674 if(str_comp(a: pArg, b: g_Config.m_ClAssetGame) != 0)
675 {
676 pThis->GameClient()->LoadGameSkin(pPath: pArg);
677 }
678 }
679
680 pfnCallback(pResult, pCallbackUserData);
681}
682
683void CMenus::ConchainAssetParticles(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
684{
685 CMenus *pThis = (CMenus *)pUserData;
686 if(pResult->NumArguments() == 1)
687 {
688 const char *pArg = pResult->GetString(Index: 0);
689 if(str_comp(a: pArg, b: g_Config.m_ClAssetParticles) != 0)
690 {
691 pThis->GameClient()->LoadParticlesSkin(pPath: pArg);
692 }
693 }
694
695 pfnCallback(pResult, pCallbackUserData);
696}
697
698void CMenus::ConchainAssetEmoticons(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
699{
700 CMenus *pThis = (CMenus *)pUserData;
701 if(pResult->NumArguments() == 1)
702 {
703 const char *pArg = pResult->GetString(Index: 0);
704 if(str_comp(a: pArg, b: g_Config.m_ClAssetEmoticons) != 0)
705 {
706 pThis->GameClient()->LoadEmoticonsSkin(pPath: pArg);
707 }
708 }
709
710 pfnCallback(pResult, pCallbackUserData);
711}
712
713void CMenus::ConchainAssetHud(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
714{
715 CMenus *pThis = (CMenus *)pUserData;
716 if(pResult->NumArguments() == 1)
717 {
718 const char *pArg = pResult->GetString(Index: 0);
719 if(str_comp(a: pArg, b: g_Config.m_ClAssetHud) != 0)
720 {
721 pThis->GameClient()->LoadHudSkin(pPath: pArg);
722 }
723 }
724
725 pfnCallback(pResult, pCallbackUserData);
726}
727
728void CMenus::ConchainAssetExtras(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
729{
730 CMenus *pThis = (CMenus *)pUserData;
731 if(pResult->NumArguments() == 1)
732 {
733 const char *pArg = pResult->GetString(Index: 0);
734 if(str_comp(a: pArg, b: g_Config.m_ClAssetExtras) != 0)
735 {
736 pThis->GameClient()->LoadExtrasSkin(pPath: pArg);
737 }
738 }
739
740 pfnCallback(pResult, pCallbackUserData);
741}
742