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