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#include "countryflags.h"
4#include "menus.h"
5#include "skins.h"
6
7#include <base/log.h>
8#include <base/math.h>
9#include <base/system.h>
10
11#include <engine/graphics.h>
12#include <engine/shared/config.h>
13#include <engine/shared/localization.h>
14#include <engine/shared/protocol7.h>
15#include <engine/storage.h>
16#include <engine/textrender.h>
17#include <engine/updater.h>
18
19#include <generated/protocol.h>
20
21#include <game/client/animstate.h>
22#include <game/client/components/chat.h>
23#include <game/client/components/menu_background.h>
24#include <game/client/components/sounds.h>
25#include <game/client/gameclient.h>
26#include <game/client/skin.h>
27#include <game/client/ui.h>
28#include <game/client/ui_listbox.h>
29#include <game/client/ui_scrollregion.h>
30#include <game/localization.h>
31
32#include <array>
33#include <chrono>
34#include <memory>
35#include <numeric>
36#include <string>
37#include <vector>
38
39using namespace FontIcons;
40using namespace std::chrono_literals;
41
42void CMenus::RenderSettingsGeneral(CUIRect MainView)
43{
44 char aBuf[128 + IO_MAX_PATH_LENGTH];
45 CUIRect Label, Button, Left, Right, Game, ClientSettings;
46 MainView.HSplitTop(Cut: 150.0f, pTop: &Game, pBottom: &ClientSettings);
47
48 // game
49 {
50 // headline
51 Game.HSplitTop(Cut: 30.0f, pTop: &Label, pBottom: &Game);
52 Ui()->DoLabel(pRect: &Label, pText: Localize(pStr: "Game"), Size: 20.0f, Align: TEXTALIGN_ML);
53 Game.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &Game);
54 Game.VSplitMid(pLeft: &Left, pRight: nullptr, Spacing: 20.0f);
55
56 // dynamic camera
57 Left.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Left);
58 const bool IsDyncam = g_Config.m_ClDyncam || g_Config.m_ClMouseFollowfactor > 0;
59 if(DoButton_CheckBox(pId: &g_Config.m_ClDyncam, pText: Localize(pStr: "Dynamic Camera"), Checked: IsDyncam, pRect: &Button))
60 {
61 if(IsDyncam)
62 {
63 g_Config.m_ClDyncam = 0;
64 g_Config.m_ClMouseFollowfactor = 0;
65 }
66 else
67 {
68 g_Config.m_ClDyncam = 1;
69 }
70 }
71
72 // smooth dynamic camera
73 Left.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &Left);
74 Left.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Left);
75 if(g_Config.m_ClDyncam)
76 {
77 if(DoButton_CheckBox(pId: &g_Config.m_ClDyncamSmoothness, pText: Localize(pStr: "Smooth Dynamic Camera"), Checked: g_Config.m_ClDyncamSmoothness, pRect: &Button))
78 {
79 if(g_Config.m_ClDyncamSmoothness)
80 {
81 g_Config.m_ClDyncamSmoothness = 0;
82 }
83 else
84 {
85 g_Config.m_ClDyncamSmoothness = 50;
86 g_Config.m_ClDyncamStabilizing = 50;
87 }
88 }
89 }
90
91 // weapon pickup
92 Left.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &Left);
93 Left.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Left);
94 if(DoButton_CheckBox(pId: &g_Config.m_ClAutoswitchWeapons, pText: Localize(pStr: "Switch weapon on pickup"), Checked: g_Config.m_ClAutoswitchWeapons, pRect: &Button))
95 g_Config.m_ClAutoswitchWeapons ^= 1;
96
97 // weapon out of ammo autoswitch
98 Left.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &Left);
99 Left.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Left);
100 if(DoButton_CheckBox(pId: &g_Config.m_ClAutoswitchWeaponsOutOfAmmo, pText: Localize(pStr: "Switch weapon when out of ammo"), Checked: g_Config.m_ClAutoswitchWeaponsOutOfAmmo, pRect: &Button))
101 g_Config.m_ClAutoswitchWeaponsOutOfAmmo ^= 1;
102 }
103
104 // client
105 {
106 // headline
107 ClientSettings.HSplitTop(Cut: 30.0f, pTop: &Label, pBottom: &ClientSettings);
108 Ui()->DoLabel(pRect: &Label, pText: Localize(pStr: "Client"), Size: 20.0f, Align: TEXTALIGN_ML);
109 ClientSettings.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &ClientSettings);
110 ClientSettings.VSplitMid(pLeft: &Left, pRight: &Right, Spacing: 20.0f);
111
112 // skip main menu
113 Left.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Left);
114 if(DoButton_CheckBox(pId: &g_Config.m_ClSkipStartMenu, pText: Localize(pStr: "Skip the main menu"), Checked: g_Config.m_ClSkipStartMenu, pRect: &Button))
115 g_Config.m_ClSkipStartMenu ^= 1;
116
117 Left.HSplitTop(Cut: 10.0f, pTop: nullptr, pBottom: &Left);
118 Left.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Left);
119 str_copy(dst&: aBuf, src: " ");
120 str_append(dst&: aBuf, src: Localize(pStr: "Hz", pContext: "Hertz"));
121 Ui()->DoScrollbarOption(pId: &g_Config.m_ClRefreshRate, pOption: &g_Config.m_ClRefreshRate, pRect: &Button, pStr: Localize(pStr: "Refresh Rate"), Min: 10, Max: 1000, pScale: &CUi::ms_LinearScrollbarScale, Flags: CUi::SCROLLBAR_OPTION_INFINITE | CUi::SCROLLBAR_OPTION_NOCLAMPVALUE | CUi::SCROLLBAR_OPTION_DELAYUPDATE, pSuffix: aBuf);
122 Left.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &Left);
123 Left.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Left);
124 static int s_LowerRefreshRate;
125 if(DoButton_CheckBox(pId: &s_LowerRefreshRate, pText: Localize(pStr: "Save power by lowering refresh rate (higher input latency)"), Checked: g_Config.m_ClRefreshRate <= 480 && g_Config.m_ClRefreshRate != 0, pRect: &Button))
126 g_Config.m_ClRefreshRate = g_Config.m_ClRefreshRate > 480 || g_Config.m_ClRefreshRate == 0 ? 480 : 0;
127
128 CUIRect SettingsButton;
129 Left.HSplitBottom(Cut: 20.0f, pTop: &Left, pBottom: &SettingsButton);
130 Left.HSplitBottom(Cut: 5.0f, pTop: &Left, pBottom: nullptr);
131 static CButtonContainer s_SettingsButtonId;
132 if(DoButton_Menu(pButtonContainer: &s_SettingsButtonId, pText: Localize(pStr: "Settings file"), Checked: 0, pRect: &SettingsButton))
133 {
134 Storage()->GetCompletePath(Type: IStorage::TYPE_SAVE, CONFIG_FILE, pBuffer: aBuf, BufferSize: sizeof(aBuf));
135 Client()->ViewFile(pFilename: aBuf);
136 }
137 GameClient()->m_Tooltips.DoToolTip(pId: &s_SettingsButtonId, pNearRect: &SettingsButton, pText: Localize(pStr: "Open the settings file"));
138
139 CUIRect SavesButton;
140 Left.HSplitBottom(Cut: 20.0f, pTop: &Left, pBottom: &SavesButton);
141 Left.HSplitBottom(Cut: 5.0f, pTop: &Left, pBottom: nullptr);
142 static CButtonContainer s_SavesButtonId;
143 if(DoButton_Menu(pButtonContainer: &s_SavesButtonId, pText: Localize(pStr: "Saves file"), Checked: 0, pRect: &SavesButton))
144 {
145 Storage()->GetCompletePath(Type: IStorage::TYPE_SAVE, pDir: SAVES_FILE, pBuffer: aBuf, BufferSize: sizeof(aBuf));
146 Client()->ViewFile(pFilename: aBuf);
147 }
148 GameClient()->m_Tooltips.DoToolTip(pId: &s_SavesButtonId, pNearRect: &SavesButton, pText: Localize(pStr: "Open the saves file"));
149
150 CUIRect ConfigButton;
151 Left.HSplitBottom(Cut: 20.0f, pTop: &Left, pBottom: &ConfigButton);
152 Left.HSplitBottom(Cut: 5.0f, pTop: &Left, pBottom: nullptr);
153 static CButtonContainer s_ConfigButtonId;
154 if(DoButton_Menu(pButtonContainer: &s_ConfigButtonId, pText: Localize(pStr: "Config directory"), Checked: 0, pRect: &ConfigButton))
155 {
156 Storage()->GetCompletePath(Type: IStorage::TYPE_SAVE, pDir: "", pBuffer: aBuf, BufferSize: sizeof(aBuf));
157 Client()->ViewFile(pFilename: aBuf);
158 }
159 GameClient()->m_Tooltips.DoToolTip(pId: &s_ConfigButtonId, pNearRect: &ConfigButton, pText: Localize(pStr: "Open the directory that contains the configuration and user files"));
160
161 CUIRect DirectoryButton;
162 Left.HSplitBottom(Cut: 20.0f, pTop: &Left, pBottom: &DirectoryButton);
163 Left.HSplitBottom(Cut: 5.0f, pTop: &Left, pBottom: nullptr);
164 static CButtonContainer s_ThemesButtonId;
165 if(DoButton_Menu(pButtonContainer: &s_ThemesButtonId, pText: Localize(pStr: "Themes directory"), Checked: 0, pRect: &DirectoryButton))
166 {
167 Storage()->GetCompletePath(Type: IStorage::TYPE_SAVE, pDir: "themes", pBuffer: aBuf, BufferSize: sizeof(aBuf));
168 Storage()->CreateFolder(pFoldername: "themes", Type: IStorage::TYPE_SAVE);
169 Client()->ViewFile(pFilename: aBuf);
170 }
171 GameClient()->m_Tooltips.DoToolTip(pId: &s_ThemesButtonId, pNearRect: &DirectoryButton, pText: Localize(pStr: "Open the directory to add custom themes"));
172
173 Left.HSplitTop(Cut: 20.0f, pTop: nullptr, pBottom: &Left);
174 RenderThemeSelection(MainView: Left);
175
176 // auto demo settings
177 {
178 Right.HSplitTop(Cut: 40.0f, pTop: nullptr, pBottom: &Right);
179 Right.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Right);
180 if(DoButton_CheckBox(pId: &g_Config.m_ClAutoDemoRecord, pText: Localize(pStr: "Automatically record demos"), Checked: g_Config.m_ClAutoDemoRecord, pRect: &Button))
181 g_Config.m_ClAutoDemoRecord ^= 1;
182
183 Right.HSplitTop(Cut: 2 * 20.0f, pTop: &Button, pBottom: &Right);
184 if(g_Config.m_ClAutoDemoRecord)
185 Ui()->DoScrollbarOption(pId: &g_Config.m_ClAutoDemoMax, pOption: &g_Config.m_ClAutoDemoMax, pRect: &Button, pStr: Localize(pStr: "Max demos"), Min: 1, Max: 1000, pScale: &CUi::ms_LinearScrollbarScale, Flags: CUi::SCROLLBAR_OPTION_INFINITE | CUi::SCROLLBAR_OPTION_MULTILINE);
186
187 Right.HSplitTop(Cut: 10.0f, pTop: nullptr, pBottom: &Right);
188 Right.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Right);
189 if(DoButton_CheckBox(pId: &g_Config.m_ClAutoScreenshot, pText: Localize(pStr: "Automatically take game over screenshot"), Checked: g_Config.m_ClAutoScreenshot, pRect: &Button))
190 g_Config.m_ClAutoScreenshot ^= 1;
191
192 Right.HSplitTop(Cut: 2 * 20.0f, pTop: &Button, pBottom: &Right);
193 if(g_Config.m_ClAutoScreenshot)
194 Ui()->DoScrollbarOption(pId: &g_Config.m_ClAutoScreenshotMax, pOption: &g_Config.m_ClAutoScreenshotMax, pRect: &Button, pStr: Localize(pStr: "Max Screenshots"), Min: 1, Max: 1000, pScale: &CUi::ms_LinearScrollbarScale, Flags: CUi::SCROLLBAR_OPTION_INFINITE | CUi::SCROLLBAR_OPTION_MULTILINE);
195 }
196
197 // auto statboard screenshot
198 {
199 Right.HSplitTop(Cut: 10.0f, pTop: nullptr, pBottom: &Right);
200 Right.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Right);
201 if(DoButton_CheckBox(pId: &g_Config.m_ClAutoStatboardScreenshot, pText: Localize(pStr: "Automatically take statboard screenshot"), Checked: g_Config.m_ClAutoStatboardScreenshot, pRect: &Button))
202 {
203 g_Config.m_ClAutoStatboardScreenshot ^= 1;
204 }
205
206 Right.HSplitTop(Cut: 2 * 20.0f, pTop: &Button, pBottom: &Right);
207 if(g_Config.m_ClAutoStatboardScreenshot)
208 Ui()->DoScrollbarOption(pId: &g_Config.m_ClAutoStatboardScreenshotMax, pOption: &g_Config.m_ClAutoStatboardScreenshotMax, pRect: &Button, pStr: Localize(pStr: "Max Screenshots"), Min: 1, Max: 1000, pScale: &CUi::ms_LinearScrollbarScale, Flags: CUi::SCROLLBAR_OPTION_INFINITE | CUi::SCROLLBAR_OPTION_MULTILINE);
209 }
210
211 // auto statboard csv
212 {
213 Right.HSplitTop(Cut: 10.0f, pTop: nullptr, pBottom: &Right);
214 Right.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Right);
215 if(DoButton_CheckBox(pId: &g_Config.m_ClAutoCSV, pText: Localize(pStr: "Automatically create statboard csv"), Checked: g_Config.m_ClAutoCSV, pRect: &Button))
216 {
217 g_Config.m_ClAutoCSV ^= 1;
218 }
219
220 Right.HSplitTop(Cut: 2 * 20.0f, pTop: &Button, pBottom: &Right);
221 if(g_Config.m_ClAutoCSV)
222 Ui()->DoScrollbarOption(pId: &g_Config.m_ClAutoCSVMax, pOption: &g_Config.m_ClAutoCSVMax, pRect: &Button, pStr: Localize(pStr: "Max CSVs"), Min: 1, Max: 1000, pScale: &CUi::ms_LinearScrollbarScale, Flags: CUi::SCROLLBAR_OPTION_INFINITE | CUi::SCROLLBAR_OPTION_MULTILINE);
223 }
224 }
225}
226
227void CMenus::SetNeedSendInfo()
228{
229 if(m_Dummy)
230 m_NeedSendDummyinfo = true;
231 else
232 m_NeedSendinfo = true;
233}
234
235void CMenus::RenderSettingsPlayer(CUIRect MainView)
236{
237 CUIRect TabBar, PlayerTab, DummyTab, ChangeInfo, QuickSearch;
238 MainView.HSplitTop(Cut: 20.0f, pTop: &TabBar, pBottom: &MainView);
239 TabBar.VSplitMid(pLeft: &TabBar, pRight: &ChangeInfo, Spacing: 20.f);
240 TabBar.VSplitMid(pLeft: &PlayerTab, pRight: &DummyTab);
241 MainView.HSplitTop(Cut: 10.0f, pTop: nullptr, pBottom: &MainView);
242
243 static CButtonContainer s_PlayerTabButton;
244 if(DoButton_MenuTab(pButtonContainer: &s_PlayerTabButton, pText: Localize(pStr: "Player"), Checked: !m_Dummy, pRect: &PlayerTab, Corners: IGraphics::CORNER_L, pAnimator: nullptr, pDefaultColor: nullptr, pActiveColor: nullptr, pHoverColor: nullptr, EdgeRounding: 4.0f))
245 {
246 m_Dummy = false;
247 }
248
249 static CButtonContainer s_DummyTabButton;
250 if(DoButton_MenuTab(pButtonContainer: &s_DummyTabButton, pText: Localize(pStr: "Dummy"), Checked: m_Dummy, pRect: &DummyTab, Corners: IGraphics::CORNER_R, pAnimator: nullptr, pDefaultColor: nullptr, pActiveColor: nullptr, pHoverColor: nullptr, EdgeRounding: 4.0f))
251 {
252 m_Dummy = true;
253 }
254
255 if(Client()->State() == IClient::STATE_ONLINE &&
256 GameClient()->m_aNextChangeInfo[m_Dummy] > Client()->GameTick(Conn: m_Dummy))
257 {
258 char aChangeInfo[128], aTimeLeft[32];
259 str_format(buffer: aTimeLeft, buffer_size: sizeof(aTimeLeft), format: Localize(pStr: "%ds left"), (GameClient()->m_aNextChangeInfo[m_Dummy] - Client()->GameTick(Conn: m_Dummy) + Client()->GameTickSpeed() - 1) / Client()->GameTickSpeed());
260 str_format(buffer: aChangeInfo, buffer_size: sizeof(aChangeInfo), format: "%s: %s", Localize(pStr: "Player info change cooldown"), aTimeLeft);
261 Ui()->DoLabel(pRect: &ChangeInfo, pText: aChangeInfo, Size: 10.f, Align: TEXTALIGN_ML);
262 }
263
264 static CLineInput s_NameInput;
265 static CLineInput s_ClanInput;
266
267 int *pCountry;
268 if(!m_Dummy)
269 {
270 pCountry = &g_Config.m_PlayerCountry;
271 s_NameInput.SetBuffer(pStr: g_Config.m_PlayerName, MaxSize: sizeof(g_Config.m_PlayerName));
272 s_NameInput.SetEmptyText(Client()->PlayerName());
273 s_ClanInput.SetBuffer(pStr: g_Config.m_PlayerClan, MaxSize: sizeof(g_Config.m_PlayerClan));
274 }
275 else
276 {
277 pCountry = &g_Config.m_ClDummyCountry;
278 s_NameInput.SetBuffer(pStr: g_Config.m_ClDummyName, MaxSize: sizeof(g_Config.m_ClDummyName));
279 s_NameInput.SetEmptyText(Client()->DummyName());
280 s_ClanInput.SetBuffer(pStr: g_Config.m_ClDummyClan, MaxSize: sizeof(g_Config.m_ClDummyClan));
281 }
282
283 // player name
284 CUIRect Button, Label;
285 MainView.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MainView);
286 Button.VSplitLeft(Cut: 80.0f, pLeft: &Label, pRight: &Button);
287 Button.VSplitLeft(Cut: 150.0f, pLeft: &Button, pRight: nullptr);
288 char aBuf[128];
289 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s:", Localize(pStr: "Name"));
290 Ui()->DoLabel(pRect: &Label, pText: aBuf, Size: 14.0f, Align: TEXTALIGN_ML);
291 if(Ui()->DoEditBox(pLineInput: &s_NameInput, pRect: &Button, FontSize: 14.0f))
292 {
293 SetNeedSendInfo();
294 }
295
296 // player clan
297 MainView.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &MainView);
298 MainView.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MainView);
299 Button.VSplitLeft(Cut: 80.0f, pLeft: &Label, pRight: &Button);
300 Button.VSplitLeft(Cut: 150.0f, pLeft: &Button, pRight: nullptr);
301 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s:", Localize(pStr: "Clan"));
302 Ui()->DoLabel(pRect: &Label, pText: aBuf, Size: 14.0f, Align: TEXTALIGN_ML);
303 if(Ui()->DoEditBox(pLineInput: &s_ClanInput, pRect: &Button, FontSize: 14.0f))
304 {
305 SetNeedSendInfo();
306 }
307
308 // country flag selector
309 static CLineInputBuffered<25> s_FlagFilterInput;
310
311 std::vector<const CCountryFlags::CCountryFlag *> vpFilteredFlags;
312 for(size_t i = 0; i < GameClient()->m_CountryFlags.Num(); ++i)
313 {
314 const CCountryFlags::CCountryFlag &Entry = GameClient()->m_CountryFlags.GetByIndex(Index: i);
315 if(!str_find_nocase(haystack: Entry.m_aCountryCodeString, needle: s_FlagFilterInput.GetString()))
316 continue;
317 vpFilteredFlags.push_back(x: &Entry);
318 }
319
320 MainView.HSplitTop(Cut: 10.0f, pTop: nullptr, pBottom: &MainView);
321 MainView.HSplitBottom(Cut: 20.0f, pTop: &MainView, pBottom: &QuickSearch);
322 MainView.HSplitBottom(Cut: 5.0f, pTop: &MainView, pBottom: nullptr);
323 QuickSearch.VSplitLeft(Cut: 220.0f, pLeft: &QuickSearch, pRight: nullptr);
324
325 int OldSelected = -1;
326 static CListBox s_ListBox;
327 s_ListBox.DoStart(RowHeight: 48.0f, NumItems: vpFilteredFlags.size(), ItemsPerRow: 10, RowsPerScroll: 3, SelectedIndex: OldSelected, pRect: &MainView);
328
329 for(size_t i = 0; i < vpFilteredFlags.size(); i++)
330 {
331 const CCountryFlags::CCountryFlag *pEntry = vpFilteredFlags[i];
332
333 if(pEntry->m_CountryCode == *pCountry)
334 OldSelected = i;
335
336 const CListboxItem Item = s_ListBox.DoNextItem(pId: &pEntry->m_CountryCode, Selected: OldSelected >= 0 && (size_t)OldSelected == i);
337 if(!Item.m_Visible)
338 continue;
339
340 CUIRect FlagRect;
341 Item.m_Rect.Margin(Cut: 5.0f, pOtherRect: &FlagRect);
342 FlagRect.HSplitBottom(Cut: 12.0f, pTop: &FlagRect, pBottom: &Label);
343 Label.HSplitTop(Cut: 2.0f, pTop: nullptr, pBottom: &Label);
344 const float OldWidth = FlagRect.w;
345 FlagRect.w = FlagRect.h * 2;
346 FlagRect.x += (OldWidth - FlagRect.w) / 2.0f;
347 GameClient()->m_CountryFlags.Render(CountryCode: pEntry->m_CountryCode, Color: ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f), x: FlagRect.x, y: FlagRect.y, w: FlagRect.w, h: FlagRect.h);
348
349 if(pEntry->m_Texture.IsValid())
350 {
351 Ui()->DoLabel(pRect: &Label, pText: pEntry->m_aCountryCodeString, Size: 10.0f, Align: TEXTALIGN_MC);
352 }
353 }
354
355 const int NewSelected = s_ListBox.DoEnd();
356 if(OldSelected != NewSelected)
357 {
358 *pCountry = vpFilteredFlags[NewSelected]->m_CountryCode;
359 SetNeedSendInfo();
360 }
361
362 Ui()->DoEditBox_Search(pLineInput: &s_FlagFilterInput, pRect: &QuickSearch, FontSize: 14.0f, HotkeyEnabled: !Ui()->IsPopupOpen() && !GameClient()->m_GameConsole.IsActive());
363}
364
365void CMenus::RenderSettingsTee(CUIRect MainView)
366{
367 CUIRect TabBar, PlayerTab, DummyTab, ChangeInfo;
368 MainView.HSplitTop(Cut: 20.0f, pTop: &TabBar, pBottom: &MainView);
369 TabBar.VSplitMid(pLeft: &TabBar, pRight: &ChangeInfo, Spacing: 20.f);
370 TabBar.VSplitMid(pLeft: &PlayerTab, pRight: &DummyTab);
371 MainView.HSplitTop(Cut: 10.0f, pTop: nullptr, pBottom: &MainView);
372
373 static CButtonContainer s_PlayerTabButton;
374 if(DoButton_MenuTab(pButtonContainer: &s_PlayerTabButton, pText: Localize(pStr: "Player"), Checked: !m_Dummy, pRect: &PlayerTab, Corners: IGraphics::CORNER_L, pAnimator: nullptr, pDefaultColor: nullptr, pActiveColor: nullptr, pHoverColor: nullptr, EdgeRounding: 4.0f))
375 {
376 m_Dummy = false;
377 m_SkinListScrollToSelected = true;
378 }
379
380 static CButtonContainer s_DummyTabButton;
381 if(DoButton_MenuTab(pButtonContainer: &s_DummyTabButton, pText: Localize(pStr: "Dummy"), Checked: m_Dummy, pRect: &DummyTab, Corners: IGraphics::CORNER_R, pAnimator: nullptr, pDefaultColor: nullptr, pActiveColor: nullptr, pHoverColor: nullptr, EdgeRounding: 4.0f))
382 {
383 m_Dummy = true;
384 m_SkinListScrollToSelected = true;
385 }
386
387 if(Client()->State() == IClient::STATE_ONLINE &&
388 GameClient()->m_aNextChangeInfo[m_Dummy] > Client()->GameTick(Conn: m_Dummy))
389 {
390 char aChangeInfo[128], aTimeLeft[32];
391 str_format(buffer: aTimeLeft, buffer_size: sizeof(aTimeLeft), format: Localize(pStr: "%ds left"), (GameClient()->m_aNextChangeInfo[m_Dummy] - Client()->GameTick(Conn: m_Dummy) + Client()->GameTickSpeed() - 1) / Client()->GameTickSpeed());
392 str_format(buffer: aChangeInfo, buffer_size: sizeof(aChangeInfo), format: "%s: %s", Localize(pStr: "Player info change cooldown"), aTimeLeft);
393 Ui()->DoLabel(pRect: &ChangeInfo, pText: aChangeInfo, Size: 10.f, Align: TEXTALIGN_ML);
394 }
395
396 if(g_Config.m_Debug)
397 {
398 const CSkins::CSkinLoadingStats Stats = GameClient()->m_Skins.LoadingStats();
399 char aStats[256];
400 str_format(buffer: aStats, buffer_size: sizeof(aStats), format: "unloaded: %" PRIzu ", pending: %" PRIzu ", loading: %" PRIzu ",\nloaded: %" PRIzu ", error: %" PRIzu ", notfound: %" PRIzu,
401 Stats.m_NumUnloaded, Stats.m_NumPending, Stats.m_NumLoading, Stats.m_NumLoaded, Stats.m_NumError, Stats.m_NumNotFound);
402 Ui()->DoLabel(pRect: &ChangeInfo, pText: aStats, Size: 9.0f, Align: TEXTALIGN_MR);
403 }
404
405 char *pSkinName;
406 size_t SkinNameSize;
407 int *pUseCustomColor;
408 unsigned *pColorBody;
409 unsigned *pColorFeet;
410 int *pEmote;
411 if(!m_Dummy)
412 {
413 pSkinName = g_Config.m_ClPlayerSkin;
414 SkinNameSize = sizeof(g_Config.m_ClPlayerSkin);
415 pUseCustomColor = &g_Config.m_ClPlayerUseCustomColor;
416 pColorBody = &g_Config.m_ClPlayerColorBody;
417 pColorFeet = &g_Config.m_ClPlayerColorFeet;
418 pEmote = &g_Config.m_ClPlayerDefaultEyes;
419 }
420 else
421 {
422 pSkinName = g_Config.m_ClDummySkin;
423 SkinNameSize = sizeof(g_Config.m_ClDummySkin);
424 pUseCustomColor = &g_Config.m_ClDummyUseCustomColor;
425 pColorBody = &g_Config.m_ClDummyColorBody;
426 pColorFeet = &g_Config.m_ClDummyColorFeet;
427 pEmote = &g_Config.m_ClDummyDefaultEyes;
428 }
429
430 const float EyeButtonSize = 40.0f;
431 const bool RenderEyesBelow = MainView.w < 750.0f;
432 CUIRect YourSkin, Checkboxes, SkinPrefix, Eyes, Button, Label;
433 MainView.HSplitTop(Cut: 90.0f, pTop: &YourSkin, pBottom: &MainView);
434 if(RenderEyesBelow)
435 {
436 YourSkin.VSplitLeft(Cut: MainView.w * 0.45f, pLeft: &YourSkin, pRight: &Checkboxes);
437 Checkboxes.VSplitLeft(Cut: MainView.w * 0.35f, pLeft: &Checkboxes, pRight: &SkinPrefix);
438 MainView.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &MainView);
439 MainView.HSplitTop(Cut: EyeButtonSize, pTop: &Eyes, pBottom: &MainView);
440 Eyes.VSplitRight(Cut: EyeButtonSize * NUM_EMOTES + 5.0f * (NUM_EMOTES - 1), pLeft: nullptr, pRight: &Eyes);
441 }
442 else
443 {
444 YourSkin.VSplitRight(Cut: 3 * EyeButtonSize + 2 * 5.0f, pLeft: &YourSkin, pRight: &Eyes);
445 const float RemainderWidth = YourSkin.w;
446 YourSkin.VSplitLeft(Cut: RemainderWidth * 0.4f, pLeft: &YourSkin, pRight: &Checkboxes);
447 Checkboxes.VSplitLeft(Cut: RemainderWidth * 0.35f, pLeft: &Checkboxes, pRight: &SkinPrefix);
448 SkinPrefix.VSplitRight(Cut: 20.0f, pLeft: &SkinPrefix, pRight: nullptr);
449 }
450 YourSkin.VSplitRight(Cut: 20.0f, pLeft: &YourSkin, pRight: nullptr);
451 Checkboxes.VSplitRight(Cut: 20.0f, pLeft: &Checkboxes, pRight: nullptr);
452
453 // Checkboxes
454 bool ShouldRefresh = false;
455 Checkboxes.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Checkboxes);
456 if(DoButton_CheckBox(pId: &g_Config.m_ClDownloadSkins, pText: Localize(pStr: "Download skins"), Checked: g_Config.m_ClDownloadSkins, pRect: &Button))
457 {
458 g_Config.m_ClDownloadSkins ^= 1;
459 ShouldRefresh = true;
460 }
461
462 Checkboxes.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Checkboxes);
463 if(DoButton_CheckBox(pId: &g_Config.m_ClDownloadCommunitySkins, pText: Localize(pStr: "Download community skins"), Checked: g_Config.m_ClDownloadCommunitySkins, pRect: &Button))
464 {
465 g_Config.m_ClDownloadCommunitySkins ^= 1;
466 ShouldRefresh = true;
467 }
468
469 Checkboxes.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Checkboxes);
470 if(DoButton_CheckBox(pId: &g_Config.m_ClVanillaSkinsOnly, pText: Localize(pStr: "Vanilla skins only"), Checked: g_Config.m_ClVanillaSkinsOnly, pRect: &Button))
471 {
472 g_Config.m_ClVanillaSkinsOnly ^= 1;
473 ShouldRefresh = true;
474 }
475
476 Checkboxes.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Checkboxes);
477 if(DoButton_CheckBox(pId: &g_Config.m_ClFatSkins, pText: Localize(pStr: "Fat skins (DDFat)"), Checked: g_Config.m_ClFatSkins, pRect: &Button))
478 {
479 g_Config.m_ClFatSkins ^= 1;
480 }
481
482 // Skin prefix
483 {
484 SkinPrefix.HSplitTop(Cut: 20.0f, pTop: &Label, pBottom: &SkinPrefix);
485 Ui()->DoLabel(pRect: &Label, pText: Localize(pStr: "Skin prefix"), Size: 14.0f, Align: TEXTALIGN_ML);
486
487 SkinPrefix.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &SkinPrefix);
488 static CLineInput s_SkinPrefixInput(g_Config.m_ClSkinPrefix, sizeof(g_Config.m_ClSkinPrefix));
489 if(Ui()->DoClearableEditBox(pLineInput: &s_SkinPrefixInput, pRect: &Button, FontSize: 14.0f))
490 {
491 ShouldRefresh = true;
492 }
493
494 SkinPrefix.HSplitTop(Cut: 2.0f, pTop: nullptr, pBottom: &SkinPrefix);
495
496 static const char *s_apSkinPrefixes[] = {"kitty", "santa"};
497 static CButtonContainer s_aPrefixButtons[std::size(s_apSkinPrefixes)];
498 for(size_t i = 0; i < std::size(s_apSkinPrefixes); i++)
499 {
500 SkinPrefix.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &SkinPrefix);
501 Button.HMargin(Cut: 2.0f, pOtherRect: &Button);
502 if(DoButton_Menu(pButtonContainer: &s_aPrefixButtons[i], pText: s_apSkinPrefixes[i], Checked: 0, pRect: &Button))
503 {
504 str_copy(dst&: g_Config.m_ClSkinPrefix, src: s_apSkinPrefixes[i]);
505 ShouldRefresh = true;
506 }
507 }
508 }
509
510 // Player skin area
511 CUIRect CustomColorsButton, RandomSkinButton;
512 YourSkin.HSplitTop(Cut: 20.0f, pTop: &Label, pBottom: &YourSkin);
513 YourSkin.HSplitBottom(Cut: 20.0f, pTop: &YourSkin, pBottom: &CustomColorsButton);
514 CustomColorsButton.VSplitRight(Cut: 30.0f, pLeft: &CustomColorsButton, pRight: &RandomSkinButton);
515 CustomColorsButton.VSplitRight(Cut: 20.0f, pLeft: &CustomColorsButton, pRight: nullptr);
516 YourSkin.VSplitLeft(Cut: 65.0f, pLeft: &YourSkin, pRight: &Button);
517 Button.VSplitLeft(Cut: 5.0f, pLeft: nullptr, pRight: &Button);
518 Button.HMargin(Cut: (Button.h - 20.0f) / 2.0f, pOtherRect: &Button);
519
520 char aBuf[128 + IO_MAX_PATH_LENGTH];
521 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s:", Localize(pStr: "Your skin"));
522 Ui()->DoLabel(pRect: &Label, pText: aBuf, Size: 14.0f, Align: TEXTALIGN_ML);
523
524 CSkins::CSkinList &SkinList = GameClient()->m_Skins.SkinList();
525 const CSkin *pDefaultSkin = GameClient()->m_Skins.Find(pName: "default");
526 const CSkins::CSkinContainer *pOwnSkinContainer = GameClient()->m_Skins.FindContainerOrNullptr(pName: pSkinName[0] == '\0' ? "default" : pSkinName);
527 if(pOwnSkinContainer != nullptr && pOwnSkinContainer->IsSpecial())
528 {
529 pOwnSkinContainer = nullptr; // Special skins cannot be selected, show as missing due to invalid name
530 }
531
532 CTeeRenderInfo OwnSkinInfo;
533 OwnSkinInfo.Apply(pSkin: pOwnSkinContainer == nullptr || pOwnSkinContainer->Skin() == nullptr ? pDefaultSkin : pOwnSkinContainer->Skin().get());
534 OwnSkinInfo.ApplyColors(CustomColoredSkin: *pUseCustomColor, ColorBody: *pColorBody, ColorFeet: *pColorFeet);
535 OwnSkinInfo.m_Size = 50.0f;
536
537 // Tee
538 {
539 vec2 OffsetToMid;
540 CRenderTools::GetRenderTeeOffsetToRenderedTee(pAnim: CAnimState::GetIdle(), pInfo: &OwnSkinInfo, TeeOffsetToMid&: OffsetToMid);
541 const vec2 TeeRenderPos = vec2(YourSkin.x + YourSkin.w / 2.0f, YourSkin.y + YourSkin.h / 2.0f + OffsetToMid.y);
542 // tee looking towards cursor, and it is happy when you touch it
543 const vec2 DeltaPosition = Ui()->MousePos() - TeeRenderPos;
544 const float Distance = length(a: DeltaPosition);
545 const float InteractionDistance = 20.0f;
546 const vec2 TeeDirection = Distance < InteractionDistance ? normalize(v: vec2(DeltaPosition.x, maximum(a: DeltaPosition.y, b: 0.5f))) : normalize(v: DeltaPosition);
547 const int TeeEmote = Distance < InteractionDistance ? EMOTE_HAPPY : *pEmote;
548 RenderTools()->RenderTee(pAnim: CAnimState::GetIdle(), pInfo: &OwnSkinInfo, Emote: TeeEmote, Dir: TeeDirection, Pos: TeeRenderPos);
549 }
550
551 // Skin loading status
552 const auto &&RenderSkinStatus = [&](CUIRect Parent, const CSkins::CSkinContainer *pSkinContainer, const void *pStatusTooltipId) {
553 if(pSkinContainer != nullptr && pSkinContainer->State() == CSkins::CSkinContainer::EState::LOADED)
554 {
555 return;
556 }
557
558 CUIRect StatusIcon;
559 Parent.HSplitTop(Cut: 20.0f, pTop: &StatusIcon, pBottom: nullptr);
560 StatusIcon.VSplitLeft(Cut: 20.0f, pLeft: &StatusIcon, pRight: nullptr);
561
562 if(pSkinContainer != nullptr &&
563 (pSkinContainer->State() == CSkins::CSkinContainer::EState::UNLOADED ||
564 pSkinContainer->State() == CSkins::CSkinContainer::EState::PENDING ||
565 pSkinContainer->State() == CSkins::CSkinContainer::EState::LOADING))
566 {
567 Ui()->RenderProgressSpinner(Center: StatusIcon.Center(), OuterRadius: 5.0f);
568 }
569 else
570 {
571 TextRender()->TextColor(Color: ColorRGBA(1.0f, 0.0f, 0.0f, 1.0f));
572 TextRender()->SetFontPreset(EFontPreset::ICON_FONT);
573 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);
574 Ui()->DoLabel(pRect: &StatusIcon, pText: pSkinContainer == nullptr || pSkinContainer->State() == CSkins::CSkinContainer::EState::ERROR ? FONT_ICON_TRIANGLE_EXCLAMATION : FONT_ICON_QUESTION, Size: 12.0f, Align: TEXTALIGN_MC);
575 TextRender()->SetRenderFlags(0);
576 TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT);
577 TextRender()->TextColor(Color: TextRender()->DefaultTextColor());
578 Ui()->DoButtonLogic(pId: pStatusTooltipId, Checked: 0, pRect: &StatusIcon, Flags: BUTTONFLAG_NONE);
579 const char *pErrorTooltip;
580 if(pSkinContainer == nullptr)
581 {
582 pErrorTooltip = Localize(pStr: "This skin name cannot be used.");
583 }
584 else if(pSkinContainer->State() == CSkins::CSkinContainer::EState::ERROR)
585 {
586 pErrorTooltip = Localize(pStr: "Skin could not be loaded due to an error. Check the local console for details.");
587 }
588 else
589 {
590 pErrorTooltip = Localize(pStr: "Skin could not be found.");
591 }
592 GameClient()->m_Tooltips.DoToolTip(pId: pStatusTooltipId, pNearRect: &StatusIcon, pText: pErrorTooltip);
593 }
594 };
595 static char s_StatusTooltipId;
596 RenderSkinStatus(YourSkin, pOwnSkinContainer, &s_StatusTooltipId);
597
598 // Skin name
599 static CLineInput s_SkinInput;
600 s_SkinInput.SetBuffer(pStr: pSkinName, MaxSize: SkinNameSize);
601 s_SkinInput.SetEmptyText("default");
602 if(Ui()->DoClearableEditBox(pLineInput: &s_SkinInput, pRect: &Button, FontSize: 14.0f))
603 {
604 SetNeedSendInfo();
605 m_SkinListScrollToSelected = true;
606 SkinList.ForceRefresh();
607 }
608
609 // Random skin button
610 static CButtonContainer s_RandomSkinButton;
611 static const char *s_apDice[] = {FONT_ICON_DICE_ONE, FONT_ICON_DICE_TWO, FONT_ICON_DICE_THREE, FONT_ICON_DICE_FOUR, FONT_ICON_DICE_FIVE, FONT_ICON_DICE_SIX};
612 static int s_CurrentDie = rand() % std::size(s_apDice);
613 TextRender()->SetFontPreset(EFontPreset::ICON_FONT);
614 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);
615 if(DoButton_Menu(pButtonContainer: &s_RandomSkinButton, pText: s_apDice[s_CurrentDie], Checked: 0, pRect: &RandomSkinButton, Flags: BUTTONFLAG_LEFT, pImageName: nullptr, Corners: IGraphics::CORNER_ALL, Rounding: 5.0f, FontFactor: -0.2f))
616 {
617 GameClient()->m_Skins.RandomizeSkin(Dummy: m_Dummy);
618 SetNeedSendInfo();
619 m_SkinListScrollToSelected = true;
620 s_CurrentDie = rand() % std::size(s_apDice);
621 }
622 TextRender()->SetRenderFlags(0);
623 TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT);
624 GameClient()->m_Tooltips.DoToolTip(pId: &s_RandomSkinButton, pNearRect: &RandomSkinButton, pText: Localize(pStr: "Create a random skin"));
625
626 // Custom colors button
627 if(DoButton_CheckBox(pId: pUseCustomColor, pText: Localize(pStr: "Custom colors"), Checked: *pUseCustomColor, pRect: &CustomColorsButton))
628 {
629 *pUseCustomColor = *pUseCustomColor ? 0 : 1;
630 SetNeedSendInfo();
631 }
632
633 // Default eyes
634 {
635 CTeeRenderInfo EyeSkinInfo = OwnSkinInfo;
636 EyeSkinInfo.m_Size = EyeButtonSize;
637 vec2 OffsetToMid;
638 CRenderTools::GetRenderTeeOffsetToRenderedTee(pAnim: CAnimState::GetIdle(), pInfo: &EyeSkinInfo, TeeOffsetToMid&: OffsetToMid);
639
640 CUIRect EyesRow;
641 Eyes.HSplitTop(Cut: EyeButtonSize, pTop: &EyesRow, pBottom: &Eyes);
642 static CButtonContainer s_aEyeButtons[NUM_EMOTES];
643 for(int CurrentEyeEmote = 0; CurrentEyeEmote < NUM_EMOTES; CurrentEyeEmote++)
644 {
645 EyesRow.VSplitLeft(Cut: EyeButtonSize, pLeft: &Button, pRight: &EyesRow);
646 EyesRow.VSplitLeft(Cut: 5.0f, pLeft: nullptr, pRight: &EyesRow);
647 if(!RenderEyesBelow && (CurrentEyeEmote + 1) % 3 == 0)
648 {
649 Eyes.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &Eyes);
650 Eyes.HSplitTop(Cut: EyeButtonSize, pTop: &EyesRow, pBottom: &Eyes);
651 }
652
653 const ColorRGBA EyeButtonColor = ColorRGBA(1.0f, 1.0f, 1.0f, 0.25f + (*pEmote == CurrentEyeEmote ? 0.25f : 0.0f));
654 if(DoButton_Menu(pButtonContainer: &s_aEyeButtons[CurrentEyeEmote], pText: "", Checked: 0, pRect: &Button, Flags: BUTTONFLAG_LEFT, pImageName: nullptr, Corners: IGraphics::CORNER_ALL, Rounding: 5.0f, FontFactor: 0.0f, Color: EyeButtonColor))
655 {
656 *pEmote = CurrentEyeEmote;
657 if((int)m_Dummy == g_Config.m_ClDummy)
658 GameClient()->m_Emoticon.EyeEmote(EyeEmote: CurrentEyeEmote);
659 }
660 GameClient()->m_Tooltips.DoToolTip(pId: &s_aEyeButtons[CurrentEyeEmote], pNearRect: &Button, pText: Localize(pStr: "Choose default eyes when joining a server"));
661 RenderTools()->RenderTee(pAnim: CAnimState::GetIdle(), pInfo: &EyeSkinInfo, Emote: CurrentEyeEmote, Dir: vec2(1.0f, 0.0f), Pos: vec2(Button.x + Button.w / 2.0f, Button.y + Button.h / 2.0f + OffsetToMid.y));
662 }
663 }
664
665 // Custom color pickers
666 MainView.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &MainView);
667 if(*pUseCustomColor)
668 {
669 CUIRect CustomColors;
670 MainView.HSplitTop(Cut: 95.0f, pTop: &CustomColors, pBottom: &MainView);
671 CUIRect aRects[2];
672 CustomColors.VSplitMid(pLeft: &aRects[0], pRight: &aRects[1], Spacing: 20.0f);
673
674 unsigned *apColors[] = {pColorBody, pColorFeet};
675 const char *apParts[] = {Localize(pStr: "Body"), Localize(pStr: "Feet")};
676
677 for(int i = 0; i < 2; i++)
678 {
679 aRects[i].HSplitTop(Cut: 20.0f, pTop: &Label, pBottom: &aRects[i]);
680 Ui()->DoLabel(pRect: &Label, pText: apParts[i], Size: 14.0f, Align: TEXTALIGN_ML);
681 if(RenderHslaScrollbars(pRect: &aRects[i], pColor: apColors[i], Alpha: false, DarkestLight: ColorHSLA::DARKEST_LGT))
682 {
683 SetNeedSendInfo();
684 }
685 }
686 }
687 MainView.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &MainView);
688
689 // Layout bottom controls and use remainder for skin selector
690 CUIRect QuickSearch, DatabaseButton, DirectoryButton, RefreshButton;
691 MainView.HSplitBottom(Cut: 20.0f, pTop: &MainView, pBottom: &QuickSearch);
692 MainView.HSplitBottom(Cut: 5.0f, pTop: &MainView, pBottom: nullptr);
693 QuickSearch.VSplitLeft(Cut: 220.0f, pLeft: &QuickSearch, pRight: &DatabaseButton);
694 DatabaseButton.VSplitLeft(Cut: 10.0f, pLeft: nullptr, pRight: &DatabaseButton);
695 DatabaseButton.VSplitLeft(Cut: 150.0f, pLeft: &DatabaseButton, pRight: &DirectoryButton);
696 DirectoryButton.VSplitRight(Cut: 175.0f, pLeft: nullptr, pRight: &DirectoryButton);
697 DirectoryButton.VSplitRight(Cut: 25.0f, pLeft: &DirectoryButton, pRight: &RefreshButton);
698 DirectoryButton.VSplitRight(Cut: 10.0f, pLeft: &DirectoryButton, pRight: nullptr);
699
700 // Skin selector
701 static CListBox s_ListBox;
702 std::vector<CSkins::CSkinListEntry> &vSkinList = SkinList.Skins();
703 int OldSelected = -1;
704 s_ListBox.DoStart(RowHeight: 50.0f, NumItems: vSkinList.size(), ItemsPerRow: 4, RowsPerScroll: 2, SelectedIndex: OldSelected, pRect: &MainView);
705 for(size_t i = 0; i < vSkinList.size(); ++i)
706 {
707 CSkins::CSkinListEntry &SkinListEntry = vSkinList[i];
708 const CSkins::CSkinContainer *pSkinContainer = vSkinList[i].SkinContainer();
709
710 if(!m_Dummy ? SkinListEntry.IsSelectedMain() : SkinListEntry.IsSelectedDummy())
711 {
712 OldSelected = i;
713 if(m_SkinListScrollToSelected)
714 {
715 s_ListBox.ScrollToSelected();
716 m_SkinListScrollToSelected = false;
717 }
718 }
719
720 const CListboxItem Item = s_ListBox.DoNextItem(pId: SkinListEntry.ListItemId(), Selected: OldSelected >= 0 && (size_t)OldSelected == i);
721 if(!Item.m_Visible)
722 {
723 continue;
724 }
725
726 SkinListEntry.RequestLoad();
727 const CSkin *pSkin = pSkinContainer->State() == CSkins::CSkinContainer::EState::LOADED ? pSkinContainer->Skin().get() : pDefaultSkin;
728
729 Item.m_Rect.VSplitLeft(Cut: 60.0f, pLeft: &Button, pRight: &Label);
730
731 {
732 CTeeRenderInfo Info = OwnSkinInfo;
733 Info.Apply(pSkin);
734 vec2 OffsetToMid;
735 CRenderTools::GetRenderTeeOffsetToRenderedTee(pAnim: CAnimState::GetIdle(), pInfo: &Info, TeeOffsetToMid&: OffsetToMid);
736 const vec2 TeeRenderPos = vec2(Button.x + Button.w / 2.0f, Button.y + Button.h / 2 + OffsetToMid.y);
737 RenderTools()->RenderTee(pAnim: CAnimState::GetIdle(), pInfo: &Info, Emote: *pEmote, Dir: vec2(1.0f, 0.0f), Pos: TeeRenderPos);
738 }
739
740 {
741 SLabelProperties Props;
742 Props.m_MaxWidth = Label.w - 5.0f;
743 const auto &NameMatch = SkinListEntry.NameMatch();
744 if(NameMatch.has_value())
745 {
746 const auto [MatchStart, MatchLength] = NameMatch.value();
747 Props.m_vColorSplits.emplace_back(args: MatchStart, args: MatchLength, args: ColorRGBA(0.4f, 0.4f, 1.0f, 1.0f));
748 }
749 Ui()->DoLabel(pRect: &Label, pText: pSkinContainer->Name(), Size: 12.0f, Align: TEXTALIGN_ML, LabelProps: Props);
750 }
751
752 if(g_Config.m_Debug)
753 {
754 Graphics()->TextureClear();
755 Graphics()->QuadsBegin();
756 Graphics()->SetColor(*pUseCustomColor ? color_cast<ColorRGBA>(hsl: ColorHSLA(*pColorBody).UnclampLighting(Darkest: ColorHSLA::DARKEST_LGT)) : pSkin->m_BloodColor);
757 IGraphics::CQuadItem QuadItem(Label.x, Label.y, 12.0f, 12.0f);
758 Graphics()->QuadsDrawTL(pArray: &QuadItem, Num: 1);
759 Graphics()->QuadsEnd();
760 }
761
762 // render skin favorite icon
763 {
764 CUIRect FavIcon;
765 Item.m_Rect.HSplitTop(Cut: 20.0f, pTop: &FavIcon, pBottom: nullptr);
766 FavIcon.VSplitRight(Cut: 20.0f, pLeft: nullptr, pRight: &FavIcon);
767 if(DoButton_Favorite(pButtonId: SkinListEntry.FavoriteButtonId(), pParentId: SkinListEntry.ListItemId(), Checked: SkinListEntry.IsFavorite(), pRect: &FavIcon))
768 {
769 if(SkinListEntry.IsFavorite())
770 {
771 GameClient()->m_Skins.RemoveFavorite(pName: pSkinContainer->Name());
772 }
773 else
774 {
775 GameClient()->m_Skins.AddFavorite(pName: pSkinContainer->Name());
776 }
777 }
778 }
779
780 RenderSkinStatus(Item.m_Rect, pSkinContainer, SkinListEntry.ErrorTooltipId());
781 }
782
783 const int NewSelected = s_ListBox.DoEnd();
784 if(OldSelected != NewSelected)
785 {
786 str_copy(dst: pSkinName, src: vSkinList[NewSelected].SkinContainer()->Name(), dst_size: SkinNameSize);
787 SkinList.ForceRefresh();
788 SetNeedSendInfo();
789 }
790
791 static CLineInput s_SkinFilterInput(g_Config.m_ClSkinFilterString, sizeof(g_Config.m_ClSkinFilterString));
792 if(SkinList.UnfilteredCount() > 0 && vSkinList.empty())
793 {
794 CUIRect FilterLabel, ResetButton;
795 MainView.HMargin(Cut: (MainView.h - (16.0f + 18.0f + 8.0f)) / 2.0f, pOtherRect: &FilterLabel);
796 FilterLabel.HSplitTop(Cut: 16.0f, pTop: &FilterLabel, pBottom: &ResetButton);
797 ResetButton.HSplitTop(Cut: 8.0f, pTop: nullptr, pBottom: &ResetButton);
798 ResetButton.VMargin(Cut: (ResetButton.w - 200.0f) / 2.0f, pOtherRect: &ResetButton);
799 Ui()->DoLabel(pRect: &FilterLabel, pText: Localize(pStr: "No skins match your filter criteria"), Size: 16.0f, Align: TEXTALIGN_MC);
800 static CButtonContainer s_ResetButton;
801 if(DoButton_Menu(pButtonContainer: &s_ResetButton, pText: Localize(pStr: "Reset filter"), Checked: 0, pRect: &ResetButton))
802 {
803 s_SkinFilterInput.Clear();
804 SkinList.ForceRefresh();
805 }
806 }
807
808 if(Ui()->DoEditBox_Search(pLineInput: &s_SkinFilterInput, pRect: &QuickSearch, FontSize: 14.0f, HotkeyEnabled: !Ui()->IsPopupOpen() && !GameClient()->m_GameConsole.IsActive()))
809 {
810 SkinList.ForceRefresh();
811 }
812
813 static CButtonContainer s_SkinDatabaseButton;
814 if(DoButton_Menu(pButtonContainer: &s_SkinDatabaseButton, pText: Localize(pStr: "Skin Database"), Checked: 0, pRect: &DatabaseButton))
815 {
816 Client()->ViewLink(pLink: "https://ddnet.org/skins/");
817 }
818
819 static CButtonContainer s_DirectoryButton;
820 if(DoButton_Menu(pButtonContainer: &s_DirectoryButton, pText: Localize(pStr: "Skins directory"), Checked: 0, pRect: &DirectoryButton))
821 {
822 Storage()->GetCompletePath(Type: IStorage::TYPE_SAVE, pDir: "skins", pBuffer: aBuf, BufferSize: sizeof(aBuf));
823 Storage()->CreateFolder(pFoldername: "skins", Type: IStorage::TYPE_SAVE);
824 Client()->ViewFile(pFilename: aBuf);
825 }
826 GameClient()->m_Tooltips.DoToolTip(pId: &s_DirectoryButton, pNearRect: &DirectoryButton, pText: Localize(pStr: "Open the directory to add custom skins"));
827
828 TextRender()->SetFontPreset(EFontPreset::ICON_FONT);
829 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);
830 static CButtonContainer s_SkinRefreshButton;
831 if(DoButton_Menu(pButtonContainer: &s_SkinRefreshButton, pText: FONT_ICON_ARROW_ROTATE_RIGHT, Checked: 0, pRect: &RefreshButton) || Input()->KeyPress(Key: KEY_F5) || (Input()->KeyPress(Key: KEY_R) && Input()->ModifierIsPressed()))
832 {
833 ShouldRefresh = true;
834 }
835 TextRender()->SetRenderFlags(0);
836 TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT);
837
838 if(ShouldRefresh)
839 {
840 GameClient()->RefreshSkins(SkinDescriptorFlags: CSkinDescriptor::FLAG_SIX);
841 }
842}
843
844void CMenus::RenderSettingsGraphics(CUIRect MainView)
845{
846 CUIRect Button;
847 char aBuf[128];
848 bool CheckSettings = false;
849
850 static const int MAX_RESOLUTIONS = 256;
851 static CVideoMode s_aModes[MAX_RESOLUTIONS];
852 static int s_NumNodes = Graphics()->GetVideoModes(pModes: s_aModes, MaxModes: MAX_RESOLUTIONS, Screen: g_Config.m_GfxScreen);
853 static int s_GfxFsaaSamples = g_Config.m_GfxFsaaSamples;
854 static bool s_GfxBackendChanged = false;
855 static bool s_GfxGpuChanged = false;
856
857 static int s_InitDisplayAllVideoModes = g_Config.m_GfxDisplayAllVideoModes;
858
859 static bool s_WasInit = false;
860 static bool s_ModesReload = false;
861 if(!s_WasInit)
862 {
863 s_WasInit = true;
864
865 Graphics()->AddWindowPropChangeListener(pFunc: []() {
866 s_ModesReload = true;
867 });
868 }
869
870 if(s_ModesReload || g_Config.m_GfxDisplayAllVideoModes != s_InitDisplayAllVideoModes)
871 {
872 s_NumNodes = Graphics()->GetVideoModes(pModes: s_aModes, MaxModes: MAX_RESOLUTIONS, Screen: g_Config.m_GfxScreen);
873 s_ModesReload = false;
874 s_InitDisplayAllVideoModes = g_Config.m_GfxDisplayAllVideoModes;
875 }
876
877 CUIRect ModeList, ModeLabel;
878 MainView.VSplitLeft(Cut: 350.0f, pLeft: &MainView, pRight: &ModeList);
879 ModeList.HSplitTop(Cut: 24.0f, pTop: &ModeLabel, pBottom: &ModeList);
880 MainView.VSplitLeft(Cut: 340.0f, pLeft: &MainView, pRight: nullptr);
881
882 // display mode list
883 static CListBox s_ListBox;
884 static const float sc_RowHeightResList = 22.0f;
885 static const float sc_FontSizeResListHeader = 12.0f;
886 static const float sc_FontSizeResList = 10.0f;
887
888 {
889 int G = std::gcd(m: g_Config.m_GfxScreenWidth, n: g_Config.m_GfxScreenHeight);
890 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s: %dx%d @%dhz %d bit (%d:%d)", Localize(pStr: "Current"), (int)(g_Config.m_GfxScreenWidth * Graphics()->ScreenHiDPIScale()), (int)(g_Config.m_GfxScreenHeight * Graphics()->ScreenHiDPIScale()), g_Config.m_GfxScreenRefreshRate, g_Config.m_GfxColorDepth, g_Config.m_GfxScreenWidth / G, g_Config.m_GfxScreenHeight / G);
891 Ui()->DoLabel(pRect: &ModeLabel, pText: aBuf, Size: sc_FontSizeResListHeader, Align: TEXTALIGN_MC);
892 }
893
894 int OldSelected = -1;
895 s_ListBox.SetActive(!Ui()->IsPopupOpen());
896 s_ListBox.DoStart(RowHeight: sc_RowHeightResList, NumItems: s_NumNodes, ItemsPerRow: 1, RowsPerScroll: 3, SelectedIndex: OldSelected, pRect: &ModeList);
897
898 for(int i = 0; i < s_NumNodes; ++i)
899 {
900 const int Depth = s_aModes[i].m_Red + s_aModes[i].m_Green + s_aModes[i].m_Blue > 16 ? 24 : 16;
901 if(g_Config.m_GfxColorDepth == Depth &&
902 g_Config.m_GfxScreenWidth == s_aModes[i].m_WindowWidth &&
903 g_Config.m_GfxScreenHeight == s_aModes[i].m_WindowHeight &&
904 g_Config.m_GfxScreenRefreshRate == s_aModes[i].m_RefreshRate)
905 {
906 OldSelected = i;
907 }
908
909 const CListboxItem Item = s_ListBox.DoNextItem(pId: &s_aModes[i], Selected: OldSelected == i);
910 if(!Item.m_Visible)
911 continue;
912
913 int G = std::gcd(m: s_aModes[i].m_WindowWidth, n: s_aModes[i].m_WindowHeight);
914 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: " %dx%d @%dhz %d bit (%d:%d)", s_aModes[i].m_CanvasWidth, s_aModes[i].m_CanvasHeight, s_aModes[i].m_RefreshRate, Depth, s_aModes[i].m_WindowWidth / G, s_aModes[i].m_WindowHeight / G);
915 Ui()->DoLabel(pRect: &Item.m_Rect, pText: aBuf, Size: sc_FontSizeResList, Align: TEXTALIGN_ML);
916 }
917
918 const int NewSelected = s_ListBox.DoEnd();
919 if(OldSelected != NewSelected)
920 {
921 const int Depth = s_aModes[NewSelected].m_Red + s_aModes[NewSelected].m_Green + s_aModes[NewSelected].m_Blue > 16 ? 24 : 16;
922 g_Config.m_GfxColorDepth = Depth;
923 g_Config.m_GfxScreenWidth = s_aModes[NewSelected].m_WindowWidth;
924 g_Config.m_GfxScreenHeight = s_aModes[NewSelected].m_WindowHeight;
925 g_Config.m_GfxScreenRefreshRate = s_aModes[NewSelected].m_RefreshRate;
926 Graphics()->ResizeToScreen();
927 }
928
929 // switches
930 CUIRect WindowModeDropDown;
931 MainView.HSplitTop(Cut: 20.0f, pTop: &WindowModeDropDown, pBottom: &MainView);
932
933 const char *apWindowModes[] = {Localize(pStr: "Windowed"), Localize(pStr: "Windowed borderless"), Localize(pStr: "Windowed fullscreen"), Localize(pStr: "Desktop fullscreen"), Localize(pStr: "Fullscreen")};
934 static const int s_NumWindowMode = std::size(apWindowModes);
935
936 const int OldWindowMode = (g_Config.m_GfxFullscreen ? (g_Config.m_GfxFullscreen == 1 ? 4 : (g_Config.m_GfxFullscreen == 2 ? 3 : 2)) : (g_Config.m_GfxBorderless ? 1 : 0));
937
938 static CUi::SDropDownState s_WindowModeDropDownState;
939 static CScrollRegion s_WindowModeDropDownScrollRegion;
940 s_WindowModeDropDownState.m_SelectionPopupContext.m_pScrollRegion = &s_WindowModeDropDownScrollRegion;
941 const int NewWindowMode = Ui()->DoDropDown(pRect: &WindowModeDropDown, CurSelection: OldWindowMode, pStrs: apWindowModes, Num: s_NumWindowMode, State&: s_WindowModeDropDownState);
942 if(OldWindowMode != NewWindowMode)
943 {
944 if(NewWindowMode == 0)
945 Graphics()->SetWindowParams(FullscreenMode: 0, IsBorderless: false);
946 else if(NewWindowMode == 1)
947 Graphics()->SetWindowParams(FullscreenMode: 0, IsBorderless: true);
948 else if(NewWindowMode == 2)
949 Graphics()->SetWindowParams(FullscreenMode: 3, IsBorderless: false);
950 else if(NewWindowMode == 3)
951 Graphics()->SetWindowParams(FullscreenMode: 2, IsBorderless: false);
952 else if(NewWindowMode == 4)
953 Graphics()->SetWindowParams(FullscreenMode: 1, IsBorderless: false);
954 }
955
956 if(Graphics()->GetNumScreens() > 1)
957 {
958 CUIRect ScreenDropDown;
959 MainView.HSplitTop(Cut: 2.0f, pTop: nullptr, pBottom: &MainView);
960 MainView.HSplitTop(Cut: 20.0f, pTop: &ScreenDropDown, pBottom: &MainView);
961
962 const int NumScreens = Graphics()->GetNumScreens();
963 static std::vector<std::string> s_vScreenNames;
964 static std::vector<const char *> s_vpScreenNames;
965 s_vScreenNames.resize(new_size: NumScreens);
966 s_vpScreenNames.resize(new_size: NumScreens);
967
968 for(int i = 0; i < NumScreens; ++i)
969 {
970 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s %d: %s", Localize(pStr: "Screen"), i, Graphics()->GetScreenName(Screen: i));
971 s_vScreenNames[i] = aBuf;
972 s_vpScreenNames[i] = s_vScreenNames[i].c_str();
973 }
974
975 static CUi::SDropDownState s_ScreenDropDownState;
976 static CScrollRegion s_ScreenDropDownScrollRegion;
977 s_ScreenDropDownState.m_SelectionPopupContext.m_pScrollRegion = &s_ScreenDropDownScrollRegion;
978 const int NewScreen = Ui()->DoDropDown(pRect: &ScreenDropDown, CurSelection: g_Config.m_GfxScreen, pStrs: s_vpScreenNames.data(), Num: s_vpScreenNames.size(), State&: s_ScreenDropDownState);
979 if(NewScreen != g_Config.m_GfxScreen)
980 Graphics()->SwitchWindowScreen(Index: NewScreen, MoveToCenter: true);
981 }
982
983 MainView.HSplitTop(Cut: 2.0f, pTop: nullptr, pBottom: &MainView);
984 MainView.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MainView);
985 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s (%s)", Localize(pStr: "V-Sync"), Localize(pStr: "may cause delay"));
986 if(DoButton_CheckBox(pId: &g_Config.m_GfxVsync, pText: aBuf, Checked: g_Config.m_GfxVsync, pRect: &Button))
987 {
988 Graphics()->SetVSync(!g_Config.m_GfxVsync);
989 }
990
991 bool MultiSamplingChanged = false;
992 MainView.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MainView);
993 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s (%s)", Localize(pStr: "FSAA samples"), Localize(pStr: "may cause delay"));
994 int GfxFsaaSamplesMouseButton = DoButton_CheckBox_Number(pId: &g_Config.m_GfxFsaaSamples, pText: aBuf, Checked: g_Config.m_GfxFsaaSamples, pRect: &Button);
995 int CurFSAA = g_Config.m_GfxFsaaSamples == 0 ? 1 : g_Config.m_GfxFsaaSamples;
996 if(GfxFsaaSamplesMouseButton == 1) // inc
997 {
998 g_Config.m_GfxFsaaSamples = std::pow(x: 2, y: (int)std::log2(x: CurFSAA) + 1);
999 if(g_Config.m_GfxFsaaSamples > 64)
1000 g_Config.m_GfxFsaaSamples = 0;
1001 MultiSamplingChanged = true;
1002 }
1003 else if(GfxFsaaSamplesMouseButton == 2) // dec
1004 {
1005 if(CurFSAA == 1)
1006 g_Config.m_GfxFsaaSamples = 64;
1007 else if(CurFSAA == 2)
1008 g_Config.m_GfxFsaaSamples = 0;
1009 else
1010 g_Config.m_GfxFsaaSamples = std::pow(x: 2, y: (int)std::log2(x: CurFSAA) - 1);
1011 MultiSamplingChanged = true;
1012 }
1013
1014 uint32_t MultiSamplingCountBackend = 0;
1015 if(MultiSamplingChanged)
1016 {
1017 if(Graphics()->SetMultiSampling(ReqMultiSamplingCount: g_Config.m_GfxFsaaSamples, MultiSamplingCountBackend))
1018 {
1019 // try again with 0 if mouse click was increasing multi sampling
1020 // else just accept the current value as is
1021 if((uint32_t)g_Config.m_GfxFsaaSamples > MultiSamplingCountBackend && GfxFsaaSamplesMouseButton == 1)
1022 Graphics()->SetMultiSampling(ReqMultiSamplingCount: 0, MultiSamplingCountBackend);
1023 g_Config.m_GfxFsaaSamples = (int)MultiSamplingCountBackend;
1024 }
1025 else
1026 {
1027 CheckSettings = true;
1028 }
1029 }
1030
1031 MainView.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MainView);
1032 if(DoButton_CheckBox(pId: &g_Config.m_GfxHighDetail, pText: Localize(pStr: "High Detail"), Checked: g_Config.m_GfxHighDetail, pRect: &Button))
1033 g_Config.m_GfxHighDetail ^= 1;
1034 GameClient()->m_Tooltips.DoToolTip(pId: &g_Config.m_GfxHighDetail, pNearRect: &Button, pText: Localize(pStr: "Allows maps to render with more detail"));
1035
1036 MainView.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MainView);
1037 if(DoButton_CheckBox(pId: &g_Config.m_ClShowfps, pText: Localize(pStr: "Show FPS"), Checked: g_Config.m_ClShowfps, pRect: &Button))
1038 g_Config.m_ClShowfps ^= 1;
1039 GameClient()->m_Tooltips.DoToolTip(pId: &g_Config.m_ClShowfps, pNearRect: &Button, pText: Localize(pStr: "Renders your frame rate in the top right"));
1040
1041 MainView.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MainView);
1042 str_copy(dst&: aBuf, src: " ");
1043 str_append(dst&: aBuf, src: Localize(pStr: "Hz", pContext: "Hertz"));
1044 Ui()->DoScrollbarOption(pId: &g_Config.m_GfxRefreshRate, pOption: &g_Config.m_GfxRefreshRate, pRect: &Button, pStr: Localize(pStr: "Refresh Rate"), Min: 10, Max: 1000, pScale: &CUi::ms_LinearScrollbarScale, Flags: CUi::SCROLLBAR_OPTION_INFINITE | CUi::SCROLLBAR_OPTION_NOCLAMPVALUE | CUi::SCROLLBAR_OPTION_DELAYUPDATE, pSuffix: aBuf);
1045
1046 MainView.HSplitTop(Cut: 2.0f, pTop: nullptr, pBottom: &MainView);
1047 static CButtonContainer s_UiColorResetId;
1048 DoLine_ColorPicker(pResetId: &s_UiColorResetId, LineSize: 25.0f, LabelSize: 13.0f, BottomMargin: 2.0f, pMainRect: &MainView, pText: Localize(pStr: "UI Color"), pColorValue: &g_Config.m_UiColor, DefaultColor: color_cast<ColorRGBA>(hsl: ColorHSLA(0xE4A046AFU, true)), CheckBoxSpacing: false, pCheckBoxValue: nullptr, Alpha: true);
1049
1050 // Backend list
1051 struct SMenuBackendInfo
1052 {
1053 int m_Major = 0;
1054 int m_Minor = 0;
1055 int m_Patch = 0;
1056 const char *m_pBackendName = "";
1057 bool m_Found = false;
1058 };
1059 std::array<std::array<SMenuBackendInfo, EGraphicsDriverAgeType::GRAPHICS_DRIVER_AGE_TYPE_COUNT>, EBackendType::BACKEND_TYPE_COUNT> aaSupportedBackends{};
1060 uint32_t FoundBackendCount = 0;
1061 for(uint32_t i = 0; i < BACKEND_TYPE_COUNT; ++i)
1062 {
1063 if(EBackendType(i) == BACKEND_TYPE_AUTO)
1064 continue;
1065 for(uint32_t n = 0; n < GRAPHICS_DRIVER_AGE_TYPE_COUNT; ++n)
1066 {
1067 auto &Info = aaSupportedBackends[i][n];
1068 if(Graphics()->GetDriverVersion(DriverAgeType: EGraphicsDriverAgeType(n), Major&: Info.m_Major, Minor&: Info.m_Minor, Patch&: Info.m_Patch, pName&: Info.m_pBackendName, BackendType: EBackendType(i)))
1069 {
1070 // don't count blocked opengl drivers
1071 if(EBackendType(i) != BACKEND_TYPE_OPENGL || EGraphicsDriverAgeType(n) == GRAPHICS_DRIVER_AGE_TYPE_LEGACY || g_Config.m_GfxDriverIsBlocked == 0)
1072 {
1073 Info.m_Found = true;
1074 ++FoundBackendCount;
1075 }
1076 }
1077 }
1078 }
1079
1080 if(FoundBackendCount > 1)
1081 {
1082 CUIRect Text, BackendDropDown;
1083 MainView.HSplitTop(Cut: 10.0f, pTop: nullptr, pBottom: &MainView);
1084 MainView.HSplitTop(Cut: 20.0f, pTop: &Text, pBottom: &MainView);
1085 MainView.HSplitTop(Cut: 2.0f, pTop: nullptr, pBottom: &MainView);
1086 MainView.HSplitTop(Cut: 20.0f, pTop: &BackendDropDown, pBottom: &MainView);
1087 Ui()->DoLabel(pRect: &Text, pText: Localize(pStr: "Renderer"), Size: 16.0f, Align: TEXTALIGN_MC);
1088
1089 static std::vector<std::string> s_vBackendIdNames;
1090 static std::vector<const char *> s_vpBackendIdNamesCStr;
1091 static std::vector<SMenuBackendInfo> s_vBackendInfos;
1092
1093 size_t BackendCount = FoundBackendCount + 1;
1094 s_vBackendIdNames.resize(new_size: BackendCount);
1095 s_vpBackendIdNamesCStr.resize(new_size: BackendCount);
1096 s_vBackendInfos.resize(new_size: BackendCount);
1097
1098 char aTmpBackendName[256];
1099
1100 auto IsInfoDefault = [](const SMenuBackendInfo &CheckInfo) {
1101 return str_comp_nocase(a: CheckInfo.m_pBackendName, b: CConfig::ms_pGfxBackend) == 0 && CheckInfo.m_Major == CConfig::ms_GfxGLMajor && CheckInfo.m_Minor == CConfig::ms_GfxGLMinor && CheckInfo.m_Patch == CConfig::ms_GfxGLPatch;
1102 };
1103
1104 int OldSelectedBackend = -1;
1105 uint32_t CurCounter = 0;
1106 for(uint32_t i = 0; i < BACKEND_TYPE_COUNT; ++i)
1107 {
1108 for(uint32_t n = 0; n < GRAPHICS_DRIVER_AGE_TYPE_COUNT; ++n)
1109 {
1110 auto &Info = aaSupportedBackends[i][n];
1111 if(Info.m_Found)
1112 {
1113 bool IsDefault = IsInfoDefault(Info);
1114 str_format(buffer: aTmpBackendName, buffer_size: sizeof(aTmpBackendName), format: "%s (%d.%d.%d)%s%s", Info.m_pBackendName, Info.m_Major, Info.m_Minor, Info.m_Patch, IsDefault ? " - " : "", IsDefault ? Localize(pStr: "default") : "");
1115 s_vBackendIdNames[CurCounter] = aTmpBackendName;
1116 s_vpBackendIdNamesCStr[CurCounter] = s_vBackendIdNames[CurCounter].c_str();
1117 if(str_comp_nocase(a: Info.m_pBackendName, b: g_Config.m_GfxBackend) == 0 && g_Config.m_GfxGLMajor == Info.m_Major && g_Config.m_GfxGLMinor == Info.m_Minor && g_Config.m_GfxGLPatch == Info.m_Patch)
1118 {
1119 OldSelectedBackend = CurCounter;
1120 }
1121
1122 s_vBackendInfos[CurCounter] = Info;
1123 ++CurCounter;
1124 }
1125 }
1126 }
1127
1128 if(OldSelectedBackend != -1)
1129 {
1130 // no custom selected
1131 BackendCount -= 1;
1132 }
1133 else
1134 {
1135 // custom selected one
1136 str_format(buffer: aTmpBackendName, buffer_size: sizeof(aTmpBackendName), format: "%s (%s %d.%d.%d)", Localize(pStr: "custom"), g_Config.m_GfxBackend, g_Config.m_GfxGLMajor, g_Config.m_GfxGLMinor, g_Config.m_GfxGLPatch);
1137 s_vBackendIdNames[CurCounter] = aTmpBackendName;
1138 s_vpBackendIdNamesCStr[CurCounter] = s_vBackendIdNames[CurCounter].c_str();
1139 OldSelectedBackend = CurCounter;
1140
1141 s_vBackendInfos[CurCounter].m_pBackendName = "custom";
1142 s_vBackendInfos[CurCounter].m_Major = g_Config.m_GfxGLMajor;
1143 s_vBackendInfos[CurCounter].m_Minor = g_Config.m_GfxGLMinor;
1144 s_vBackendInfos[CurCounter].m_Patch = g_Config.m_GfxGLPatch;
1145 }
1146
1147 static int s_OldSelectedBackend = -1;
1148 if(s_OldSelectedBackend == -1)
1149 s_OldSelectedBackend = OldSelectedBackend;
1150
1151 static CUi::SDropDownState s_BackendDropDownState;
1152 static CScrollRegion s_BackendDropDownScrollRegion;
1153 s_BackendDropDownState.m_SelectionPopupContext.m_pScrollRegion = &s_BackendDropDownScrollRegion;
1154 const int NewBackend = Ui()->DoDropDown(pRect: &BackendDropDown, CurSelection: OldSelectedBackend, pStrs: s_vpBackendIdNamesCStr.data(), Num: BackendCount, State&: s_BackendDropDownState);
1155 if(OldSelectedBackend != NewBackend)
1156 {
1157 str_copy(dst&: g_Config.m_GfxBackend, src: s_vBackendInfos[NewBackend].m_pBackendName);
1158 g_Config.m_GfxGLMajor = s_vBackendInfos[NewBackend].m_Major;
1159 g_Config.m_GfxGLMinor = s_vBackendInfos[NewBackend].m_Minor;
1160 g_Config.m_GfxGLPatch = s_vBackendInfos[NewBackend].m_Patch;
1161
1162 CheckSettings = true;
1163 s_GfxBackendChanged = s_OldSelectedBackend != NewBackend;
1164 }
1165 }
1166
1167 // GPU list
1168 const auto &GpuList = Graphics()->GetGpus();
1169 if(GpuList.m_vGpus.size() > 1)
1170 {
1171 CUIRect Text, GpuDropDown;
1172 MainView.HSplitTop(Cut: 10.0f, pTop: nullptr, pBottom: &MainView);
1173 MainView.HSplitTop(Cut: 20.0f, pTop: &Text, pBottom: &MainView);
1174 MainView.HSplitTop(Cut: 2.0f, pTop: nullptr, pBottom: &MainView);
1175 MainView.HSplitTop(Cut: 20.0f, pTop: &GpuDropDown, pBottom: &MainView);
1176 Ui()->DoLabel(pRect: &Text, pText: Localize(pStr: "Graphics card"), Size: 16.0f, Align: TEXTALIGN_MC);
1177
1178 static std::vector<const char *> s_vpGpuIdNames;
1179
1180 size_t GpuCount = GpuList.m_vGpus.size() + 1;
1181 s_vpGpuIdNames.resize(new_size: GpuCount);
1182
1183 char aCurDeviceName[256 + 4];
1184
1185 int OldSelectedGpu = -1;
1186 for(size_t i = 0; i < GpuCount; ++i)
1187 {
1188 if(i == 0)
1189 {
1190 str_format(buffer: aCurDeviceName, buffer_size: sizeof(aCurDeviceName), format: "%s (%s)", Localize(pStr: "auto"), GpuList.m_AutoGpu.m_aName);
1191 s_vpGpuIdNames[i] = aCurDeviceName;
1192 if(str_comp(a: "auto", b: g_Config.m_GfxGpuName) == 0)
1193 {
1194 OldSelectedGpu = 0;
1195 }
1196 }
1197 else
1198 {
1199 s_vpGpuIdNames[i] = GpuList.m_vGpus[i - 1].m_aName;
1200 if(str_comp(a: GpuList.m_vGpus[i - 1].m_aName, b: g_Config.m_GfxGpuName) == 0)
1201 {
1202 OldSelectedGpu = i;
1203 }
1204 }
1205 }
1206
1207 static int s_OldSelectedGpu = -1;
1208 if(s_OldSelectedGpu == -1)
1209 s_OldSelectedGpu = OldSelectedGpu;
1210
1211 static CUi::SDropDownState s_GpuDropDownState;
1212 static CScrollRegion s_GpuDropDownScrollRegion;
1213 s_GpuDropDownState.m_SelectionPopupContext.m_pScrollRegion = &s_GpuDropDownScrollRegion;
1214 const int NewGpu = Ui()->DoDropDown(pRect: &GpuDropDown, CurSelection: OldSelectedGpu, pStrs: s_vpGpuIdNames.data(), Num: GpuCount, State&: s_GpuDropDownState);
1215 if(OldSelectedGpu != NewGpu)
1216 {
1217 if(NewGpu == 0)
1218 str_copy(dst&: g_Config.m_GfxGpuName, src: "auto");
1219 else
1220 str_copy(dst&: g_Config.m_GfxGpuName, src: GpuList.m_vGpus[NewGpu - 1].m_aName);
1221 CheckSettings = true;
1222 s_GfxGpuChanged = NewGpu != s_OldSelectedGpu;
1223 }
1224 }
1225
1226 // check if the new settings require a restart
1227 if(CheckSettings)
1228 {
1229 m_NeedRestartGraphics = !(s_GfxFsaaSamples == g_Config.m_GfxFsaaSamples &&
1230 !s_GfxBackendChanged &&
1231 !s_GfxGpuChanged);
1232 }
1233}
1234
1235void CMenus::RenderSettingsSound(CUIRect MainView)
1236{
1237 static int s_SndEnable = g_Config.m_SndEnable;
1238
1239 CUIRect Button;
1240 MainView.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MainView);
1241 if(DoButton_CheckBox(pId: &g_Config.m_SndEnable, pText: Localize(pStr: "Use sounds"), Checked: g_Config.m_SndEnable, pRect: &Button))
1242 {
1243 g_Config.m_SndEnable ^= 1;
1244 UpdateMusicState();
1245 m_NeedRestartSound = g_Config.m_SndEnable && !s_SndEnable;
1246 }
1247
1248 if(!g_Config.m_SndEnable)
1249 return;
1250
1251 MainView.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MainView);
1252 if(DoButton_CheckBox(pId: &g_Config.m_SndMusic, pText: Localize(pStr: "Play background music"), Checked: g_Config.m_SndMusic, pRect: &Button))
1253 {
1254 g_Config.m_SndMusic ^= 1;
1255 UpdateMusicState();
1256 }
1257
1258 MainView.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MainView);
1259 if(DoButton_CheckBox(pId: &g_Config.m_SndNonactiveMute, pText: Localize(pStr: "Mute when not active"), Checked: g_Config.m_SndNonactiveMute, pRect: &Button))
1260 g_Config.m_SndNonactiveMute ^= 1;
1261
1262 MainView.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MainView);
1263 if(DoButton_CheckBox(pId: &g_Config.m_SndGame, pText: Localize(pStr: "Enable game sounds"), Checked: g_Config.m_SndGame, pRect: &Button))
1264 g_Config.m_SndGame ^= 1;
1265
1266 MainView.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MainView);
1267 if(DoButton_CheckBox(pId: &g_Config.m_SndGun, pText: Localize(pStr: "Enable gun sound"), Checked: g_Config.m_SndGun, pRect: &Button))
1268 g_Config.m_SndGun ^= 1;
1269
1270 MainView.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MainView);
1271 if(DoButton_CheckBox(pId: &g_Config.m_SndLongPain, pText: Localize(pStr: "Enable long pain sound (used when shooting in freeze)"), Checked: g_Config.m_SndLongPain, pRect: &Button))
1272 g_Config.m_SndLongPain ^= 1;
1273
1274 MainView.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MainView);
1275 if(DoButton_CheckBox(pId: &g_Config.m_SndServerMessage, pText: Localize(pStr: "Enable server message sound"), Checked: g_Config.m_SndServerMessage, pRect: &Button))
1276 g_Config.m_SndServerMessage ^= 1;
1277
1278 MainView.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MainView);
1279 if(DoButton_CheckBox(pId: &g_Config.m_SndChat, pText: Localize(pStr: "Enable regular chat sound"), Checked: g_Config.m_SndChat, pRect: &Button))
1280 g_Config.m_SndChat ^= 1;
1281
1282 MainView.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MainView);
1283 if(DoButton_CheckBox(pId: &g_Config.m_SndTeamChat, pText: Localize(pStr: "Enable team chat sound"), Checked: g_Config.m_SndTeamChat, pRect: &Button))
1284 g_Config.m_SndTeamChat ^= 1;
1285
1286 MainView.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MainView);
1287 if(DoButton_CheckBox(pId: &g_Config.m_SndHighlight, pText: Localize(pStr: "Enable highlighted chat sound"), Checked: g_Config.m_SndHighlight, pRect: &Button))
1288 g_Config.m_SndHighlight ^= 1;
1289
1290 // volume slider
1291 {
1292 MainView.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &MainView);
1293 MainView.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MainView);
1294 Ui()->DoScrollbarOption(pId: &g_Config.m_SndVolume, pOption: &g_Config.m_SndVolume, pRect: &Button, pStr: Localize(pStr: "Sound volume"), Min: 0, Max: 100, pScale: &CUi::ms_LogarithmicScrollbarScale, Flags: 0u, pSuffix: "%");
1295 }
1296
1297 // volume slider game sounds
1298 {
1299 MainView.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &MainView);
1300 MainView.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MainView);
1301 Ui()->DoScrollbarOption(pId: &g_Config.m_SndGameVolume, pOption: &g_Config.m_SndGameVolume, pRect: &Button, pStr: Localize(pStr: "Game sound volume"), Min: 0, Max: 100, pScale: &CUi::ms_LogarithmicScrollbarScale, Flags: 0u, pSuffix: "%");
1302 }
1303
1304 // volume slider gui sounds
1305 {
1306 MainView.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &MainView);
1307 MainView.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MainView);
1308 Ui()->DoScrollbarOption(pId: &g_Config.m_SndChatVolume, pOption: &g_Config.m_SndChatVolume, pRect: &Button, pStr: Localize(pStr: "Chat sound volume"), Min: 0, Max: 100, pScale: &CUi::ms_LogarithmicScrollbarScale, Flags: 0u, pSuffix: "%");
1309 }
1310
1311 // volume slider map sounds
1312 {
1313 MainView.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &MainView);
1314 MainView.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MainView);
1315 Ui()->DoScrollbarOption(pId: &g_Config.m_SndMapVolume, pOption: &g_Config.m_SndMapVolume, pRect: &Button, pStr: Localize(pStr: "Map sound volume"), Min: 0, Max: 100, pScale: &CUi::ms_LogarithmicScrollbarScale, Flags: 0u, pSuffix: "%");
1316 }
1317
1318 // volume slider background music
1319 {
1320 MainView.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &MainView);
1321 MainView.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MainView);
1322 Ui()->DoScrollbarOption(pId: &g_Config.m_SndBackgroundMusicVolume, pOption: &g_Config.m_SndBackgroundMusicVolume, pRect: &Button, pStr: Localize(pStr: "Background music volume"), Min: 0, Max: 100, pScale: &CUi::ms_LogarithmicScrollbarScale, Flags: 0u, pSuffix: "%");
1323 }
1324}
1325
1326void CMenus::RenderLanguageSettings(CUIRect MainView)
1327{
1328 const float CreditsFontSize = 14.0f;
1329 const float CreditsMargin = 10.0f;
1330
1331 CUIRect List, CreditsScroll;
1332 MainView.HSplitBottom(Cut: 4.0f * CreditsFontSize + 2.0f * CreditsMargin + CScrollRegion::HEIGHT_MAGIC_FIX, pTop: &List, pBottom: &CreditsScroll);
1333 List.HSplitBottom(Cut: 5.0f, pTop: &List, pBottom: nullptr);
1334
1335 RenderLanguageSelection(MainView: List);
1336
1337 CreditsScroll.Draw(Color: ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f), Corners: IGraphics::CORNER_ALL, Rounding: 5.0f);
1338
1339 static CScrollRegion s_CreditsScrollRegion;
1340 vec2 ScrollOffset(0.0f, 0.0f);
1341 CScrollRegionParams ScrollParams;
1342 ScrollParams.m_ScrollUnit = CreditsFontSize;
1343 s_CreditsScrollRegion.Begin(pClipRect: &CreditsScroll, pOutOffset: &ScrollOffset, pParams: &ScrollParams);
1344 CreditsScroll.y += ScrollOffset.y;
1345
1346 CTextCursor Cursor;
1347 Cursor.m_FontSize = CreditsFontSize;
1348 Cursor.m_LineWidth = CreditsScroll.w - 2.0f * CreditsMargin;
1349
1350 const unsigned OldRenderFlags = TextRender()->GetRenderFlags();
1351 TextRender()->SetRenderFlags(OldRenderFlags | TEXT_RENDER_FLAG_ONE_TIME_USE);
1352 STextContainerIndex CreditsTextContainer;
1353 TextRender()->CreateTextContainer(TextContainerIndex&: CreditsTextContainer, pCursor: &Cursor, pText: Localize(pStr: "English translation by the DDNet Team", pContext: "Translation credits: Add your own name here when you update translations"));
1354 TextRender()->SetRenderFlags(OldRenderFlags);
1355 if(CreditsTextContainer.Valid())
1356 {
1357 CUIRect CreditsLabel;
1358 CreditsScroll.HSplitTop(Cut: TextRender()->GetBoundingBoxTextContainer(TextContainerIndex: CreditsTextContainer).m_H + 2.0f * CreditsMargin, pTop: &CreditsLabel, pBottom: &CreditsScroll);
1359 s_CreditsScrollRegion.AddRect(Rect: CreditsLabel);
1360 CreditsLabel.Margin(Cut: CreditsMargin, pOtherRect: &CreditsLabel);
1361 TextRender()->RenderTextContainer(TextContainerIndex: CreditsTextContainer, TextColor: TextRender()->DefaultTextColor(), TextOutlineColor: TextRender()->DefaultTextOutlineColor(), X: CreditsLabel.x, Y: CreditsLabel.y);
1362 TextRender()->DeleteTextContainer(TextContainerIndex&: CreditsTextContainer);
1363 }
1364
1365 s_CreditsScrollRegion.End();
1366}
1367
1368bool CMenus::RenderLanguageSelection(CUIRect MainView)
1369{
1370 static int s_SelectedLanguage = -2; // -2 = unloaded, -1 = unset
1371 static CListBox s_ListBox;
1372
1373 if(s_SelectedLanguage == -2)
1374 {
1375 s_SelectedLanguage = -1;
1376 for(size_t i = 0; i < g_Localization.Languages().size(); i++)
1377 {
1378 if(str_comp(a: g_Localization.Languages()[i].m_Filename.c_str(), b: g_Config.m_ClLanguagefile) == 0)
1379 {
1380 s_SelectedLanguage = i;
1381 s_ListBox.ScrollToSelected();
1382 break;
1383 }
1384 }
1385 }
1386
1387 const int OldSelected = s_SelectedLanguage;
1388
1389 s_ListBox.DoStart(RowHeight: 24.0f, NumItems: g_Localization.Languages().size(), ItemsPerRow: 1, RowsPerScroll: 3, SelectedIndex: s_SelectedLanguage, pRect: &MainView);
1390
1391 for(const auto &Language : g_Localization.Languages())
1392 {
1393 const CListboxItem Item = s_ListBox.DoNextItem(pId: &Language.m_Name, Selected: s_SelectedLanguage != -1 && !str_comp(a: g_Localization.Languages()[s_SelectedLanguage].m_Name.c_str(), b: Language.m_Name.c_str()));
1394 if(!Item.m_Visible)
1395 continue;
1396
1397 CUIRect FlagRect, Label;
1398 Item.m_Rect.VSplitLeft(Cut: Item.m_Rect.h * 2.0f, pLeft: &FlagRect, pRight: &Label);
1399 FlagRect.VMargin(Cut: 6.0f, pOtherRect: &FlagRect);
1400 FlagRect.HMargin(Cut: 3.0f, pOtherRect: &FlagRect);
1401 GameClient()->m_CountryFlags.Render(CountryCode: Language.m_CountryCode, Color: ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f), x: FlagRect.x, y: FlagRect.y, w: FlagRect.w, h: FlagRect.h);
1402
1403 Ui()->DoLabel(pRect: &Label, pText: Language.m_Name.c_str(), Size: 16.0f, Align: TEXTALIGN_ML);
1404 }
1405
1406 s_SelectedLanguage = s_ListBox.DoEnd();
1407
1408 if(OldSelected != s_SelectedLanguage)
1409 {
1410 str_copy(dst&: g_Config.m_ClLanguagefile, src: g_Localization.Languages()[s_SelectedLanguage].m_Filename.c_str());
1411 GameClient()->OnLanguageChange();
1412 }
1413
1414 return s_ListBox.WasItemActivated();
1415}
1416
1417void CMenus::RenderSettings(CUIRect MainView)
1418{
1419 // render background
1420 CUIRect Button, TabBar, RestartBar;
1421 MainView.VSplitRight(Cut: 120.0f, pLeft: &MainView, pRight: &TabBar);
1422 MainView.Draw(Color: ms_ColorTabbarActive, Corners: IGraphics::CORNER_B, Rounding: 10.0f);
1423 MainView.Margin(Cut: 20.0f, pOtherRect: &MainView);
1424
1425 const bool NeedRestart = m_NeedRestartGraphics || m_NeedRestartSound || m_NeedRestartUpdate;
1426 if(NeedRestart)
1427 {
1428 MainView.HSplitBottom(Cut: 20.0f, pTop: &MainView, pBottom: &RestartBar);
1429 MainView.HSplitBottom(Cut: 10.0f, pTop: &MainView, pBottom: nullptr);
1430 }
1431
1432 TabBar.HSplitTop(Cut: 50.0f, pTop: &Button, pBottom: &TabBar);
1433 Button.Draw(Color: ms_ColorTabbarActive, Corners: IGraphics::CORNER_BR, Rounding: 10.0f);
1434
1435 const char *apTabs[SETTINGS_LENGTH] = {
1436 Localize(pStr: "Language"),
1437 Localize(pStr: "General"),
1438 Localize(pStr: "Player"),
1439 Client()->IsSixup() ? "Tee 0.7" : Localize(pStr: "Tee"),
1440 Localize(pStr: "Appearance"),
1441 Localize(pStr: "Controls"),
1442 Localize(pStr: "Graphics"),
1443 Localize(pStr: "Sound"),
1444 Localize(pStr: "DDNet"),
1445 Localize(pStr: "Assets")};
1446 static CButtonContainer s_aTabButtons[SETTINGS_LENGTH];
1447
1448 for(int i = 0; i < SETTINGS_LENGTH; i++)
1449 {
1450 TabBar.HSplitTop(Cut: 10.0f, pTop: nullptr, pBottom: &TabBar);
1451 TabBar.HSplitTop(Cut: 26.0f, pTop: &Button, pBottom: &TabBar);
1452 if(DoButton_MenuTab(pButtonContainer: &s_aTabButtons[i], pText: apTabs[i], Checked: g_Config.m_UiSettingsPage == i, pRect: &Button, Corners: IGraphics::CORNER_R, pAnimator: &m_aAnimatorsSettingsTab[i]))
1453 g_Config.m_UiSettingsPage = i;
1454 }
1455
1456 if(g_Config.m_UiSettingsPage == SETTINGS_LANGUAGE)
1457 {
1458 GameClient()->m_MenuBackground.ChangePosition(PositionNumber: CMenuBackground::POS_SETTINGS_LANGUAGE);
1459 RenderLanguageSettings(MainView);
1460 }
1461 else if(g_Config.m_UiSettingsPage == SETTINGS_GENERAL)
1462 {
1463 GameClient()->m_MenuBackground.ChangePosition(PositionNumber: CMenuBackground::POS_SETTINGS_GENERAL);
1464 RenderSettingsGeneral(MainView);
1465 }
1466 else if(g_Config.m_UiSettingsPage == SETTINGS_PLAYER)
1467 {
1468 GameClient()->m_MenuBackground.ChangePosition(PositionNumber: CMenuBackground::POS_SETTINGS_PLAYER);
1469 RenderSettingsPlayer(MainView);
1470 }
1471 else if(g_Config.m_UiSettingsPage == SETTINGS_TEE)
1472 {
1473 GameClient()->m_MenuBackground.ChangePosition(PositionNumber: CMenuBackground::POS_SETTINGS_TEE);
1474 if(Client()->IsSixup())
1475 RenderSettingsTee7(MainView);
1476 else
1477 RenderSettingsTee(MainView);
1478 }
1479 else if(g_Config.m_UiSettingsPage == SETTINGS_APPEARANCE)
1480 {
1481 GameClient()->m_MenuBackground.ChangePosition(PositionNumber: CMenuBackground::POS_SETTINGS_APPEARANCE);
1482 RenderSettingsAppearance(MainView);
1483 }
1484 else if(g_Config.m_UiSettingsPage == SETTINGS_CONTROLS)
1485 {
1486 GameClient()->m_MenuBackground.ChangePosition(PositionNumber: CMenuBackground::POS_SETTINGS_CONTROLS);
1487 m_MenusSettingsControls.Render(MainView);
1488 }
1489 else if(g_Config.m_UiSettingsPage == SETTINGS_GRAPHICS)
1490 {
1491 GameClient()->m_MenuBackground.ChangePosition(PositionNumber: CMenuBackground::POS_SETTINGS_GRAPHICS);
1492 RenderSettingsGraphics(MainView);
1493 }
1494 else if(g_Config.m_UiSettingsPage == SETTINGS_SOUND)
1495 {
1496 GameClient()->m_MenuBackground.ChangePosition(PositionNumber: CMenuBackground::POS_SETTINGS_SOUND);
1497 RenderSettingsSound(MainView);
1498 }
1499 else if(g_Config.m_UiSettingsPage == SETTINGS_DDNET)
1500 {
1501 GameClient()->m_MenuBackground.ChangePosition(PositionNumber: CMenuBackground::POS_SETTINGS_DDNET);
1502 RenderSettingsDDNet(MainView);
1503 }
1504 else if(g_Config.m_UiSettingsPage == SETTINGS_ASSETS)
1505 {
1506 GameClient()->m_MenuBackground.ChangePosition(PositionNumber: CMenuBackground::POS_SETTINGS_ASSETS);
1507 RenderSettingsCustom(MainView);
1508 }
1509 else
1510 {
1511 dbg_assert_failed("ui_settings_page invalid");
1512 }
1513
1514 if(NeedRestart)
1515 {
1516 CUIRect RestartWarning, RestartButton;
1517 RestartBar.VSplitRight(Cut: 125.0f, pLeft: &RestartWarning, pRight: &RestartButton);
1518 RestartWarning.VSplitRight(Cut: 10.0f, pLeft: &RestartWarning, pRight: nullptr);
1519 if(m_NeedRestartUpdate)
1520 {
1521 TextRender()->TextColor(r: 1.0f, g: 0.4f, b: 0.4f, a: 1.0f);
1522 Ui()->DoLabel(pRect: &RestartWarning, pText: Localize(pStr: "DDNet Client needs to be restarted to complete update!"), Size: 14.0f, Align: TEXTALIGN_ML);
1523 TextRender()->TextColor(r: 1.0f, g: 1.0f, b: 1.0f, a: 1.0f);
1524 }
1525 else
1526 {
1527 Ui()->DoLabel(pRect: &RestartWarning, pText: Localize(pStr: "You must restart the game for all settings to take effect."), Size: 14.0f, Align: TEXTALIGN_ML);
1528 }
1529
1530 static CButtonContainer s_RestartButton;
1531 if(DoButton_Menu(pButtonContainer: &s_RestartButton, pText: Localize(pStr: "Restart"), Checked: 0, pRect: &RestartButton))
1532 {
1533 if(Client()->State() == IClient::STATE_ONLINE || GameClient()->Editor()->HasUnsavedData())
1534 {
1535 m_Popup = POPUP_RESTART;
1536 }
1537 else
1538 {
1539 Client()->Restart();
1540 }
1541 }
1542 }
1543}
1544
1545bool CMenus::RenderHslaScrollbars(CUIRect *pRect, unsigned int *pColor, bool Alpha, float DarkestLight)
1546{
1547 const unsigned PrevPackedColor = *pColor;
1548 ColorHSLA Color(*pColor, Alpha);
1549 const ColorHSLA OriginalColor = Color;
1550 const char *apLabels[] = {Localize(pStr: "Hue"), Localize(pStr: "Sat."), Localize(pStr: "Lht."), Localize(pStr: "Alpha")};
1551 const float SizePerEntry = 20.0f;
1552 const float MarginPerEntry = 5.0f;
1553 const float PreviewMargin = 2.5f;
1554 const float PreviewHeight = 40.0f + 2 * PreviewMargin;
1555 const float OffY = (SizePerEntry + MarginPerEntry) * (3 + (Alpha ? 1 : 0)) - PreviewHeight;
1556
1557 CUIRect Preview;
1558 pRect->VSplitLeft(Cut: PreviewHeight, pLeft: &Preview, pRight: pRect);
1559 Preview.HSplitTop(Cut: OffY / 2.0f, pTop: nullptr, pBottom: &Preview);
1560 Preview.HSplitTop(Cut: PreviewHeight, pTop: &Preview, pBottom: nullptr);
1561
1562 Preview.Draw(Color: ColorRGBA(0.15f, 0.15f, 0.15f, 1.0f), Corners: IGraphics::CORNER_ALL, Rounding: 4.0f + PreviewMargin);
1563 Preview.Margin(Cut: PreviewMargin, pOtherRect: &Preview);
1564 Preview.Draw(Color: color_cast<ColorRGBA>(hsl: Color.UnclampLighting(Darkest: DarkestLight)), Corners: IGraphics::CORNER_ALL, Rounding: 4.0f + PreviewMargin);
1565
1566 auto &&RenderHueRect = [&](CUIRect *pColorRect) {
1567 float CurXOff = pColorRect->x;
1568 const float SizeColor = pColorRect->w / 6;
1569
1570 // red to yellow
1571 {
1572 IGraphics::CColorVertex aColorVertices[] = {
1573 IGraphics::CColorVertex(0, 1, 0, 0, 1),
1574 IGraphics::CColorVertex(1, 1, 1, 0, 1),
1575 IGraphics::CColorVertex(2, 1, 0, 0, 1),
1576 IGraphics::CColorVertex(3, 1, 1, 0, 1)};
1577 Graphics()->SetColorVertex(pArray: aColorVertices, Num: std::size(aColorVertices));
1578
1579 IGraphics::CFreeformItem Freeform(
1580 CurXOff, pColorRect->y,
1581 CurXOff + SizeColor, pColorRect->y,
1582 CurXOff, pColorRect->y + pColorRect->h,
1583 CurXOff + SizeColor, pColorRect->y + pColorRect->h);
1584 Graphics()->QuadsDrawFreeform(pArray: &Freeform, Num: 1);
1585 }
1586
1587 // yellow to green
1588 CurXOff += SizeColor;
1589 {
1590 IGraphics::CColorVertex aColorVertices[] = {
1591 IGraphics::CColorVertex(0, 1, 1, 0, 1),
1592 IGraphics::CColorVertex(1, 0, 1, 0, 1),
1593 IGraphics::CColorVertex(2, 1, 1, 0, 1),
1594 IGraphics::CColorVertex(3, 0, 1, 0, 1)};
1595 Graphics()->SetColorVertex(pArray: aColorVertices, Num: std::size(aColorVertices));
1596
1597 IGraphics::CFreeformItem Freeform(
1598 CurXOff, pColorRect->y,
1599 CurXOff + SizeColor, pColorRect->y,
1600 CurXOff, pColorRect->y + pColorRect->h,
1601 CurXOff + SizeColor, pColorRect->y + pColorRect->h);
1602 Graphics()->QuadsDrawFreeform(pArray: &Freeform, Num: 1);
1603 }
1604
1605 CurXOff += SizeColor;
1606 // green to turquoise
1607 {
1608 IGraphics::CColorVertex aColorVertices[] = {
1609 IGraphics::CColorVertex(0, 0, 1, 0, 1),
1610 IGraphics::CColorVertex(1, 0, 1, 1, 1),
1611 IGraphics::CColorVertex(2, 0, 1, 0, 1),
1612 IGraphics::CColorVertex(3, 0, 1, 1, 1)};
1613 Graphics()->SetColorVertex(pArray: aColorVertices, Num: std::size(aColorVertices));
1614
1615 IGraphics::CFreeformItem Freeform(
1616 CurXOff, pColorRect->y,
1617 CurXOff + SizeColor, pColorRect->y,
1618 CurXOff, pColorRect->y + pColorRect->h,
1619 CurXOff + SizeColor, pColorRect->y + pColorRect->h);
1620 Graphics()->QuadsDrawFreeform(pArray: &Freeform, Num: 1);
1621 }
1622
1623 CurXOff += SizeColor;
1624 // turquoise to blue
1625 {
1626 IGraphics::CColorVertex aColorVertices[] = {
1627 IGraphics::CColorVertex(0, 0, 1, 1, 1),
1628 IGraphics::CColorVertex(1, 0, 0, 1, 1),
1629 IGraphics::CColorVertex(2, 0, 1, 1, 1),
1630 IGraphics::CColorVertex(3, 0, 0, 1, 1)};
1631 Graphics()->SetColorVertex(pArray: aColorVertices, Num: std::size(aColorVertices));
1632
1633 IGraphics::CFreeformItem Freeform(
1634 CurXOff, pColorRect->y,
1635 CurXOff + SizeColor, pColorRect->y,
1636 CurXOff, pColorRect->y + pColorRect->h,
1637 CurXOff + SizeColor, pColorRect->y + pColorRect->h);
1638 Graphics()->QuadsDrawFreeform(pArray: &Freeform, Num: 1);
1639 }
1640
1641 CurXOff += SizeColor;
1642 // blue to purple
1643 {
1644 IGraphics::CColorVertex aColorVertices[] = {
1645 IGraphics::CColorVertex(0, 0, 0, 1, 1),
1646 IGraphics::CColorVertex(1, 1, 0, 1, 1),
1647 IGraphics::CColorVertex(2, 0, 0, 1, 1),
1648 IGraphics::CColorVertex(3, 1, 0, 1, 1)};
1649 Graphics()->SetColorVertex(pArray: aColorVertices, Num: std::size(aColorVertices));
1650
1651 IGraphics::CFreeformItem Freeform(
1652 CurXOff, pColorRect->y,
1653 CurXOff + SizeColor, pColorRect->y,
1654 CurXOff, pColorRect->y + pColorRect->h,
1655 CurXOff + SizeColor, pColorRect->y + pColorRect->h);
1656 Graphics()->QuadsDrawFreeform(pArray: &Freeform, Num: 1);
1657 }
1658
1659 CurXOff += SizeColor;
1660 // purple to red
1661 {
1662 IGraphics::CColorVertex aColorVertices[] = {
1663 IGraphics::CColorVertex(0, 1, 0, 1, 1),
1664 IGraphics::CColorVertex(1, 1, 0, 0, 1),
1665 IGraphics::CColorVertex(2, 1, 0, 1, 1),
1666 IGraphics::CColorVertex(3, 1, 0, 0, 1)};
1667 Graphics()->SetColorVertex(pArray: aColorVertices, Num: std::size(aColorVertices));
1668
1669 IGraphics::CFreeformItem Freeform(
1670 CurXOff, pColorRect->y,
1671 CurXOff + SizeColor, pColorRect->y,
1672 CurXOff, pColorRect->y + pColorRect->h,
1673 CurXOff + SizeColor, pColorRect->y + pColorRect->h);
1674 Graphics()->QuadsDrawFreeform(pArray: &Freeform, Num: 1);
1675 }
1676 };
1677
1678 auto &&RenderSaturationRect = [&](CUIRect *pColorRect, const ColorRGBA &CurColor) {
1679 ColorHSLA LeftColor = color_cast<ColorHSLA>(rgb: CurColor);
1680 ColorHSLA RightColor = color_cast<ColorHSLA>(rgb: CurColor);
1681
1682 LeftColor.s = 0.0f;
1683 RightColor.s = 1.0f;
1684
1685 const ColorRGBA LeftColorRGBA = color_cast<ColorRGBA>(hsl: LeftColor);
1686 const ColorRGBA RightColorRGBA = color_cast<ColorRGBA>(hsl: RightColor);
1687
1688 Graphics()->SetColor4(TopLeft: LeftColorRGBA, TopRight: RightColorRGBA, BottomLeft: RightColorRGBA, BottomRight: LeftColorRGBA);
1689
1690 IGraphics::CFreeformItem Freeform(
1691 pColorRect->x, pColorRect->y,
1692 pColorRect->x + pColorRect->w, pColorRect->y,
1693 pColorRect->x, pColorRect->y + pColorRect->h,
1694 pColorRect->x + pColorRect->w, pColorRect->y + pColorRect->h);
1695 Graphics()->QuadsDrawFreeform(pArray: &Freeform, Num: 1);
1696 };
1697
1698 auto &&RenderLightingRect = [&](CUIRect *pColorRect, const ColorRGBA &CurColor) {
1699 ColorHSLA LeftColor = color_cast<ColorHSLA>(rgb: CurColor);
1700 ColorHSLA RightColor = color_cast<ColorHSLA>(rgb: CurColor);
1701
1702 LeftColor.l = DarkestLight;
1703 RightColor.l = 1.0f;
1704
1705 const ColorRGBA LeftColorRGBA = color_cast<ColorRGBA>(hsl: LeftColor);
1706 const ColorRGBA RightColorRGBA = color_cast<ColorRGBA>(hsl: RightColor);
1707
1708 Graphics()->SetColor4(TopLeft: LeftColorRGBA, TopRight: RightColorRGBA, BottomLeft: RightColorRGBA, BottomRight: LeftColorRGBA);
1709
1710 IGraphics::CFreeformItem Freeform(
1711 pColorRect->x, pColorRect->y,
1712 pColorRect->x + pColorRect->w, pColorRect->y,
1713 pColorRect->x, pColorRect->y + pColorRect->h,
1714 pColorRect->x + pColorRect->w, pColorRect->y + pColorRect->h);
1715 Graphics()->QuadsDrawFreeform(pArray: &Freeform, Num: 1);
1716 };
1717
1718 auto &&RenderAlphaRect = [&](CUIRect *pColorRect, const ColorRGBA &CurColorFull) {
1719 const ColorRGBA LeftColorRGBA = color_cast<ColorRGBA>(hsl: color_cast<ColorHSLA>(rgb: CurColorFull).WithAlpha(alpha: 0.0f));
1720 const ColorRGBA RightColorRGBA = color_cast<ColorRGBA>(hsl: color_cast<ColorHSLA>(rgb: CurColorFull).WithAlpha(alpha: 1.0f));
1721
1722 Graphics()->SetColor4(TopLeft: LeftColorRGBA, TopRight: RightColorRGBA, BottomLeft: RightColorRGBA, BottomRight: LeftColorRGBA);
1723
1724 IGraphics::CFreeformItem Freeform(
1725 pColorRect->x, pColorRect->y,
1726 pColorRect->x + pColorRect->w, pColorRect->y,
1727 pColorRect->x, pColorRect->y + pColorRect->h,
1728 pColorRect->x + pColorRect->w, pColorRect->y + pColorRect->h);
1729 Graphics()->QuadsDrawFreeform(pArray: &Freeform, Num: 1);
1730 };
1731
1732 for(int i = 0; i < 3 + Alpha; i++)
1733 {
1734 CUIRect Button, Label;
1735 pRect->HSplitTop(Cut: SizePerEntry, pTop: &Button, pBottom: pRect);
1736 pRect->HSplitTop(Cut: MarginPerEntry, pTop: nullptr, pBottom: pRect);
1737 Button.VSplitLeft(Cut: 10.0f, pLeft: nullptr, pRight: &Button);
1738 Button.VSplitLeft(Cut: 100.0f, pLeft: &Label, pRight: &Button);
1739
1740 Button.Draw(Color: ColorRGBA(0.15f, 0.15f, 0.15f, 1.0f), Corners: IGraphics::CORNER_ALL, Rounding: 1.0f);
1741
1742 CUIRect Rail;
1743 Button.Margin(Cut: 2.0f, pOtherRect: &Rail);
1744
1745 char aBuf[32];
1746 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s: %03d", apLabels[i], round_to_int(f: Color[i] * 255.0f));
1747 Ui()->DoLabel(pRect: &Label, pText: aBuf, Size: 14.0f, Align: TEXTALIGN_ML);
1748
1749 ColorRGBA HandleColor;
1750 Graphics()->TextureClear();
1751 Graphics()->TrianglesBegin();
1752 if(i == 0)
1753 {
1754 RenderHueRect(&Rail);
1755 HandleColor = color_cast<ColorRGBA>(hsl: ColorHSLA(Color.h, 1.0f, 0.5f, 1.0f));
1756 }
1757 else if(i == 1)
1758 {
1759 RenderSaturationRect(&Rail, color_cast<ColorRGBA>(hsl: ColorHSLA(Color.h, 1.0f, 0.5f, 1.0f)));
1760 HandleColor = color_cast<ColorRGBA>(hsl: ColorHSLA(Color.h, Color.s, 0.5f, 1.0f));
1761 }
1762 else if(i == 2)
1763 {
1764 RenderLightingRect(&Rail, color_cast<ColorRGBA>(hsl: ColorHSLA(Color.h, Color.s, 0.5f, 1.0f)));
1765 HandleColor = color_cast<ColorRGBA>(hsl: ColorHSLA(Color.h, Color.s, Color.l, 1.0f).UnclampLighting(Darkest: DarkestLight));
1766 }
1767 else if(i == 3)
1768 {
1769 RenderAlphaRect(&Rail, color_cast<ColorRGBA>(hsl: ColorHSLA(Color.h, Color.s, Color.l, 1.0f).UnclampLighting(Darkest: DarkestLight)));
1770 HandleColor = color_cast<ColorRGBA>(hsl: Color.UnclampLighting(Darkest: DarkestLight));
1771 }
1772 Graphics()->TrianglesEnd();
1773
1774 Color[i] = Ui()->DoScrollbarH(pId: &((char *)pColor)[i], pRect: &Button, Current: Color[i], pColorInner: &HandleColor);
1775 }
1776
1777 if(OriginalColor != Color)
1778 {
1779 *pColor = Color.Pack(Alpha);
1780 }
1781 return PrevPackedColor != *pColor;
1782}
1783
1784enum
1785{
1786 APPEARANCE_TAB_HUD = 0,
1787 APPEARANCE_TAB_CHAT = 1,
1788 APPEARANCE_TAB_NAME_PLATE = 2,
1789 APPEARANCE_TAB_HOOK_COLLISION = 3,
1790 APPEARANCE_TAB_INFO_MESSAGES = 4,
1791 APPEARANCE_TAB_LASER = 5,
1792 NUMBER_OF_APPEARANCE_TABS = 6,
1793};
1794
1795void CMenus::RenderSettingsAppearance(CUIRect MainView)
1796{
1797 char aBuf[128];
1798 static int s_CurTab = 0;
1799
1800 CUIRect TabBar, LeftView, RightView, Button;
1801
1802 MainView.HSplitTop(Cut: 20.0f, pTop: &TabBar, pBottom: &MainView);
1803 const float TabWidth = TabBar.w / NUMBER_OF_APPEARANCE_TABS;
1804 static CButtonContainer s_aPageTabs[NUMBER_OF_APPEARANCE_TABS] = {};
1805 const char *apTabNames[NUMBER_OF_APPEARANCE_TABS] = {
1806 Localize(pStr: "HUD"),
1807 Localize(pStr: "Chat"),
1808 Localize(pStr: "Name Plate"),
1809 Localize(pStr: "Hook Collisions"),
1810 Localize(pStr: "Info Messages"),
1811 Localize(pStr: "Laser")};
1812
1813 for(int Tab = APPEARANCE_TAB_HUD; Tab < NUMBER_OF_APPEARANCE_TABS; ++Tab)
1814 {
1815 TabBar.VSplitLeft(Cut: TabWidth, pLeft: &Button, pRight: &TabBar);
1816 const int Corners = Tab == APPEARANCE_TAB_HUD ? IGraphics::CORNER_L : (Tab == NUMBER_OF_APPEARANCE_TABS - 1 ? IGraphics::CORNER_R : IGraphics::CORNER_NONE);
1817 if(DoButton_MenuTab(pButtonContainer: &s_aPageTabs[Tab], pText: apTabNames[Tab], Checked: s_CurTab == Tab, pRect: &Button, Corners, pAnimator: nullptr, pDefaultColor: nullptr, pActiveColor: nullptr, pHoverColor: nullptr, EdgeRounding: 4.0f))
1818 {
1819 s_CurTab = Tab;
1820 }
1821 }
1822
1823 MainView.HSplitTop(Cut: 10.0f, pTop: nullptr, pBottom: &MainView);
1824
1825 const float LineSize = 20.0f;
1826 const float ColorPickerLineSize = 25.0f;
1827 const float HeadlineFontSize = 20.0f;
1828 const float HeadlineHeight = 30.0f;
1829 const float MarginSmall = 5.0f;
1830 const float MarginBetweenViews = 20.0f;
1831
1832 const float ColorPickerLabelSize = 13.0f;
1833 const float ColorPickerLineSpacing = 5.0f;
1834
1835 if(s_CurTab == APPEARANCE_TAB_HUD)
1836 {
1837 MainView.VSplitMid(pLeft: &LeftView, pRight: &RightView, Spacing: MarginBetweenViews);
1838
1839 // ***** HUD ***** //
1840 Ui()->DoLabel_AutoLineSize(pText: Localize(pStr: "HUD"), FontSize: HeadlineFontSize,
1841 Align: TEXTALIGN_ML, pRect: &LeftView, LineSize: HeadlineHeight);
1842 LeftView.HSplitTop(Cut: MarginSmall, pTop: nullptr, pBottom: &LeftView);
1843
1844 // Switch of the entire HUD
1845 DoButton_CheckBoxAutoVMarginAndSet(pId: &g_Config.m_ClShowhud, pText: Localize(pStr: "Show ingame HUD"), pValue: &g_Config.m_ClShowhud, pRect: &LeftView, VMargin: LineSize);
1846
1847 // Switches of the various normal HUD elements
1848 DoButton_CheckBoxAutoVMarginAndSet(pId: &g_Config.m_ClShowhudHealthAmmo, pText: Localize(pStr: "Show health, shields and ammo"), pValue: &g_Config.m_ClShowhudHealthAmmo, pRect: &LeftView, VMargin: LineSize);
1849 DoButton_CheckBoxAutoVMarginAndSet(pId: &g_Config.m_ClShowhudScore, pText: Localize(pStr: "Show score"), pValue: &g_Config.m_ClShowhudScore, pRect: &LeftView, VMargin: LineSize);
1850 DoButton_CheckBoxAutoVMarginAndSet(pId: &g_Config.m_ClShowLocalTimeAlways, pText: Localize(pStr: "Show local time always"), pValue: &g_Config.m_ClShowLocalTimeAlways, pRect: &LeftView, VMargin: LineSize);
1851 DoButton_CheckBoxAutoVMarginAndSet(pId: &g_Config.m_ClSpecCursor, pText: Localize(pStr: "Show spectator cursor"), pValue: &g_Config.m_ClSpecCursor, pRect: &LeftView, VMargin: LineSize);
1852
1853 // Settings of the HUD element for votes
1854 DoButton_CheckBoxAutoVMarginAndSet(pId: &g_Config.m_ClShowVotesAfterVoting, pText: Localize(pStr: "Show votes window after voting"), pValue: &g_Config.m_ClShowVotesAfterVoting, pRect: &LeftView, VMargin: LineSize);
1855
1856 // ***** Scoreboard ***** //
1857 LeftView.HSplitTop(Cut: MarginBetweenViews, pTop: nullptr, pBottom: &LeftView);
1858 Ui()->DoLabel_AutoLineSize(pText: Localize(pStr: "Scoreboard"), FontSize: HeadlineFontSize,
1859 Align: TEXTALIGN_ML, pRect: &LeftView, LineSize: HeadlineHeight);
1860 LeftView.HSplitTop(Cut: MarginSmall, pTop: nullptr, pBottom: &LeftView);
1861
1862 ColorRGBA GreenDefault(0.78f, 1.0f, 0.8f, 1.0f);
1863 static CButtonContainer s_AuthedColor, s_SameClanColor;
1864 DoLine_ColorPicker(pResetId: &s_AuthedColor, LineSize: ColorPickerLineSize, LabelSize: ColorPickerLabelSize, BottomMargin: ColorPickerLineSpacing, pMainRect: &LeftView, pText: Localize(pStr: "Authed name color in scoreboard"), pColorValue: &g_Config.m_ClAuthedPlayerColor, DefaultColor: GreenDefault, CheckBoxSpacing: false);
1865 DoLine_ColorPicker(pResetId: &s_SameClanColor, LineSize: ColorPickerLineSize, LabelSize: ColorPickerLabelSize, BottomMargin: ColorPickerLineSpacing, pMainRect: &LeftView, pText: Localize(pStr: "Same clan color in scoreboard"), pColorValue: &g_Config.m_ClSameClanColor, DefaultColor: GreenDefault, CheckBoxSpacing: false);
1866
1867 // ***** DDRace HUD ***** //
1868 Ui()->DoLabel_AutoLineSize(pText: Localize(pStr: "DDRace HUD"), FontSize: HeadlineFontSize,
1869 Align: TEXTALIGN_ML, pRect: &RightView, LineSize: HeadlineHeight);
1870 RightView.HSplitTop(Cut: MarginSmall, pTop: nullptr, pBottom: &RightView);
1871
1872 // Switches of various DDRace HUD elements
1873 DoButton_CheckBoxAutoVMarginAndSet(pId: &g_Config.m_ClShowIds, pText: Localize(pStr: "Show client IDs (scoreboard, chat, spectator)"), pValue: &g_Config.m_ClShowIds, pRect: &RightView, VMargin: LineSize);
1874 DoButton_CheckBoxAutoVMarginAndSet(pId: &g_Config.m_ClShowhudDDRace, pText: Localize(pStr: "Show DDRace HUD"), pValue: &g_Config.m_ClShowhudDDRace, pRect: &RightView, VMargin: LineSize);
1875 if(g_Config.m_ClShowhudDDRace)
1876 {
1877 DoButton_CheckBoxAutoVMarginAndSet(pId: &g_Config.m_ClShowhudJumpsIndicator, pText: Localize(pStr: "Show jumps indicator"), pValue: &g_Config.m_ClShowhudJumpsIndicator, pRect: &RightView, VMargin: LineSize);
1878 }
1879 else
1880 {
1881 RightView.HSplitTop(Cut: LineSize, pTop: nullptr, pBottom: &RightView); // Create empty space for hidden option
1882 }
1883
1884 // Eye with a number of spectators
1885 DoButton_CheckBoxAutoVMarginAndSet(pId: &g_Config.m_ClShowhudSpectatorCount, pText: Localize(pStr: "Show number of spectators"), pValue: &g_Config.m_ClShowhudSpectatorCount, pRect: &RightView, VMargin: LineSize);
1886
1887 // Switch for dummy actions display
1888 DoButton_CheckBoxAutoVMarginAndSet(pId: &g_Config.m_ClShowhudDummyActions, pText: Localize(pStr: "Show dummy actions"), pValue: &g_Config.m_ClShowhudDummyActions, pRect: &RightView, VMargin: LineSize);
1889
1890 // Player movement information display settings
1891 DoButton_CheckBoxAutoVMarginAndSet(pId: &g_Config.m_ClShowhudPlayerPosition, pText: Localize(pStr: "Show player position"), pValue: &g_Config.m_ClShowhudPlayerPosition, pRect: &RightView, VMargin: LineSize);
1892 DoButton_CheckBoxAutoVMarginAndSet(pId: &g_Config.m_ClShowhudPlayerSpeed, pText: Localize(pStr: "Show player speed"), pValue: &g_Config.m_ClShowhudPlayerSpeed, pRect: &RightView, VMargin: LineSize);
1893 DoButton_CheckBoxAutoVMarginAndSet(pId: &g_Config.m_ClShowhudPlayerAngle, pText: Localize(pStr: "Show player target angle"), pValue: &g_Config.m_ClShowhudPlayerAngle, pRect: &RightView, VMargin: LineSize);
1894
1895 // Freeze bar settings
1896 DoButton_CheckBoxAutoVMarginAndSet(pId: &g_Config.m_ClShowFreezeBars, pText: Localize(pStr: "Show freeze bars"), pValue: &g_Config.m_ClShowFreezeBars, pRect: &RightView, VMargin: LineSize);
1897 RightView.HSplitTop(Cut: LineSize * 2.0f, pTop: &Button, pBottom: &RightView);
1898 if(g_Config.m_ClShowFreezeBars)
1899 {
1900 Ui()->DoScrollbarOption(pId: &g_Config.m_ClFreezeBarsAlphaInsideFreeze, pOption: &g_Config.m_ClFreezeBarsAlphaInsideFreeze, pRect: &Button, pStr: Localize(pStr: "Opacity of freeze bars inside freeze"), Min: 0, Max: 100, pScale: &CUi::ms_LinearScrollbarScale, Flags: CUi::SCROLLBAR_OPTION_MULTILINE, pSuffix: "%");
1901 }
1902 }
1903 else if(s_CurTab == APPEARANCE_TAB_CHAT)
1904 {
1905 CChat &Chat = GameClient()->m_Chat;
1906 CUIRect TopView, PreviewView;
1907 MainView.HSplitBottom(Cut: 220.0f, pTop: &TopView, pBottom: &PreviewView);
1908 TopView.HSplitBottom(Cut: MarginBetweenViews, pTop: &TopView, pBottom: nullptr);
1909 TopView.VSplitMid(pLeft: &LeftView, pRight: &RightView, Spacing: MarginBetweenViews);
1910
1911 // ***** Chat ***** //
1912 Ui()->DoLabel_AutoLineSize(pText: Localize(pStr: "Chat"), FontSize: HeadlineFontSize,
1913 Align: TEXTALIGN_ML, pRect: &LeftView, LineSize: HeadlineHeight);
1914 LeftView.HSplitTop(Cut: MarginSmall, pTop: nullptr, pBottom: &LeftView);
1915
1916 // General chat settings
1917 LeftView.HSplitTop(Cut: LineSize, pTop: &Button, pBottom: &LeftView);
1918 if(DoButton_CheckBox(pId: &g_Config.m_ClShowChat, pText: Localize(pStr: "Show chat"), Checked: g_Config.m_ClShowChat, pRect: &Button))
1919 {
1920 g_Config.m_ClShowChat = g_Config.m_ClShowChat ? 0 : 1;
1921 }
1922 LeftView.HSplitTop(Cut: LineSize, pTop: &Button, pBottom: &LeftView);
1923 if(g_Config.m_ClShowChat)
1924 {
1925 static int s_ShowChat = 0;
1926 if(DoButton_CheckBox(pId: &s_ShowChat, pText: Localize(pStr: "Always show chat"), Checked: g_Config.m_ClShowChat == 2, pRect: &Button))
1927 g_Config.m_ClShowChat = g_Config.m_ClShowChat != 2 ? 2 : 1;
1928 }
1929
1930 DoButton_CheckBoxAutoVMarginAndSet(pId: &g_Config.m_ClChatTeamColors, pText: Localize(pStr: "Show names in chat in team colors"), pValue: &g_Config.m_ClChatTeamColors, pRect: &LeftView, VMargin: LineSize);
1931 DoButton_CheckBoxAutoVMarginAndSet(pId: &g_Config.m_ClShowChatFriends, pText: Localize(pStr: "Show only chat messages from friends"), pValue: &g_Config.m_ClShowChatFriends, pRect: &LeftView, VMargin: LineSize);
1932 DoButton_CheckBoxAutoVMarginAndSet(pId: &g_Config.m_ClShowChatTeamMembersOnly, pText: Localize(pStr: "Show only chat messages from team members"), pValue: &g_Config.m_ClShowChatTeamMembersOnly, pRect: &LeftView, VMargin: LineSize);
1933
1934 if(DoButton_CheckBoxAutoVMarginAndSet(pId: &g_Config.m_ClChatOld, pText: Localize(pStr: "Use old chat style"), pValue: &g_Config.m_ClChatOld, pRect: &LeftView, VMargin: LineSize))
1935 GameClient()->m_Chat.RebuildChat();
1936
1937 // DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClCensorChat, Localize("Censor profanity"), &g_Config.m_ClCensorChat, &LeftView, LineSize);
1938
1939 LeftView.HSplitTop(Cut: LineSize, pTop: &Button, pBottom: &LeftView);
1940 if(Ui()->DoScrollbarOption(pId: &g_Config.m_ClChatFontSize, pOption: &g_Config.m_ClChatFontSize, pRect: &Button, pStr: Localize(pStr: "Chat font size"), Min: 10, Max: 100))
1941 {
1942 Chat.EnsureCoherentWidth();
1943 Chat.RebuildChat();
1944 }
1945
1946 LeftView.HSplitTop(Cut: LineSize, pTop: &Button, pBottom: &LeftView);
1947 if(Ui()->DoScrollbarOption(pId: &g_Config.m_ClChatWidth, pOption: &g_Config.m_ClChatWidth, pRect: &Button, pStr: Localize(pStr: "Chat width"), Min: 120, Max: 400))
1948 {
1949 Chat.EnsureCoherentFontSize();
1950 Chat.RebuildChat();
1951 }
1952
1953 static CButtonContainer s_BackgroundColor;
1954 DoLine_ColorPicker(pResetId: &s_BackgroundColor, LineSize: ColorPickerLineSize, LabelSize: ColorPickerLabelSize, BottomMargin: ColorPickerLineSpacing, pMainRect: &LeftView, pText: Localize(pStr: "Chat background color"), pColorValue: &g_Config.m_ClChatBackgroundColor, DefaultColor: color_cast<ColorRGBA>(hsl: ColorHSLA(CConfig::ms_ClChatBackgroundColor, true)), CheckBoxSpacing: false, pCheckBoxValue: nullptr, Alpha: true);
1955
1956 // ***** Messages ***** //
1957 Ui()->DoLabel_AutoLineSize(pText: Localize(pStr: "Messages"), FontSize: HeadlineFontSize,
1958 Align: TEXTALIGN_ML, pRect: &RightView, LineSize: HeadlineHeight);
1959 RightView.HSplitTop(Cut: MarginSmall, pTop: nullptr, pBottom: &RightView);
1960
1961 // Message Colors and extra settings
1962 static CButtonContainer s_SystemMessageColor;
1963 DoLine_ColorPicker(pResetId: &s_SystemMessageColor, LineSize: ColorPickerLineSize, LabelSize: ColorPickerLabelSize, BottomMargin: ColorPickerLineSpacing, pMainRect: &RightView, pText: Localize(pStr: "System message"), pColorValue: &g_Config.m_ClMessageSystemColor, DefaultColor: ColorRGBA(1.0f, 1.0f, 0.5f), CheckBoxSpacing: true, pCheckBoxValue: &g_Config.m_ClShowChatSystem);
1964 static CButtonContainer s_HighlightedMessageColor;
1965 DoLine_ColorPicker(pResetId: &s_HighlightedMessageColor, LineSize: ColorPickerLineSize, LabelSize: ColorPickerLabelSize, BottomMargin: ColorPickerLineSpacing, pMainRect: &RightView, pText: Localize(pStr: "Highlighted message"), pColorValue: &g_Config.m_ClMessageHighlightColor, DefaultColor: ColorRGBA(1.0f, 0.5f, 0.5f));
1966 static CButtonContainer s_TeamMessageColor;
1967 DoLine_ColorPicker(pResetId: &s_TeamMessageColor, LineSize: ColorPickerLineSize, LabelSize: ColorPickerLabelSize, BottomMargin: ColorPickerLineSpacing, pMainRect: &RightView, pText: Localize(pStr: "Team message"), pColorValue: &g_Config.m_ClMessageTeamColor, DefaultColor: ColorRGBA(0.65f, 1.0f, 0.65f));
1968 static CButtonContainer s_FriendMessageColor;
1969 DoLine_ColorPicker(pResetId: &s_FriendMessageColor, LineSize: ColorPickerLineSize, LabelSize: ColorPickerLabelSize, BottomMargin: ColorPickerLineSpacing, pMainRect: &RightView, pText: Localize(pStr: "Friend message"), pColorValue: &g_Config.m_ClMessageFriendColor, DefaultColor: ColorRGBA(1.0f, 0.137f, 0.137f), CheckBoxSpacing: true, pCheckBoxValue: &g_Config.m_ClMessageFriend);
1970 static CButtonContainer s_NormalMessageColor;
1971 DoLine_ColorPicker(pResetId: &s_NormalMessageColor, LineSize: ColorPickerLineSize, LabelSize: ColorPickerLabelSize, BottomMargin: ColorPickerLineSpacing, pMainRect: &RightView, pText: Localize(pStr: "Normal message"), pColorValue: &g_Config.m_ClMessageColor, DefaultColor: ColorRGBA(1.0f, 1.0f, 1.0f));
1972
1973 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s (echo)", Localize(pStr: "Client message"));
1974 static CButtonContainer s_ClientMessageColor;
1975 DoLine_ColorPicker(pResetId: &s_ClientMessageColor, LineSize: ColorPickerLineSize, LabelSize: ColorPickerLabelSize, BottomMargin: ColorPickerLineSpacing, pMainRect: &RightView, pText: aBuf, pColorValue: &g_Config.m_ClMessageClientColor, DefaultColor: ColorRGBA(0.5f, 0.78f, 1.0f));
1976
1977 // ***** Chat Preview ***** //
1978 Ui()->DoLabel_AutoLineSize(pText: Localize(pStr: "Preview"), FontSize: HeadlineFontSize,
1979 Align: TEXTALIGN_ML, pRect: &PreviewView, LineSize: HeadlineHeight);
1980 PreviewView.HSplitTop(Cut: MarginSmall, pTop: nullptr, pBottom: &PreviewView);
1981
1982 // Use the rest of the view for preview
1983 PreviewView.Draw(Color: ColorRGBA(1, 1, 1, 0.1f), Corners: IGraphics::CORNER_ALL, Rounding: 5.0f);
1984 PreviewView.Margin(Cut: MarginSmall, pOtherRect: &PreviewView);
1985
1986 ColorRGBA SystemColor = color_cast<ColorRGBA, ColorHSLA>(hsl: ColorHSLA(g_Config.m_ClMessageSystemColor));
1987 ColorRGBA HighlightedColor = color_cast<ColorRGBA, ColorHSLA>(hsl: ColorHSLA(g_Config.m_ClMessageHighlightColor));
1988 ColorRGBA TeamColor = color_cast<ColorRGBA, ColorHSLA>(hsl: ColorHSLA(g_Config.m_ClMessageTeamColor));
1989 ColorRGBA FriendColor = color_cast<ColorRGBA, ColorHSLA>(hsl: ColorHSLA(g_Config.m_ClMessageFriendColor));
1990 ColorRGBA NormalColor = color_cast<ColorRGBA, ColorHSLA>(hsl: ColorHSLA(g_Config.m_ClMessageColor));
1991 ColorRGBA ClientColor = color_cast<ColorRGBA, ColorHSLA>(hsl: ColorHSLA(g_Config.m_ClMessageClientColor));
1992 ColorRGBA DefaultNameColor(0.8f, 0.8f, 0.8f, 1.0f);
1993
1994 const float RealFontSize = Chat.FontSize() * 2;
1995 const float RealMsgPaddingX = (!g_Config.m_ClChatOld ? Chat.MessagePaddingX() : 0) * 2;
1996 const float RealMsgPaddingY = (!g_Config.m_ClChatOld ? Chat.MessagePaddingY() : 0) * 2;
1997 const float RealMsgPaddingTee = (!g_Config.m_ClChatOld ? Chat.MessageTeeSize() + CChat::MESSAGE_TEE_PADDING_RIGHT : 0) * 2;
1998 const float RealOffsetY = RealFontSize + RealMsgPaddingY;
1999
2000 const float X = RealMsgPaddingX / 2.0f + PreviewView.x;
2001 float Y = PreviewView.y;
2002 float LineWidth = g_Config.m_ClChatWidth * 2 - (RealMsgPaddingX * 1.5f) - RealMsgPaddingTee;
2003
2004 str_copy(dst&: aBuf, src: Client()->PlayerName());
2005
2006 const CAnimState *pIdleState = CAnimState::GetIdle();
2007 const float RealTeeSize = Chat.MessageTeeSize() * 2;
2008 const float RealTeeSizeHalved = Chat.MessageTeeSize();
2009 constexpr float TWSkinUnreliableOffset = -0.25f;
2010 const float OffsetTeeY = RealTeeSizeHalved;
2011 const float FullHeightMinusTee = RealOffsetY - RealTeeSize;
2012
2013 struct SPreviewLine
2014 {
2015 int m_ClientId;
2016 bool m_Team;
2017 char m_aName[64];
2018 char m_aText[256];
2019 bool m_Friend;
2020 bool m_Player;
2021 bool m_Client;
2022 bool m_Highlighted;
2023 int m_TimesRepeated;
2024
2025 CTeeRenderInfo m_RenderInfo;
2026 };
2027
2028 static std::vector<SPreviewLine> s_vLines;
2029
2030 enum ELineFlag
2031 {
2032 FLAG_TEAM = 1 << 0,
2033 FLAG_FRIEND = 1 << 1,
2034 FLAG_HIGHLIGHT = 1 << 2,
2035 FLAG_CLIENT = 1 << 3
2036 };
2037 enum
2038 {
2039 PREVIEW_SYS,
2040 PREVIEW_HIGHLIGHT,
2041 PREVIEW_TEAM,
2042 PREVIEW_FRIEND,
2043 PREVIEW_SPAMMER,
2044 PREVIEW_CLIENT
2045 };
2046 auto &&SetPreviewLine = [](int Index, int ClientId, const char *pName, const char *pText, int Flag, int Repeats) {
2047 SPreviewLine *pLine;
2048 if((int)s_vLines.size() <= Index)
2049 {
2050 s_vLines.emplace_back();
2051 pLine = &s_vLines.back();
2052 }
2053 else
2054 {
2055 pLine = &s_vLines[Index];
2056 }
2057 pLine->m_ClientId = ClientId;
2058 pLine->m_Team = Flag & FLAG_TEAM;
2059 pLine->m_Friend = Flag & FLAG_FRIEND;
2060 pLine->m_Player = ClientId >= 0;
2061 pLine->m_Highlighted = Flag & FLAG_HIGHLIGHT;
2062 pLine->m_Client = Flag & FLAG_CLIENT;
2063 pLine->m_TimesRepeated = Repeats;
2064 str_copy(dst&: pLine->m_aName, src: pName);
2065 str_copy(dst&: pLine->m_aText, src: pText);
2066 };
2067 auto &&SetLineSkin = [RealTeeSize](int Index, const CSkin *pSkin) {
2068 if(Index >= (int)s_vLines.size())
2069 return;
2070 s_vLines[Index].m_RenderInfo.m_Size = RealTeeSize;
2071 s_vLines[Index].m_RenderInfo.Apply(pSkin);
2072 };
2073
2074 auto &&RenderPreview = [&](int LineIndex, int x, int y, bool Render = true) {
2075 if(LineIndex >= (int)s_vLines.size())
2076 return vec2(0, 0);
2077 CTextCursor LocalCursor;
2078 LocalCursor.SetPosition(vec2(x, y));
2079 LocalCursor.m_FontSize = RealFontSize;
2080 LocalCursor.m_Flags = Render ? TEXTFLAG_RENDER : 0;
2081 LocalCursor.m_LineWidth = LineWidth;
2082 const auto &Line = s_vLines[LineIndex];
2083
2084 char aClientId[16] = "";
2085 if(g_Config.m_ClShowIds && Line.m_ClientId >= 0 && Line.m_aName[0] != '\0')
2086 {
2087 GameClient()->FormatClientId(ClientId: Line.m_ClientId, aClientId, Format: EClientIdFormat::INDENT_FORCE);
2088 }
2089
2090 char aCount[12];
2091 if(Line.m_ClientId < 0)
2092 str_format(buffer: aCount, buffer_size: sizeof(aCount), format: "[%d] ", Line.m_TimesRepeated + 1);
2093 else
2094 str_format(buffer: aCount, buffer_size: sizeof(aCount), format: " [%d]", Line.m_TimesRepeated + 1);
2095
2096 if(Line.m_Player)
2097 {
2098 LocalCursor.m_X += RealMsgPaddingTee;
2099
2100 if(Line.m_Friend && g_Config.m_ClMessageFriend)
2101 {
2102 if(Render)
2103 TextRender()->TextColor(Color: FriendColor);
2104 TextRender()->TextEx(pCursor: &LocalCursor, pText: "♥ ", Length: -1);
2105 }
2106 }
2107
2108 ColorRGBA NameColor;
2109 if(Line.m_Team)
2110 NameColor = CalculateNameColor(TextColorHSL: color_cast<ColorHSLA>(rgb: TeamColor));
2111 else if(Line.m_Player)
2112 NameColor = DefaultNameColor;
2113 else if(Line.m_Client)
2114 NameColor = ClientColor;
2115 else
2116 NameColor = SystemColor;
2117
2118 if(Render)
2119 TextRender()->TextColor(Color: NameColor);
2120
2121 TextRender()->TextEx(pCursor: &LocalCursor, pText: aClientId);
2122 TextRender()->TextEx(pCursor: &LocalCursor, pText: Line.m_aName);
2123
2124 if(Line.m_TimesRepeated > 0)
2125 {
2126 if(Render)
2127 TextRender()->TextColor(r: 1.0f, g: 1.0f, b: 1.0f, a: 0.3f);
2128 TextRender()->TextEx(pCursor: &LocalCursor, pText: aCount, Length: -1);
2129 }
2130
2131 if(Line.m_ClientId >= 0 && Line.m_aName[0] != '\0')
2132 {
2133 if(Render)
2134 TextRender()->TextColor(Color: NameColor);
2135 TextRender()->TextEx(pCursor: &LocalCursor, pText: ": ", Length: -1);
2136 }
2137
2138 CTextCursor AppendCursor = LocalCursor;
2139 AppendCursor.m_LongestLineWidth = 0.0f;
2140 if(!g_Config.m_ClChatOld)
2141 {
2142 AppendCursor.m_StartX = LocalCursor.m_X;
2143 AppendCursor.m_LineWidth -= LocalCursor.m_LongestLineWidth;
2144 }
2145
2146 if(Render)
2147 {
2148 if(Line.m_Highlighted)
2149 TextRender()->TextColor(Color: HighlightedColor);
2150 else if(Line.m_Team)
2151 TextRender()->TextColor(Color: TeamColor);
2152 else if(Line.m_Player)
2153 TextRender()->TextColor(Color: NormalColor);
2154 }
2155
2156 TextRender()->TextEx(pCursor: &AppendCursor, pText: Line.m_aText, Length: -1);
2157 if(Render)
2158 TextRender()->TextColor(Color: TextRender()->DefaultTextColor());
2159
2160 return vec2{LocalCursor.m_LongestLineWidth + AppendCursor.m_LongestLineWidth, AppendCursor.Height() + RealMsgPaddingY};
2161 };
2162
2163 // Set preview lines
2164 {
2165 char aLineBuilder[128];
2166
2167 str_format(buffer: aLineBuilder, buffer_size: sizeof(aLineBuilder), format: "'%s' entered and joined the game", aBuf);
2168 SetPreviewLine(PREVIEW_SYS, -1, "*** ", aLineBuilder, 0, 0);
2169
2170 str_format(buffer: aLineBuilder, buffer_size: sizeof(aLineBuilder), format: "Hey, how are you %s?", aBuf);
2171 SetPreviewLine(PREVIEW_HIGHLIGHT, 7, "Random Tee", aLineBuilder, FLAG_HIGHLIGHT, 0);
2172
2173 SetPreviewLine(PREVIEW_TEAM, 11, "Your Teammate", "Let's speedrun this!", FLAG_TEAM, 0);
2174 SetPreviewLine(PREVIEW_FRIEND, 8, "Friend", "Hello there", FLAG_FRIEND, 0);
2175 SetPreviewLine(PREVIEW_SPAMMER, 9, "Spammer", "Hey fools, I'm spamming here!", 0, 5);
2176 SetPreviewLine(PREVIEW_CLIENT, -1, "— ", "Echo command executed", FLAG_CLIENT, 0);
2177 }
2178
2179 SetLineSkin(1, GameClient()->m_Skins.Find(pName: "pinky"));
2180 SetLineSkin(2, GameClient()->m_Skins.Find(pName: "default"));
2181 SetLineSkin(3, GameClient()->m_Skins.Find(pName: "cammostripes"));
2182 SetLineSkin(4, GameClient()->m_Skins.Find(pName: "beast"));
2183
2184 // Backgrounds first
2185 if(!g_Config.m_ClChatOld)
2186 {
2187 Graphics()->TextureClear();
2188 Graphics()->QuadsBegin();
2189 Graphics()->SetColor(color_cast<ColorRGBA>(hsl: ColorHSLA(g_Config.m_ClChatBackgroundColor, true)));
2190
2191 float TempY = Y;
2192 const float RealBackgroundRounding = Chat.MessageRounding() * 2.0f;
2193
2194 auto &&RenderMessageBackground = [&](int LineIndex) {
2195 auto Size = RenderPreview(LineIndex, 0, 0, false);
2196 Graphics()->DrawRectExt(x: X - RealMsgPaddingX / 2.0f, y: TempY - RealMsgPaddingY / 2.0f, w: Size.x + RealMsgPaddingX * 1.5f, h: Size.y, r: RealBackgroundRounding, Corners: IGraphics::CORNER_ALL);
2197 return Size.y;
2198 };
2199
2200 if(g_Config.m_ClShowChatSystem)
2201 {
2202 TempY += RenderMessageBackground(PREVIEW_SYS);
2203 }
2204
2205 if(!g_Config.m_ClShowChatFriends)
2206 {
2207 if(!g_Config.m_ClShowChatTeamMembersOnly)
2208 TempY += RenderMessageBackground(PREVIEW_HIGHLIGHT);
2209 TempY += RenderMessageBackground(PREVIEW_TEAM);
2210 }
2211
2212 if(!g_Config.m_ClShowChatTeamMembersOnly)
2213 TempY += RenderMessageBackground(PREVIEW_FRIEND);
2214
2215 if(!g_Config.m_ClShowChatFriends && !g_Config.m_ClShowChatTeamMembersOnly)
2216 {
2217 TempY += RenderMessageBackground(PREVIEW_SPAMMER);
2218 }
2219
2220 TempY += RenderMessageBackground(PREVIEW_CLIENT);
2221
2222 Graphics()->QuadsEnd();
2223 }
2224
2225 // System
2226 if(g_Config.m_ClShowChatSystem)
2227 {
2228 Y += RenderPreview(PREVIEW_SYS, X, Y).y;
2229 }
2230
2231 if(!g_Config.m_ClShowChatFriends)
2232 {
2233 // Highlighted
2234 if(!g_Config.m_ClChatOld && !g_Config.m_ClShowChatTeamMembersOnly)
2235 RenderTools()->RenderTee(pAnim: pIdleState, pInfo: &s_vLines[PREVIEW_HIGHLIGHT].m_RenderInfo, Emote: EMOTE_NORMAL, Dir: vec2(1, 0.1f), Pos: vec2(X + RealTeeSizeHalved, Y + OffsetTeeY + FullHeightMinusTee / 2.0f + TWSkinUnreliableOffset));
2236 if(!g_Config.m_ClShowChatTeamMembersOnly)
2237 Y += RenderPreview(PREVIEW_HIGHLIGHT, X, Y).y;
2238
2239 // Team
2240 if(!g_Config.m_ClChatOld)
2241 RenderTools()->RenderTee(pAnim: pIdleState, pInfo: &s_vLines[PREVIEW_TEAM].m_RenderInfo, Emote: EMOTE_NORMAL, Dir: vec2(1, 0.1f), Pos: vec2(X + RealTeeSizeHalved, Y + OffsetTeeY + FullHeightMinusTee / 2.0f + TWSkinUnreliableOffset));
2242 Y += RenderPreview(PREVIEW_TEAM, X, Y).y;
2243 }
2244
2245 // Friend
2246 if(!g_Config.m_ClChatOld && !g_Config.m_ClShowChatTeamMembersOnly)
2247 RenderTools()->RenderTee(pAnim: pIdleState, pInfo: &s_vLines[PREVIEW_FRIEND].m_RenderInfo, Emote: EMOTE_NORMAL, Dir: vec2(1, 0.1f), Pos: vec2(X + RealTeeSizeHalved, Y + OffsetTeeY + FullHeightMinusTee / 2.0f + TWSkinUnreliableOffset));
2248 if(!g_Config.m_ClShowChatTeamMembersOnly)
2249 Y += RenderPreview(PREVIEW_FRIEND, X, Y).y;
2250
2251 // Normal
2252 if(!g_Config.m_ClShowChatFriends && !g_Config.m_ClShowChatTeamMembersOnly)
2253 {
2254 if(!g_Config.m_ClChatOld)
2255 RenderTools()->RenderTee(pAnim: pIdleState, pInfo: &s_vLines[PREVIEW_SPAMMER].m_RenderInfo, Emote: EMOTE_NORMAL, Dir: vec2(1, 0.1f), Pos: vec2(X + RealTeeSizeHalved, Y + OffsetTeeY + FullHeightMinusTee / 2.0f + TWSkinUnreliableOffset));
2256 Y += RenderPreview(PREVIEW_SPAMMER, X, Y).y;
2257 }
2258 // Client
2259 RenderPreview(PREVIEW_CLIENT, X, Y);
2260
2261 TextRender()->TextColor(Color: TextRender()->DefaultTextColor());
2262 }
2263 else if(s_CurTab == APPEARANCE_TAB_NAME_PLATE)
2264 {
2265 MainView.VSplitMid(pLeft: &LeftView, pRight: &RightView, Spacing: MarginBetweenViews);
2266
2267 // ***** Name Plate ***** //
2268 Ui()->DoLabel_AutoLineSize(pText: Localize(pStr: "Name Plate"), FontSize: HeadlineFontSize,
2269 Align: TEXTALIGN_ML, pRect: &LeftView, LineSize: HeadlineHeight);
2270 LeftView.HSplitTop(Cut: MarginSmall, pTop: nullptr, pBottom: &LeftView);
2271
2272 // General name plate settings
2273 {
2274 int Pressed = (g_Config.m_ClNamePlates ? 2 : 0) + (g_Config.m_ClNamePlatesOwn ? 1 : 0);
2275 if(DoLine_RadioMenu(View&: LeftView, pLabel: Localize(pStr: "Show name plates"),
2276 vButtonContainers&: m_vButtonContainersNamePlateShow,
2277 vLabels: {Localize(pStr: "None", pContext: "Show name plates"), Localize(pStr: "Own", pContext: "Show name plates"), Localize(pStr: "Others", pContext: "Show name plates"), Localize(pStr: "All", pContext: "Show name plates")},
2278 vValues: {0, 1, 2, 3},
2279 Value&: Pressed))
2280 {
2281 g_Config.m_ClNamePlates = Pressed & 2 ? 1 : 0;
2282 g_Config.m_ClNamePlatesOwn = Pressed & 1 ? 1 : 0;
2283 }
2284 }
2285 LeftView.HSplitTop(Cut: LineSize, pTop: &Button, pBottom: &LeftView);
2286 Ui()->DoScrollbarOption(pId: &g_Config.m_ClNamePlatesSize, pOption: &g_Config.m_ClNamePlatesSize, pRect: &Button, pStr: Localize(pStr: "Name plates size"), Min: -50, Max: 100);
2287 LeftView.HSplitTop(Cut: LineSize, pTop: &Button, pBottom: &LeftView);
2288 Ui()->DoScrollbarOption(pId: &g_Config.m_ClNamePlatesOffset, pOption: &g_Config.m_ClNamePlatesOffset, pRect: &Button, pStr: Localize(pStr: "Name plates offset"), Min: 10, Max: 50);
2289
2290 DoButton_CheckBoxAutoVMarginAndSet(pId: &g_Config.m_ClNamePlatesClan, pText: Localize(pStr: "Show clan above name plates"), pValue: &g_Config.m_ClNamePlatesClan, pRect: &LeftView, VMargin: LineSize);
2291 LeftView.HSplitTop(Cut: LineSize, pTop: &Button, pBottom: &LeftView);
2292 if(g_Config.m_ClNamePlatesClan)
2293 Ui()->DoScrollbarOption(pId: &g_Config.m_ClNamePlatesClanSize, pOption: &g_Config.m_ClNamePlatesClanSize, pRect: &Button, pStr: Localize(pStr: "Clan plates size"), Min: -50, Max: 100);
2294
2295 DoButton_CheckBoxAutoVMarginAndSet(pId: &g_Config.m_ClNamePlatesTeamcolors, pText: Localize(pStr: "Use team colors for name plates"), pValue: &g_Config.m_ClNamePlatesTeamcolors, pRect: &LeftView, VMargin: LineSize);
2296 DoButton_CheckBoxAutoVMarginAndSet(pId: &g_Config.m_ClNamePlatesFriendMark, pText: Localize(pStr: "Show friend icon in name plates"), pValue: &g_Config.m_ClNamePlatesFriendMark, pRect: &LeftView, VMargin: LineSize);
2297
2298 DoButton_CheckBoxAutoVMarginAndSet(pId: &g_Config.m_ClNamePlatesIds, pText: Localize(pStr: "Show client IDs in name plates"), pValue: &g_Config.m_ClNamePlatesIds, pRect: &LeftView, VMargin: LineSize);
2299 if(g_Config.m_ClNamePlatesIds > 0)
2300 DoButton_CheckBoxAutoVMarginAndSet(pId: &g_Config.m_ClNamePlatesIdsSeparateLine, pText: Localize(pStr: "Show client IDs on a separate line"), pValue: &g_Config.m_ClNamePlatesIdsSeparateLine, pRect: &LeftView, VMargin: LineSize);
2301 else
2302 LeftView.HSplitTop(Cut: LineSize, pTop: nullptr, pBottom: &LeftView);
2303 LeftView.HSplitTop(Cut: LineSize, pTop: &Button, pBottom: &LeftView);
2304 if(g_Config.m_ClNamePlatesIds > 0 && g_Config.m_ClNamePlatesIdsSeparateLine > 0)
2305 Ui()->DoScrollbarOption(pId: &g_Config.m_ClNamePlatesIdsSize, pOption: &g_Config.m_ClNamePlatesIdsSize, pRect: &Button, pStr: Localize(pStr: "Client IDs size"), Min: -50, Max: 100);
2306
2307 // ***** Hook Strength ***** //
2308 LeftView.HSplitTop(Cut: MarginBetweenViews, pTop: nullptr, pBottom: &LeftView);
2309 Ui()->DoLabel_AutoLineSize(pText: Localize(pStr: "Hook Strength"), FontSize: HeadlineFontSize,
2310 Align: TEXTALIGN_ML, pRect: &LeftView, LineSize: HeadlineHeight);
2311 LeftView.HSplitTop(Cut: MarginSmall, pTop: nullptr, pBottom: &LeftView);
2312
2313 LeftView.HSplitTop(Cut: LineSize, pTop: &Button, pBottom: &LeftView);
2314 if(DoButton_CheckBox(pId: &g_Config.m_ClNamePlatesStrong, pText: Localize(pStr: "Show hook strength icon indicator"), Checked: g_Config.m_ClNamePlatesStrong, pRect: &Button))
2315 {
2316 g_Config.m_ClNamePlatesStrong = g_Config.m_ClNamePlatesStrong ? 0 : 1;
2317 }
2318 LeftView.HSplitTop(Cut: LineSize, pTop: &Button, pBottom: &LeftView);
2319 if(g_Config.m_ClNamePlatesStrong)
2320 {
2321 static int s_NamePlatesStrong = 0;
2322 if(DoButton_CheckBox(pId: &s_NamePlatesStrong, pText: Localize(pStr: "Show hook strength number indicator"), Checked: g_Config.m_ClNamePlatesStrong == 2, pRect: &Button))
2323 g_Config.m_ClNamePlatesStrong = g_Config.m_ClNamePlatesStrong != 2 ? 2 : 1;
2324 }
2325
2326 LeftView.HSplitTop(Cut: LineSize * 2.0f, pTop: &Button, pBottom: &LeftView);
2327 if(g_Config.m_ClNamePlatesStrong)
2328 {
2329 Ui()->DoScrollbarOption(pId: &g_Config.m_ClNamePlatesStrongSize, pOption: &g_Config.m_ClNamePlatesStrongSize, pRect: &Button, pStr: Localize(pStr: "Size of hook strength icon and number indicator"), Min: -50, Max: 100, pScale: &CUi::ms_LinearScrollbarScale, Flags: CUi::SCROLLBAR_OPTION_MULTILINE);
2330 }
2331
2332 // ***** Key Presses ***** //
2333 LeftView.HSplitTop(Cut: MarginBetweenViews, pTop: nullptr, pBottom: &LeftView);
2334 Ui()->DoLabel_AutoLineSize(pText: Localize(pStr: "Key Presses"), FontSize: HeadlineFontSize,
2335 Align: TEXTALIGN_ML, pRect: &LeftView, LineSize: HeadlineHeight);
2336 LeftView.HSplitTop(Cut: MarginSmall, pTop: nullptr, pBottom: &LeftView);
2337
2338 DoLine_RadioMenu(View&: LeftView, pLabel: Localize(pStr: "Show players' key presses"),
2339 vButtonContainers&: m_vButtonContainersNamePlateKeyPresses,
2340 vLabels: {Localize(pStr: "None", pContext: "Show players' key presses"), Localize(pStr: "Own", pContext: "Show players' key presses"), Localize(pStr: "Others", pContext: "Show players' key presses"), Localize(pStr: "All", pContext: "Show players' key presses")},
2341 vValues: {0, 3, 1, 2},
2342 Value&: g_Config.m_ClShowDirection);
2343
2344 LeftView.HSplitTop(Cut: LineSize, pTop: &Button, pBottom: &LeftView);
2345 if(g_Config.m_ClShowDirection > 0)
2346 Ui()->DoScrollbarOption(pId: &g_Config.m_ClDirectionSize, pOption: &g_Config.m_ClDirectionSize, pRect: &Button, pStr: Localize(pStr: "Size of key press icons"), Min: -50, Max: 100);
2347
2348 // ***** Name Plate Preview ***** //
2349 Ui()->DoLabel_AutoLineSize(pText: Localize(pStr: "Preview"), FontSize: HeadlineFontSize,
2350 Align: TEXTALIGN_ML, pRect: &RightView, LineSize: HeadlineHeight);
2351 RightView.HSplitTop(Cut: 2.0f * MarginSmall, pTop: nullptr, pBottom: &RightView);
2352
2353 // ***** Name Plate Dummy Preview ***** //
2354 RightView.HSplitBottom(Cut: LineSize, pTop: &RightView, pBottom: &Button);
2355 if(DoButton_CheckBox(pId: &m_DummyNamePlatePreview, pText: g_Config.m_ClDummy ? Localize(pStr: "Preview player's name plate") : Localize(pStr: "Preview dummy's name plate"), Checked: m_DummyNamePlatePreview, pRect: &Button))
2356 m_DummyNamePlatePreview = !m_DummyNamePlatePreview;
2357
2358 int Dummy = g_Config.m_ClDummy != (m_DummyNamePlatePreview ? 1 : 0);
2359
2360 const vec2 Position = RightView.Center();
2361
2362 GameClient()->m_NamePlates.RenderNamePlatePreview(Position, Dummy);
2363 }
2364 else if(s_CurTab == APPEARANCE_TAB_HOOK_COLLISION)
2365 {
2366 MainView.VSplitMid(pLeft: &LeftView, pRight: &RightView, Spacing: MarginBetweenViews);
2367
2368 // ***** Hookline ***** //
2369 Ui()->DoLabel_AutoLineSize(pText: Localize(pStr: "Hook collision line"), FontSize: HeadlineFontSize,
2370 Align: TEXTALIGN_ML, pRect: &LeftView, LineSize: HeadlineHeight);
2371 LeftView.HSplitTop(Cut: MarginSmall, pTop: nullptr, pBottom: &LeftView);
2372
2373 // General hookline settings
2374 LeftView.HSplitTop(Cut: LineSize, pTop: &Button, pBottom: &LeftView);
2375 if(DoButton_CheckBox(pId: &g_Config.m_ClShowHookCollOwn, pText: Localize(pStr: "Show own player's hook collision line"), Checked: g_Config.m_ClShowHookCollOwn, pRect: &Button))
2376 {
2377 g_Config.m_ClShowHookCollOwn = g_Config.m_ClShowHookCollOwn ? 0 : 1;
2378 }
2379 LeftView.HSplitTop(Cut: LineSize, pTop: &Button, pBottom: &LeftView);
2380 if(g_Config.m_ClShowHookCollOwn)
2381 {
2382 static int s_ShowHookCollOwn = 0;
2383 if(DoButton_CheckBox(pId: &s_ShowHookCollOwn, pText: Localize(pStr: "Always show own player's hook collision line"), Checked: g_Config.m_ClShowHookCollOwn == 2, pRect: &Button))
2384 g_Config.m_ClShowHookCollOwn = g_Config.m_ClShowHookCollOwn != 2 ? 2 : 1;
2385 }
2386
2387 LeftView.HSplitTop(Cut: LineSize, pTop: &Button, pBottom: &LeftView);
2388 if(DoButton_CheckBox(pId: &g_Config.m_ClShowHookCollOther, pText: Localize(pStr: "Show other players' hook collision lines"), Checked: g_Config.m_ClShowHookCollOther, pRect: &Button))
2389 {
2390 g_Config.m_ClShowHookCollOther = g_Config.m_ClShowHookCollOther >= 1 ? 0 : 1;
2391 }
2392 LeftView.HSplitTop(Cut: LineSize, pTop: &Button, pBottom: &LeftView);
2393 if(g_Config.m_ClShowHookCollOther)
2394 {
2395 static int s_ShowHookCollOther = 0;
2396 if(DoButton_CheckBox(pId: &s_ShowHookCollOther, pText: Localize(pStr: "Always show other players' hook collision lines"), Checked: g_Config.m_ClShowHookCollOther == 2, pRect: &Button))
2397 g_Config.m_ClShowHookCollOther = g_Config.m_ClShowHookCollOther != 2 ? 2 : 1;
2398 }
2399
2400 LeftView.HSplitTop(Cut: LineSize * 2.0f, pTop: &Button, pBottom: &LeftView);
2401 Ui()->DoScrollbarOption(pId: &g_Config.m_ClHookCollSize, pOption: &g_Config.m_ClHookCollSize, pRect: &Button, pStr: Localize(pStr: "Width of your own hook collision line"), Min: 0, Max: 20, pScale: &CUi::ms_LinearScrollbarScale, Flags: CUi::SCROLLBAR_OPTION_MULTILINE);
2402
2403 LeftView.HSplitTop(Cut: LineSize * 2.0f, pTop: &Button, pBottom: &LeftView);
2404 Ui()->DoScrollbarOption(pId: &g_Config.m_ClHookCollSizeOther, pOption: &g_Config.m_ClHookCollSizeOther, pRect: &Button, pStr: Localize(pStr: "Width of others' hook collision line"), Min: 0, Max: 20, pScale: &CUi::ms_LinearScrollbarScale, Flags: CUi::SCROLLBAR_OPTION_MULTILINE);
2405
2406 LeftView.HSplitTop(Cut: LineSize * 2.0f, pTop: &Button, pBottom: &LeftView);
2407 Ui()->DoScrollbarOption(pId: &g_Config.m_ClHookCollAlpha, pOption: &g_Config.m_ClHookCollAlpha, pRect: &Button, pStr: Localize(pStr: "Hook collision line opacity"), Min: 0, Max: 100, pScale: &CUi::ms_LinearScrollbarScale, Flags: CUi::SCROLLBAR_OPTION_MULTILINE, pSuffix: "%");
2408
2409 static CButtonContainer s_HookCollNoCollResetId, s_HookCollHookableCollResetId, s_HookCollTeeCollResetId;
2410 static int s_HookCollToolTip;
2411
2412 Ui()->DoLabel_AutoLineSize(pText: Localize(pStr: "Colors of the hook collision line, in case of a possible collision with:"), FontSize: 13.0f,
2413 Align: TEXTALIGN_ML, pRect: &LeftView, LineSize: HeadlineHeight);
2414
2415 Ui()->DoButtonLogic(pId: &s_HookCollToolTip, Checked: 0, pRect: &LeftView, Flags: BUTTONFLAG_NONE); // Just for the tooltip, result ignored
2416 GameClient()->m_Tooltips.DoToolTip(pId: &s_HookCollToolTip, pNearRect: &LeftView, pText: Localize(pStr: "Your movements are not taken into account when calculating the line colors"));
2417 DoLine_ColorPicker(pResetId: &s_HookCollNoCollResetId, LineSize: ColorPickerLineSize, LabelSize: ColorPickerLabelSize, BottomMargin: ColorPickerLineSpacing, pMainRect: &LeftView, pText: Localize(pStr: "Nothing hookable"), pColorValue: &g_Config.m_ClHookCollColorNoColl, DefaultColor: ColorRGBA(1.0f, 0.0f, 0.0f, 1.0f), CheckBoxSpacing: false);
2418 DoLine_ColorPicker(pResetId: &s_HookCollHookableCollResetId, LineSize: ColorPickerLineSize, LabelSize: ColorPickerLabelSize, BottomMargin: ColorPickerLineSpacing, pMainRect: &LeftView, pText: Localize(pStr: "Something hookable"), pColorValue: &g_Config.m_ClHookCollColorHookableColl, DefaultColor: ColorRGBA(130.0f / 255.0f, 232.0f / 255.0f, 160.0f / 255.0f, 1.0f), CheckBoxSpacing: false);
2419 DoLine_ColorPicker(pResetId: &s_HookCollTeeCollResetId, LineSize: ColorPickerLineSize, LabelSize: ColorPickerLabelSize, BottomMargin: ColorPickerLineSpacing, pMainRect: &LeftView, pText: Localize(pStr: "A Tee"), pColorValue: &g_Config.m_ClHookCollColorTeeColl, DefaultColor: ColorRGBA(1.0f, 1.0f, 0.0f, 1.0f), CheckBoxSpacing: false);
2420
2421 // ***** Hook collisions preview ***** //
2422 Ui()->DoLabel_AutoLineSize(pText: Localize(pStr: "Preview"), FontSize: HeadlineFontSize,
2423 Align: TEXTALIGN_ML, pRect: &RightView, LineSize: HeadlineHeight);
2424 RightView.HSplitTop(Cut: 2 * MarginSmall, pTop: nullptr, pBottom: &RightView);
2425
2426 auto DoHookCollision = [this](const vec2 &Pos, const float &Length, const int &Size, const ColorRGBA &Color, const bool &Invert) {
2427 ColorRGBA ColorModified = Color;
2428 if(Invert)
2429 ColorModified = color_invert(col: ColorModified);
2430 ColorModified = ColorModified.WithAlpha(alpha: (float)g_Config.m_ClHookCollAlpha / 100);
2431 Graphics()->TextureClear();
2432 if(Size > 0)
2433 {
2434 Graphics()->QuadsBegin();
2435 Graphics()->SetColor(ColorModified);
2436 float LineWidth = 0.5f + (float)(Size - 1) * 0.25f;
2437 IGraphics::CQuadItem QuadItem(Pos.x, Pos.y - LineWidth, Length, LineWidth * 2.f);
2438 Graphics()->QuadsDrawTL(pArray: &QuadItem, Num: 1);
2439 Graphics()->QuadsEnd();
2440 }
2441 else
2442 {
2443 Graphics()->LinesBegin();
2444 Graphics()->SetColor(ColorModified);
2445 IGraphics::CLineItem LineItem(Pos.x, Pos.y, Pos.x + Length, Pos.y);
2446 Graphics()->LinesDraw(pArray: &LineItem, Num: 1);
2447 Graphics()->LinesEnd();
2448 }
2449 };
2450
2451 CTeeRenderInfo OwnSkinInfo;
2452 OwnSkinInfo.Apply(pSkin: GameClient()->m_Skins.Find(pName: g_Config.m_ClPlayerSkin));
2453 OwnSkinInfo.ApplyColors(CustomColoredSkin: g_Config.m_ClPlayerUseCustomColor, ColorBody: g_Config.m_ClPlayerColorBody, ColorFeet: g_Config.m_ClPlayerColorFeet);
2454 OwnSkinInfo.m_Size = 50.0f;
2455
2456 CTeeRenderInfo DummySkinInfo;
2457 DummySkinInfo.Apply(pSkin: GameClient()->m_Skins.Find(pName: g_Config.m_ClDummySkin));
2458 DummySkinInfo.ApplyColors(CustomColoredSkin: g_Config.m_ClDummyUseCustomColor, ColorBody: g_Config.m_ClDummyColorBody, ColorFeet: g_Config.m_ClDummyColorFeet);
2459 DummySkinInfo.m_Size = 50.0f;
2460
2461 vec2 TeeRenderPos, DummyRenderPos;
2462
2463 const float LineLength = 150.f;
2464 const float LeftMargin = 30.f;
2465
2466 const int TileScale = 32.0f;
2467
2468 // Toggled via checkbox later, inverts some previews
2469 static bool s_HookCollPressed = false;
2470
2471 CUIRect PreviewColl;
2472
2473 // ***** Unhookable Tile Preview *****
2474 CUIRect PreviewNoColl;
2475 RightView.HSplitTop(Cut: 50.0f, pTop: &PreviewNoColl, pBottom: &RightView);
2476 RightView.HSplitTop(Cut: 4 * MarginSmall, pTop: nullptr, pBottom: &RightView);
2477 TeeRenderPos = vec2(PreviewNoColl.x + LeftMargin, PreviewNoColl.y + PreviewNoColl.h / 2.0f);
2478 DoHookCollision(TeeRenderPos, PreviewNoColl.w - LineLength, g_Config.m_ClHookCollSize, color_cast<ColorRGBA>(hsl: ColorHSLA(g_Config.m_ClHookCollColorNoColl)), s_HookCollPressed);
2479 RenderTools()->RenderTee(pAnim: CAnimState::GetIdle(), pInfo: &OwnSkinInfo, Emote: 0, Dir: vec2(1.0f, 0.0f), Pos: TeeRenderPos);
2480
2481 CUIRect NoHookTileRect;
2482 PreviewNoColl.VSplitRight(Cut: LineLength, pLeft: &PreviewNoColl, pRight: &NoHookTileRect);
2483 NoHookTileRect.VSplitLeft(Cut: 50.0f, pLeft: &NoHookTileRect, pRight: nullptr);
2484 NoHookTileRect.Margin(Cut: 10.0f, pOtherRect: &NoHookTileRect);
2485
2486 // Render unhookable tile
2487 Graphics()->TextureClear();
2488 Graphics()->TextureSet(Texture: GameClient()->m_MapImages.GetEntities(EntityLayerType: MAP_IMAGE_ENTITY_LAYER_TYPE_ALL_EXCEPT_SWITCH));
2489 Graphics()->BlendNormal();
2490 Graphics()->SetColor(r: 1.0f, g: 1.0f, b: 1.0f, a: 1.0f);
2491 RenderMap()->RenderTile(x: NoHookTileRect.x, y: NoHookTileRect.y, Index: TILE_NOHOOK, Scale: TileScale, Color: ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
2492
2493 // ***** Hookable Tile Preview *****
2494 RightView.HSplitTop(Cut: 50.0f, pTop: &PreviewColl, pBottom: &RightView);
2495 RightView.HSplitTop(Cut: 4 * MarginSmall, pTop: nullptr, pBottom: &RightView);
2496 TeeRenderPos = vec2(PreviewColl.x + LeftMargin, PreviewColl.y + PreviewColl.h / 2.0f);
2497 DoHookCollision(TeeRenderPos, PreviewColl.w - LineLength, g_Config.m_ClHookCollSize, color_cast<ColorRGBA>(hsl: ColorHSLA(g_Config.m_ClHookCollColorHookableColl)), s_HookCollPressed);
2498 RenderTools()->RenderTee(pAnim: CAnimState::GetIdle(), pInfo: &OwnSkinInfo, Emote: 0, Dir: vec2(1.0f, 0.0f), Pos: TeeRenderPos);
2499
2500 CUIRect HookTileRect;
2501 PreviewColl.VSplitRight(Cut: LineLength, pLeft: &PreviewColl, pRight: &HookTileRect);
2502 HookTileRect.VSplitLeft(Cut: 50.0f, pLeft: &HookTileRect, pRight: nullptr);
2503 HookTileRect.Margin(Cut: 10.0f, pOtherRect: &HookTileRect);
2504
2505 // Render hookable tile
2506 Graphics()->TextureClear();
2507 Graphics()->TextureSet(Texture: GameClient()->m_MapImages.GetEntities(EntityLayerType: MAP_IMAGE_ENTITY_LAYER_TYPE_ALL_EXCEPT_SWITCH));
2508 Graphics()->BlendNormal();
2509 Graphics()->SetColor(r: 1.0f, g: 1.0f, b: 1.0f, a: 1.0f);
2510 RenderMap()->RenderTile(x: HookTileRect.x, y: HookTileRect.y, Index: TILE_SOLID, Scale: TileScale, Color: ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
2511
2512 // ***** Hook Dummy Preview *****
2513 RightView.HSplitTop(Cut: 50.0f, pTop: &PreviewColl, pBottom: &RightView);
2514 RightView.HSplitTop(Cut: 4 * MarginSmall, pTop: nullptr, pBottom: &RightView);
2515 TeeRenderPos = vec2(PreviewColl.x + LeftMargin, PreviewColl.y + PreviewColl.h / 2.0f);
2516 DummyRenderPos = vec2(PreviewColl.x + PreviewColl.w - LineLength - 5.f + LeftMargin, PreviewColl.y + PreviewColl.h / 2.0f);
2517 DoHookCollision(TeeRenderPos, PreviewColl.w - LineLength - 15.f, g_Config.m_ClHookCollSize, color_cast<ColorRGBA>(hsl: ColorHSLA(g_Config.m_ClHookCollColorTeeColl)), s_HookCollPressed);
2518 RenderTools()->RenderTee(pAnim: CAnimState::GetIdle(), pInfo: &DummySkinInfo, Emote: 0, Dir: vec2(1.0f, 0.0f), Pos: DummyRenderPos);
2519 RenderTools()->RenderTee(pAnim: CAnimState::GetIdle(), pInfo: &OwnSkinInfo, Emote: 0, Dir: vec2(1.0f, 0.0f), Pos: TeeRenderPos);
2520
2521 // ***** Hook Dummy Reverse Preview *****
2522 RightView.HSplitTop(Cut: 50.0f, pTop: &PreviewColl, pBottom: &RightView);
2523 RightView.HSplitTop(Cut: 4 * MarginSmall, pTop: nullptr, pBottom: &RightView);
2524 TeeRenderPos = vec2(PreviewColl.x + LeftMargin, PreviewColl.y + PreviewColl.h / 2.0f);
2525 DummyRenderPos = vec2(PreviewColl.x + PreviewColl.w - LineLength - 5.f + LeftMargin, PreviewColl.y + PreviewColl.h / 2.0f);
2526 DoHookCollision(TeeRenderPos, PreviewColl.w - LineLength - 15.f, g_Config.m_ClHookCollSizeOther, color_cast<ColorRGBA>(hsl: ColorHSLA(g_Config.m_ClHookCollColorTeeColl)), false);
2527 RenderTools()->RenderTee(pAnim: CAnimState::GetIdle(), pInfo: &OwnSkinInfo, Emote: 0, Dir: vec2(1.0f, 0.0f), Pos: DummyRenderPos);
2528 RenderTools()->RenderTee(pAnim: CAnimState::GetIdle(), pInfo: &DummySkinInfo, Emote: 0, Dir: vec2(1.0f, 0.0f), Pos: TeeRenderPos);
2529
2530 // ***** Preview +hookcoll pressed toggle *****
2531 RightView.HSplitTop(Cut: LineSize, pTop: &Button, pBottom: &RightView);
2532 if(DoButton_CheckBox(pId: &s_HookCollPressed, pText: Localize(pStr: "Preview 'Hook collisions' being pressed"), Checked: s_HookCollPressed, pRect: &Button))
2533 s_HookCollPressed = !s_HookCollPressed;
2534 }
2535 else if(s_CurTab == APPEARANCE_TAB_INFO_MESSAGES)
2536 {
2537 MainView.VSplitMid(pLeft: &LeftView, pRight: &RightView, Spacing: MarginBetweenViews);
2538
2539 // ***** Info Messages ***** //
2540 Ui()->DoLabel_AutoLineSize(pText: Localize(pStr: "Info Messages"), FontSize: HeadlineFontSize,
2541 Align: TEXTALIGN_ML, pRect: &LeftView, LineSize: HeadlineHeight);
2542 LeftView.HSplitTop(Cut: MarginSmall, pTop: nullptr, pBottom: &LeftView);
2543
2544 // General info messages settings
2545 LeftView.HSplitTop(Cut: LineSize, pTop: &Button, pBottom: &LeftView);
2546 if(DoButton_CheckBox(pId: &g_Config.m_ClShowKillMessages, pText: Localize(pStr: "Show kill messages"), Checked: g_Config.m_ClShowKillMessages, pRect: &Button))
2547 {
2548 g_Config.m_ClShowKillMessages ^= 1;
2549 }
2550
2551 LeftView.HSplitTop(Cut: LineSize, pTop: &Button, pBottom: &LeftView);
2552 if(DoButton_CheckBox(pId: &g_Config.m_ClShowFinishMessages, pText: Localize(pStr: "Show finish messages"), Checked: g_Config.m_ClShowFinishMessages, pRect: &Button))
2553 {
2554 g_Config.m_ClShowFinishMessages ^= 1;
2555 }
2556
2557 static CButtonContainer s_KillMessageNormalColorId, s_KillMessageHighlightColorId;
2558 DoLine_ColorPicker(pResetId: &s_KillMessageNormalColorId, LineSize: ColorPickerLineSize, LabelSize: ColorPickerLabelSize, BottomMargin: ColorPickerLineSpacing, pMainRect: &LeftView, pText: Localize(pStr: "Normal Color"), pColorValue: &g_Config.m_ClKillMessageNormalColor, DefaultColor: ColorRGBA(1.0f, 1.0f, 1.0f), CheckBoxSpacing: false);
2559 DoLine_ColorPicker(pResetId: &s_KillMessageHighlightColorId, LineSize: ColorPickerLineSize, LabelSize: ColorPickerLabelSize, BottomMargin: ColorPickerLineSpacing, pMainRect: &LeftView, pText: Localize(pStr: "Highlight Color"), pColorValue: &g_Config.m_ClKillMessageHighlightColor, DefaultColor: ColorRGBA(1.0f, 1.0f, 1.0f), CheckBoxSpacing: false);
2560 }
2561 else if(s_CurTab == APPEARANCE_TAB_LASER)
2562 {
2563 MainView.VSplitMid(pLeft: &LeftView, pRight: &RightView, Spacing: MarginBetweenViews);
2564
2565 // ***** Weapons ***** //
2566 Ui()->DoLabel_AutoLineSize(pText: Localize(pStr: "Weapons"), FontSize: HeadlineFontSize,
2567 Align: TEXTALIGN_ML, pRect: &LeftView, LineSize: HeadlineHeight);
2568 LeftView.HSplitTop(Cut: MarginSmall, pTop: nullptr, pBottom: &LeftView);
2569
2570 // General weapon laser settings
2571 static CButtonContainer s_LaserRifleOutResetId, s_LaserRifleInResetId, s_LaserShotgunOutResetId, s_LaserShotgunInResetId;
2572
2573 ColorHSLA LaserRifleOutlineColor = DoLine_ColorPicker(pResetId: &s_LaserRifleOutResetId, LineSize: ColorPickerLineSize, LabelSize: ColorPickerLabelSize, BottomMargin: ColorPickerLineSpacing, pMainRect: &LeftView, pText: Localize(pStr: "Rifle Laser Outline Color"), pColorValue: &g_Config.m_ClLaserRifleOutlineColor, DefaultColor: ColorRGBA(0.074402f, 0.074402f, 0.247166f, 1.0f), CheckBoxSpacing: false);
2574 ColorHSLA LaserRifleInnerColor = DoLine_ColorPicker(pResetId: &s_LaserRifleInResetId, LineSize: ColorPickerLineSize, LabelSize: ColorPickerLabelSize, BottomMargin: ColorPickerLineSpacing, pMainRect: &LeftView, pText: Localize(pStr: "Rifle Laser Inner Color"), pColorValue: &g_Config.m_ClLaserRifleInnerColor, DefaultColor: ColorRGBA(0.498039f, 0.498039f, 1.0f, 1.0f), CheckBoxSpacing: false);
2575 ColorHSLA LaserShotgunOutlineColor = DoLine_ColorPicker(pResetId: &s_LaserShotgunOutResetId, LineSize: ColorPickerLineSize, LabelSize: ColorPickerLabelSize, BottomMargin: ColorPickerLineSpacing, pMainRect: &LeftView, pText: Localize(pStr: "Shotgun Laser Outline Color"), pColorValue: &g_Config.m_ClLaserShotgunOutlineColor, DefaultColor: ColorRGBA(0.125490f, 0.098039f, 0.043137f, 1.0f), CheckBoxSpacing: false);
2576 ColorHSLA LaserShotgunInnerColor = DoLine_ColorPicker(pResetId: &s_LaserShotgunInResetId, LineSize: ColorPickerLineSize, LabelSize: ColorPickerLabelSize, BottomMargin: ColorPickerLineSpacing, pMainRect: &LeftView, pText: Localize(pStr: "Shotgun Laser Inner Color"), pColorValue: &g_Config.m_ClLaserShotgunInnerColor, DefaultColor: ColorRGBA(0.570588f, 0.417647f, 0.252941f, 1.0f), CheckBoxSpacing: false);
2577
2578 // ***** Entities ***** //
2579 LeftView.HSplitTop(Cut: 10.0f, pTop: nullptr, pBottom: &LeftView);
2580 Ui()->DoLabel_AutoLineSize(pText: Localize(pStr: "Entities"), FontSize: HeadlineFontSize,
2581 Align: TEXTALIGN_ML, pRect: &LeftView, LineSize: HeadlineHeight);
2582 LeftView.HSplitTop(Cut: MarginSmall, pTop: nullptr, pBottom: &LeftView);
2583
2584 // General entity laser settings
2585 static CButtonContainer s_LaserDoorOutResetId, s_LaserDoorInResetId, s_LaserFreezeOutResetId, s_LaserFreezeInResetId, s_LaserDraggerOutResetId, s_LaserDraggerInResetId;
2586
2587 ColorHSLA LaserDoorOutlineColor = DoLine_ColorPicker(pResetId: &s_LaserDoorOutResetId, LineSize: ColorPickerLineSize, LabelSize: ColorPickerLabelSize, BottomMargin: ColorPickerLineSpacing, pMainRect: &LeftView, pText: Localize(pStr: "Door Laser Outline Color"), pColorValue: &g_Config.m_ClLaserDoorOutlineColor, DefaultColor: ColorRGBA(0.0f, 0.131372f, 0.096078f, 1.0f), CheckBoxSpacing: false);
2588 ColorHSLA LaserDoorInnerColor = DoLine_ColorPicker(pResetId: &s_LaserDoorInResetId, LineSize: ColorPickerLineSize, LabelSize: ColorPickerLabelSize, BottomMargin: ColorPickerLineSpacing, pMainRect: &LeftView, pText: Localize(pStr: "Door Laser Inner Color"), pColorValue: &g_Config.m_ClLaserDoorInnerColor, DefaultColor: ColorRGBA(0.262745f, 0.760784f, 0.639215f, 1.0f), CheckBoxSpacing: false);
2589 ColorHSLA LaserFreezeOutlineColor = DoLine_ColorPicker(pResetId: &s_LaserFreezeOutResetId, LineSize: ColorPickerLineSize, LabelSize: ColorPickerLabelSize, BottomMargin: ColorPickerLineSpacing, pMainRect: &LeftView, pText: Localize(pStr: "Freeze Laser Outline Color"), pColorValue: &g_Config.m_ClLaserFreezeOutlineColor, DefaultColor: ColorRGBA(0.131372f, 0.123529f, 0.182352f, 1.0f), CheckBoxSpacing: false);
2590 ColorHSLA LaserFreezeInnerColor = DoLine_ColorPicker(pResetId: &s_LaserFreezeInResetId, LineSize: ColorPickerLineSize, LabelSize: ColorPickerLabelSize, BottomMargin: ColorPickerLineSpacing, pMainRect: &LeftView, pText: Localize(pStr: "Freeze Laser Inner Color"), pColorValue: &g_Config.m_ClLaserFreezeInnerColor, DefaultColor: ColorRGBA(0.482352f, 0.443137f, 0.564705f, 1.0f), CheckBoxSpacing: false);
2591 ColorHSLA LaserDraggerOutlineColor = DoLine_ColorPicker(pResetId: &s_LaserDraggerOutResetId, LineSize: ColorPickerLineSize, LabelSize: ColorPickerLabelSize, BottomMargin: ColorPickerLineSpacing, pMainRect: &LeftView, pText: Localize(pStr: "Dragger Outline Color"), pColorValue: &g_Config.m_ClLaserDraggerOutlineColor, DefaultColor: ColorRGBA(0.1640625f, 0.015625f, 0.015625f, 1.0f), CheckBoxSpacing: false);
2592 ColorHSLA LaserDraggerInnerColor = DoLine_ColorPicker(pResetId: &s_LaserDraggerInResetId, LineSize: ColorPickerLineSize, LabelSize: ColorPickerLabelSize, BottomMargin: ColorPickerLineSpacing, pMainRect: &LeftView, pText: Localize(pStr: "Dragger Inner Color"), pColorValue: &g_Config.m_ClLaserDraggerInnerColor, DefaultColor: ColorRGBA(.8666666f, .3725490f, .3725490f, 1.0f), CheckBoxSpacing: false);
2593
2594 static CButtonContainer s_AllToRifleResetId, s_AllToDefaultResetId;
2595
2596 LeftView.HSplitTop(Cut: 4 * MarginSmall, pTop: nullptr, pBottom: &LeftView);
2597 LeftView.HSplitTop(Cut: LineSize, pTop: &Button, pBottom: &LeftView);
2598 if(DoButton_Menu(pButtonContainer: &s_AllToRifleResetId, pText: Localize(pStr: "Set all to Rifle"), Checked: 0, pRect: &Button))
2599 {
2600 g_Config.m_ClLaserShotgunOutlineColor = g_Config.m_ClLaserRifleOutlineColor;
2601 g_Config.m_ClLaserShotgunInnerColor = g_Config.m_ClLaserRifleInnerColor;
2602 g_Config.m_ClLaserDoorOutlineColor = g_Config.m_ClLaserRifleOutlineColor;
2603 g_Config.m_ClLaserDoorInnerColor = g_Config.m_ClLaserRifleInnerColor;
2604 g_Config.m_ClLaserFreezeOutlineColor = g_Config.m_ClLaserRifleOutlineColor;
2605 g_Config.m_ClLaserFreezeInnerColor = g_Config.m_ClLaserRifleInnerColor;
2606 g_Config.m_ClLaserDraggerOutlineColor = g_Config.m_ClLaserRifleOutlineColor;
2607 g_Config.m_ClLaserDraggerInnerColor = g_Config.m_ClLaserRifleInnerColor;
2608 }
2609
2610 // values taken from the CL commands
2611 LeftView.HSplitTop(Cut: 2 * MarginSmall, pTop: nullptr, pBottom: &LeftView);
2612 LeftView.HSplitTop(Cut: LineSize, pTop: &Button, pBottom: &LeftView);
2613 if(DoButton_Menu(pButtonContainer: &s_AllToDefaultResetId, pText: Localize(pStr: "Reset to defaults"), Checked: 0, pRect: &Button))
2614 {
2615 g_Config.m_ClLaserRifleOutlineColor = 11176233;
2616 g_Config.m_ClLaserRifleInnerColor = 11206591;
2617 g_Config.m_ClLaserShotgunOutlineColor = 1866773;
2618 g_Config.m_ClLaserShotgunInnerColor = 1467241;
2619 g_Config.m_ClLaserDoorOutlineColor = 7667473;
2620 g_Config.m_ClLaserDoorInnerColor = 7701379;
2621 g_Config.m_ClLaserFreezeOutlineColor = 11613223;
2622 g_Config.m_ClLaserFreezeInnerColor = 12001153;
2623 g_Config.m_ClLaserDraggerOutlineColor = 57618;
2624 g_Config.m_ClLaserDraggerInnerColor = 42398;
2625 }
2626
2627 // ***** Laser Preview ***** //
2628 Ui()->DoLabel_AutoLineSize(pText: Localize(pStr: "Preview"), FontSize: HeadlineFontSize,
2629 Align: TEXTALIGN_ML, pRect: &RightView, LineSize: HeadlineHeight);
2630 RightView.HSplitTop(Cut: MarginSmall, pTop: nullptr, pBottom: &RightView);
2631
2632 const float LaserPreviewHeight = 60.0f;
2633 CUIRect LaserPreview;
2634 RightView.HSplitTop(Cut: LaserPreviewHeight, pTop: &LaserPreview, pBottom: &RightView);
2635 RightView.HSplitTop(Cut: 2 * MarginSmall, pTop: nullptr, pBottom: &RightView);
2636 DoLaserPreview(pRect: &LaserPreview, OutlineColor: LaserRifleOutlineColor, InnerColor: LaserRifleInnerColor, LaserType: LASERTYPE_RIFLE);
2637
2638 RightView.HSplitTop(Cut: LaserPreviewHeight, pTop: &LaserPreview, pBottom: &RightView);
2639 RightView.HSplitTop(Cut: 2 * MarginSmall, pTop: nullptr, pBottom: &RightView);
2640 DoLaserPreview(pRect: &LaserPreview, OutlineColor: LaserShotgunOutlineColor, InnerColor: LaserShotgunInnerColor, LaserType: LASERTYPE_SHOTGUN);
2641
2642 RightView.HSplitTop(Cut: LaserPreviewHeight, pTop: &LaserPreview, pBottom: &RightView);
2643 RightView.HSplitTop(Cut: 2 * MarginSmall, pTop: nullptr, pBottom: &RightView);
2644 DoLaserPreview(pRect: &LaserPreview, OutlineColor: LaserDoorOutlineColor, InnerColor: LaserDoorInnerColor, LaserType: LASERTYPE_DOOR);
2645
2646 RightView.HSplitTop(Cut: LaserPreviewHeight, pTop: &LaserPreview, pBottom: &RightView);
2647 RightView.HSplitTop(Cut: 2 * MarginSmall, pTop: nullptr, pBottom: &RightView);
2648 DoLaserPreview(pRect: &LaserPreview, OutlineColor: LaserFreezeOutlineColor, InnerColor: LaserFreezeInnerColor, LaserType: LASERTYPE_FREEZE);
2649
2650 RightView.HSplitTop(Cut: LaserPreviewHeight, pTop: &LaserPreview, pBottom: &RightView);
2651 RightView.HSplitTop(Cut: 2 * MarginSmall, pTop: nullptr, pBottom: &RightView);
2652 DoLaserPreview(pRect: &LaserPreview, OutlineColor: LaserDraggerOutlineColor, InnerColor: LaserDraggerInnerColor, LaserType: LASERTYPE_DRAGGER);
2653 }
2654}
2655
2656void CMenus::RenderSettingsDDNet(CUIRect MainView)
2657{
2658 CUIRect Button, Left, Right, LeftLeft, Label;
2659
2660#if defined(CONF_AUTOUPDATE)
2661 CUIRect UpdaterRect;
2662 MainView.HSplitBottom(Cut: 20.0f, pTop: &MainView, pBottom: &UpdaterRect);
2663 MainView.HSplitBottom(Cut: 5.0f, pTop: &MainView, pBottom: nullptr);
2664#endif
2665
2666 // demo
2667 CUIRect Demo;
2668 MainView.HSplitTop(Cut: 110.0f, pTop: &Demo, pBottom: &MainView);
2669 Demo.HSplitTop(Cut: 30.0f, pTop: &Label, pBottom: &Demo);
2670 Ui()->DoLabel(pRect: &Label, pText: Localize(pStr: "Demo"), Size: 20.0f, Align: TEXTALIGN_ML);
2671 Label.VSplitMid(pLeft: nullptr, pRight: &Label, Spacing: 20.0f);
2672 Ui()->DoLabel(pRect: &Label, pText: Localize(pStr: "Ghost"), Size: 20.0f, Align: TEXTALIGN_ML);
2673
2674 Demo.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &Demo);
2675 Demo.VSplitMid(pLeft: &Left, pRight: &Right, Spacing: 20.0f);
2676
2677 Left.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Left);
2678 if(DoButton_CheckBox(pId: &g_Config.m_ClAutoRaceRecord, pText: Localize(pStr: "Save the best demo of each race"), Checked: g_Config.m_ClAutoRaceRecord, pRect: &Button))
2679 {
2680 g_Config.m_ClAutoRaceRecord ^= 1;
2681 }
2682
2683 Left.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Left);
2684 if(DoButton_CheckBox(pId: &g_Config.m_ClReplays, pText: Localize(pStr: "Enable replays"), Checked: g_Config.m_ClReplays, pRect: &Button))
2685 {
2686 g_Config.m_ClReplays ^= 1;
2687 Client()->DemoRecorder_UpdateReplayRecorder();
2688 }
2689
2690 Left.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Left);
2691 if(g_Config.m_ClReplays)
2692 Ui()->DoScrollbarOption(pId: &g_Config.m_ClReplayLength, pOption: &g_Config.m_ClReplayLength, pRect: &Button, pStr: Localize(pStr: "Default length"), Min: 10, Max: 600, pScale: &CUi::ms_LinearScrollbarScale, Flags: CUi::SCROLLBAR_OPTION_NOCLAMPVALUE);
2693
2694 Right.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Right);
2695 if(DoButton_CheckBox(pId: &g_Config.m_ClRaceGhost, pText: Localize(pStr: "Enable ghost"), Checked: g_Config.m_ClRaceGhost, pRect: &Button))
2696 {
2697 g_Config.m_ClRaceGhost ^= 1;
2698 }
2699 GameClient()->m_Tooltips.DoToolTip(pId: &g_Config.m_ClRaceGhost, pNearRect: &Button, pText: Localize(pStr: "When you cross the start line, show a ghost tee replicating the movements of your best time"));
2700
2701 if(g_Config.m_ClRaceGhost)
2702 {
2703 Right.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Right);
2704 Button.VSplitMid(pLeft: &LeftLeft, pRight: &Button);
2705 if(DoButton_CheckBox(pId: &g_Config.m_ClRaceShowGhost, pText: Localize(pStr: "Show ghost"), Checked: g_Config.m_ClRaceShowGhost, pRect: &LeftLeft))
2706 {
2707 g_Config.m_ClRaceShowGhost ^= 1;
2708 }
2709 Ui()->DoScrollbarOption(pId: &g_Config.m_ClRaceGhostAlpha, pOption: &g_Config.m_ClRaceGhostAlpha, pRect: &Button, pStr: Localize(pStr: "Opacity"), Min: 0, Max: 100, pScale: &CUi::ms_LinearScrollbarScale, Flags: 0u, pSuffix: "%");
2710
2711 Right.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Right);
2712 if(DoButton_CheckBox(pId: &g_Config.m_ClRaceSaveGhost, pText: Localize(pStr: "Save ghost"), Checked: g_Config.m_ClRaceSaveGhost, pRect: &Button))
2713 {
2714 g_Config.m_ClRaceSaveGhost ^= 1;
2715 }
2716
2717 if(g_Config.m_ClRaceSaveGhost)
2718 {
2719 Right.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Right);
2720 if(DoButton_CheckBox(pId: &g_Config.m_ClRaceGhostSaveBest, pText: Localize(pStr: "Only save improvements"), Checked: g_Config.m_ClRaceGhostSaveBest, pRect: &Button))
2721 {
2722 g_Config.m_ClRaceGhostSaveBest ^= 1;
2723 }
2724 }
2725 }
2726
2727 // gameplay
2728 CUIRect Gameplay;
2729 MainView.HSplitTop(Cut: 150.0f, pTop: &Gameplay, pBottom: &MainView);
2730 Gameplay.HSplitTop(Cut: 30.0f, pTop: &Label, pBottom: &Gameplay);
2731 Ui()->DoLabel(pRect: &Label, pText: Localize(pStr: "Gameplay"), Size: 20.0f, Align: TEXTALIGN_ML);
2732 Gameplay.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &Gameplay);
2733 Gameplay.VSplitMid(pLeft: &Left, pRight: &Right, Spacing: 20.0f);
2734
2735 Left.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Left);
2736 Ui()->DoScrollbarOption(pId: &g_Config.m_ClOverlayEntities, pOption: &g_Config.m_ClOverlayEntities, pRect: &Button, pStr: Localize(pStr: "Overlay entities"), Min: 0, Max: 100);
2737
2738 Left.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Left);
2739 Button.VSplitMid(pLeft: &LeftLeft, pRight: &Button);
2740
2741 if(DoButton_CheckBox(pId: &g_Config.m_ClTextEntities, pText: Localize(pStr: "Show text entities"), Checked: g_Config.m_ClTextEntities, pRect: &LeftLeft))
2742 g_Config.m_ClTextEntities ^= 1;
2743
2744 if(g_Config.m_ClTextEntities)
2745 {
2746 if(Ui()->DoScrollbarOption(pId: &g_Config.m_ClTextEntitiesSize, pOption: &g_Config.m_ClTextEntitiesSize, pRect: &Button, pStr: Localize(pStr: "Size"), Min: 20, Max: 100, pScale: &CUi::ms_LinearScrollbarScale, Flags: CUi::SCROLLBAR_OPTION_DELAYUPDATE))
2747 GameClient()->m_MapImages.SetTextureScale(g_Config.m_ClTextEntitiesSize);
2748 }
2749
2750 Left.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Left);
2751 Button.VSplitMid(pLeft: &LeftLeft, pRight: &Button);
2752
2753 if(DoButton_CheckBox(pId: &g_Config.m_ClShowOthers, pText: Localize(pStr: "Show others"), Checked: g_Config.m_ClShowOthers == SHOW_OTHERS_ON, pRect: &LeftLeft))
2754 g_Config.m_ClShowOthers = g_Config.m_ClShowOthers != SHOW_OTHERS_ON ? SHOW_OTHERS_ON : SHOW_OTHERS_OFF;
2755
2756 Ui()->DoScrollbarOption(pId: &g_Config.m_ClShowOthersAlpha, pOption: &g_Config.m_ClShowOthersAlpha, pRect: &Button, pStr: Localize(pStr: "Opacity"), Min: 0, Max: 100, pScale: &CUi::ms_LinearScrollbarScale, Flags: 0u, pSuffix: "%");
2757
2758 GameClient()->m_Tooltips.DoToolTip(pId: &g_Config.m_ClShowOthersAlpha, pNearRect: &Button, pText: Localize(pStr: "Adjust the opacity of entities belonging to other teams, such as tees and name plates"));
2759
2760 Left.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Left);
2761 static int s_ShowOwnTeamId = 0;
2762 if(DoButton_CheckBox(pId: &s_ShowOwnTeamId, pText: Localize(pStr: "Show others (own team only)"), Checked: g_Config.m_ClShowOthers == SHOW_OTHERS_ONLY_TEAM, pRect: &Button))
2763 {
2764 g_Config.m_ClShowOthers = g_Config.m_ClShowOthers != SHOW_OTHERS_ONLY_TEAM ? SHOW_OTHERS_ONLY_TEAM : SHOW_OTHERS_OFF;
2765 }
2766
2767 Left.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Left);
2768 if(DoButton_CheckBox(pId: &g_Config.m_ClShowQuads, pText: Localize(pStr: "Show background quads"), Checked: g_Config.m_ClShowQuads, pRect: &Button))
2769 {
2770 g_Config.m_ClShowQuads ^= 1;
2771 }
2772 GameClient()->m_Tooltips.DoToolTip(pId: &g_Config.m_ClShowQuads, pNearRect: &Button, pText: Localize(pStr: "Quads are used for background decoration"));
2773
2774 Right.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Right);
2775 if(Ui()->DoScrollbarOption(pId: &g_Config.m_ClDefaultZoom, pOption: &g_Config.m_ClDefaultZoom, pRect: &Button, pStr: Localize(pStr: "Default zoom"), Min: 0, Max: 20))
2776 GameClient()->m_Camera.SetZoom(Target: CCamera::ZoomStepsToValue(Steps: g_Config.m_ClDefaultZoom - 10), Smoothness: g_Config.m_ClSmoothZoomTime, IsUser: true);
2777
2778 Right.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Right);
2779 if(DoButton_CheckBox(pId: &g_Config.m_ClAntiPing, pText: Localize(pStr: "AntiPing"), Checked: g_Config.m_ClAntiPing, pRect: &Button))
2780 {
2781 g_Config.m_ClAntiPing ^= 1;
2782 }
2783 GameClient()->m_Tooltips.DoToolTip(pId: &g_Config.m_ClAntiPing, pNearRect: &Button, pText: Localize(pStr: "Tries to predict other entities to give a feel of low latency"));
2784
2785 if(g_Config.m_ClAntiPing)
2786 {
2787 Right.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Right);
2788 if(DoButton_CheckBox(pId: &g_Config.m_ClAntiPingPlayers, pText: Localize(pStr: "AntiPing: predict other players"), Checked: g_Config.m_ClAntiPingPlayers, pRect: &Button))
2789 {
2790 g_Config.m_ClAntiPingPlayers ^= 1;
2791 }
2792
2793 Right.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Right);
2794 if(DoButton_CheckBox(pId: &g_Config.m_ClAntiPingWeapons, pText: Localize(pStr: "AntiPing: predict weapons"), Checked: g_Config.m_ClAntiPingWeapons, pRect: &Button))
2795 {
2796 g_Config.m_ClAntiPingWeapons ^= 1;
2797 }
2798
2799 Right.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Right);
2800 if(DoButton_CheckBox(pId: &g_Config.m_ClAntiPingGrenade, pText: Localize(pStr: "AntiPing: predict grenade paths"), Checked: g_Config.m_ClAntiPingGrenade, pRect: &Button))
2801 {
2802 g_Config.m_ClAntiPingGrenade ^= 1;
2803 }
2804 Right.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Right);
2805 Ui()->DoScrollbarOption(pId: &g_Config.m_ClPredictionMargin, pOption: &g_Config.m_ClPredictionMargin, pRect: &Button, pStr: Localize(pStr: "AntiPing: prediction margin"), Min: 1, Max: 300);
2806 }
2807
2808 CUIRect Background, Miscellaneous;
2809 MainView.VSplitMid(pLeft: &Background, pRight: &Miscellaneous, Spacing: 20.0f);
2810
2811 // background
2812 Background.HSplitTop(Cut: 30.0f, pTop: &Label, pBottom: &Background);
2813 Background.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &Background);
2814 Ui()->DoLabel(pRect: &Label, pText: Localize(pStr: "Background"), Size: 20.0f, Align: TEXTALIGN_ML);
2815
2816 ColorRGBA GreyDefault(0.5f, 0.5f, 0.5f, 1);
2817
2818 static CButtonContainer s_ResetId1;
2819 DoLine_ColorPicker(pResetId: &s_ResetId1, LineSize: 25.0f, LabelSize: 13.0f, BottomMargin: 5.0f, pMainRect: &Background, pText: Localize(pStr: "Regular background color"), pColorValue: &g_Config.m_ClBackgroundColor, DefaultColor: GreyDefault, CheckBoxSpacing: false);
2820
2821 static CButtonContainer s_ResetId2;
2822 DoLine_ColorPicker(pResetId: &s_ResetId2, LineSize: 25.0f, LabelSize: 13.0f, BottomMargin: 5.0f, pMainRect: &Background, pText: Localize(pStr: "Entities background color"), pColorValue: &g_Config.m_ClBackgroundEntitiesColor, DefaultColor: GreyDefault, CheckBoxSpacing: false);
2823
2824 CUIRect EditBox, ReloadButton;
2825 Background.HSplitTop(Cut: 20.0f, pTop: &Label, pBottom: &Background);
2826 Background.HSplitTop(Cut: 2.0f, pTop: nullptr, pBottom: &Background);
2827 Label.VSplitLeft(Cut: 100.0f, pLeft: &Label, pRight: &EditBox);
2828 EditBox.VSplitRight(Cut: 60.0f, pLeft: &EditBox, pRight: &Button);
2829 Button.VSplitMid(pLeft: &ReloadButton, pRight: &Button, Spacing: 5.0f);
2830 EditBox.VSplitRight(Cut: 5.0f, pLeft: &EditBox, pRight: nullptr);
2831
2832 Ui()->DoLabel(pRect: &Label, pText: Localize(pStr: "Map"), Size: 14.0f, Align: TEXTALIGN_ML);
2833
2834 static CLineInput s_BackgroundEntitiesInput(g_Config.m_ClBackgroundEntities, sizeof(g_Config.m_ClBackgroundEntities));
2835 Ui()->DoEditBox(pLineInput: &s_BackgroundEntitiesInput, pRect: &EditBox, FontSize: 14.0f);
2836
2837 static CButtonContainer s_BackgroundEntitiesMapPicker, s_BackgroundEntitiesReload;
2838
2839 if(Ui()->DoButton_FontIcon(pButtonContainer: &s_BackgroundEntitiesReload, pText: FONT_ICON_ARROW_ROTATE_RIGHT, Checked: 0, pRect: &ReloadButton, Flags: BUTTONFLAG_LEFT))
2840 {
2841 GameClient()->m_Background.LoadBackground();
2842 }
2843
2844 if(Ui()->DoButton_FontIcon(pButtonContainer: &s_BackgroundEntitiesMapPicker, pText: FONT_ICON_FOLDER, Checked: 0, pRect: &Button, Flags: BUTTONFLAG_LEFT))
2845 {
2846 static SPopupMenuId s_PopupMapPickerId;
2847 static CPopupMapPickerContext s_PopupMapPickerContext;
2848 s_PopupMapPickerContext.m_pMenus = this;
2849 s_PopupMapPickerContext.MapListPopulate();
2850 Ui()->DoPopupMenu(pId: &s_PopupMapPickerId, X: Ui()->MouseX(), Y: Ui()->MouseY(), Width: 300.0f, Height: 250.0f, pContext: &s_PopupMapPickerContext, pfnFunc: PopupMapPicker);
2851 }
2852
2853 Background.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Background);
2854 const bool UseCurrentMap = str_comp(a: g_Config.m_ClBackgroundEntities, CURRENT_MAP) == 0;
2855 static int s_UseCurrentMapId = 0;
2856 if(DoButton_CheckBox(pId: &s_UseCurrentMapId, pText: Localize(pStr: "Use current map as background"), Checked: UseCurrentMap, pRect: &Button))
2857 {
2858 if(UseCurrentMap)
2859 g_Config.m_ClBackgroundEntities[0] = '\0';
2860 else
2861 str_copy(dst&: g_Config.m_ClBackgroundEntities, CURRENT_MAP);
2862 GameClient()->m_Background.LoadBackground();
2863 }
2864
2865 Background.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Background);
2866 if(DoButton_CheckBox(pId: &g_Config.m_ClBackgroundShowTilesLayers, pText: Localize(pStr: "Show tiles layers from BG map"), Checked: g_Config.m_ClBackgroundShowTilesLayers, pRect: &Button))
2867 g_Config.m_ClBackgroundShowTilesLayers ^= 1;
2868
2869 // miscellaneous
2870 Miscellaneous.HSplitTop(Cut: 30.0f, pTop: &Label, pBottom: &Miscellaneous);
2871 Miscellaneous.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &Miscellaneous);
2872
2873 Ui()->DoLabel(pRect: &Label, pText: Localize(pStr: "Miscellaneous"), Size: 20.0f, Align: TEXTALIGN_ML);
2874
2875 static CButtonContainer s_ButtonTimeout;
2876 Miscellaneous.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Miscellaneous);
2877 if(DoButton_Menu(pButtonContainer: &s_ButtonTimeout, pText: Localize(pStr: "New random timeout code"), Checked: 0, pRect: &Button))
2878 {
2879 Client()->GenerateTimeoutSeed();
2880 }
2881
2882 Miscellaneous.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &Miscellaneous);
2883 Miscellaneous.HSplitTop(Cut: 20.0f, pTop: &Label, pBottom: &Miscellaneous);
2884 Miscellaneous.HSplitTop(Cut: 2.0f, pTop: nullptr, pBottom: &Miscellaneous);
2885 Ui()->DoLabel(pRect: &Label, pText: Localize(pStr: "Run on join"), Size: 14.0f, Align: TEXTALIGN_ML);
2886 Miscellaneous.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Miscellaneous);
2887 static CLineInput s_RunOnJoinInput(g_Config.m_ClRunOnJoin, sizeof(g_Config.m_ClRunOnJoin));
2888 s_RunOnJoinInput.SetEmptyText(Localize(pStr: "Chat command (e.g. showall 1)"));
2889 Ui()->DoEditBox(pLineInput: &s_RunOnJoinInput, pRect: &Button, FontSize: 14.0f);
2890
2891#if defined(CONF_FAMILY_WINDOWS)
2892 static CButtonContainer s_ButtonUnregisterShell;
2893 Miscellaneous.HSplitTop(10.0f, nullptr, &Miscellaneous);
2894 Miscellaneous.HSplitTop(20.0f, &Button, &Miscellaneous);
2895 if(DoButton_Menu(&s_ButtonUnregisterShell, Localize("Unregister protocol and file extensions"), 0, &Button))
2896 {
2897 Client()->ShellUnregister();
2898 }
2899#endif
2900
2901 // Updater
2902#if defined(CONF_AUTOUPDATE)
2903 {
2904 bool NeedUpdate = str_comp(a: Client()->LatestVersion(), b: "0");
2905 IUpdater::EUpdaterState State = Updater()->GetCurrentState();
2906
2907 // Update Button
2908 char aBuf[256];
2909 if(NeedUpdate && State <= IUpdater::CLEAN)
2910 {
2911 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: Localize(pStr: "DDNet %s is available:"), Client()->LatestVersion());
2912 UpdaterRect.VSplitLeft(Cut: TextRender()->TextWidth(Size: 14.0f, pText: aBuf, StrLength: -1, LineWidth: -1.0f) + 10.0f, pLeft: &UpdaterRect, pRight: &Button);
2913 Button.VSplitLeft(Cut: 100.0f, pLeft: &Button, pRight: nullptr);
2914 static CButtonContainer s_ButtonUpdate;
2915 if(DoButton_Menu(pButtonContainer: &s_ButtonUpdate, pText: Localize(pStr: "Update now"), Checked: 0, pRect: &Button))
2916 {
2917 Updater()->InitiateUpdate();
2918 }
2919 }
2920 else if(State >= IUpdater::GETTING_MANIFEST && State < IUpdater::NEED_RESTART)
2921 str_copy(dst&: aBuf, src: Localize(pStr: "Updating…"));
2922 else if(State == IUpdater::NEED_RESTART)
2923 {
2924 str_copy(dst&: aBuf, src: Localize(pStr: "DDNet Client updated!"));
2925 m_NeedRestartUpdate = true;
2926 }
2927 else
2928 {
2929 str_copy(dst&: aBuf, src: Localize(pStr: "No updates available"));
2930 UpdaterRect.VSplitLeft(Cut: TextRender()->TextWidth(Size: 14.0f, pText: aBuf, StrLength: -1, LineWidth: -1.0f) + 10.0f, pLeft: &UpdaterRect, pRight: &Button);
2931 Button.VSplitLeft(Cut: 100.0f, pLeft: &Button, pRight: nullptr);
2932 static CButtonContainer s_ButtonUpdate;
2933 if(DoButton_Menu(pButtonContainer: &s_ButtonUpdate, pText: Localize(pStr: "Check now"), Checked: 0, pRect: &Button))
2934 {
2935 Client()->RequestDDNetInfo();
2936 }
2937 }
2938 Ui()->DoLabel(pRect: &UpdaterRect, pText: aBuf, Size: 14.0f, Align: TEXTALIGN_ML);
2939 TextRender()->TextColor(r: 1.0f, g: 1.0f, b: 1.0f, a: 1.0f);
2940 }
2941#endif
2942}
2943
2944CUi::EPopupMenuFunctionResult CMenus::PopupMapPicker(void *pContext, CUIRect View, bool Active)
2945{
2946 CPopupMapPickerContext *pPopupContext = static_cast<CPopupMapPickerContext *>(pContext);
2947 CMenus *pMenus = pPopupContext->m_pMenus;
2948
2949 static CListBox s_ListBox;
2950 s_ListBox.SetActive(Active);
2951 s_ListBox.DoStart(RowHeight: 20.0f, NumItems: pPopupContext->m_vMaps.size(), ItemsPerRow: 1, RowsPerScroll: 3, SelectedIndex: -1, pRect: &View, Background: false);
2952
2953 int MapIndex = 0;
2954 for(auto &Map : pPopupContext->m_vMaps)
2955 {
2956 MapIndex++;
2957 const CListboxItem Item = s_ListBox.DoNextItem(pId: &Map, Selected: MapIndex == pPopupContext->m_Selection);
2958 if(!Item.m_Visible)
2959 continue;
2960
2961 CUIRect Label, Icon;
2962 Item.m_Rect.VSplitLeft(Cut: 20.0f, pLeft: &Icon, pRight: &Label);
2963
2964 char aLabelText[IO_MAX_PATH_LENGTH];
2965 str_copy(dst&: aLabelText, src: Map.m_aFilename);
2966 if(Map.m_IsDirectory)
2967 str_append(dst: aLabelText, src: "/", dst_size: sizeof(aLabelText));
2968
2969 const char *pIconType;
2970 if(!Map.m_IsDirectory)
2971 {
2972 pIconType = FONT_ICON_MAP;
2973 }
2974 else
2975 {
2976 if(!str_comp(a: Map.m_aFilename, b: ".."))
2977 pIconType = FONT_ICON_FOLDER_TREE;
2978 else
2979 pIconType = FONT_ICON_FOLDER;
2980 }
2981
2982 pMenus->TextRender()->SetFontPreset(EFontPreset::ICON_FONT);
2983 pMenus->TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING);
2984 pMenus->Ui()->DoLabel(pRect: &Icon, pText: pIconType, Size: 12.0f, Align: TEXTALIGN_ML);
2985 pMenus->TextRender()->SetRenderFlags(0);
2986 pMenus->TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT);
2987
2988 pMenus->Ui()->DoLabel(pRect: &Label, pText: aLabelText, Size: 10.0f, Align: TEXTALIGN_ML);
2989 }
2990
2991 const int NewSelected = s_ListBox.DoEnd();
2992 pPopupContext->m_Selection = NewSelected >= 0 ? NewSelected : -1;
2993 if(s_ListBox.WasItemSelected() || s_ListBox.WasItemActivated())
2994 {
2995 const CMapListItem &SelectedItem = pPopupContext->m_vMaps[pPopupContext->m_Selection];
2996
2997 if(SelectedItem.m_IsDirectory)
2998 {
2999 if(!str_comp(a: SelectedItem.m_aFilename, b: ".."))
3000 {
3001 fs_parent_dir(path: pPopupContext->m_aCurrentMapFolder);
3002 }
3003 else
3004 {
3005 str_append(dst: pPopupContext->m_aCurrentMapFolder, src: "/", dst_size: sizeof(pPopupContext->m_aCurrentMapFolder));
3006 str_append(dst: pPopupContext->m_aCurrentMapFolder, src: SelectedItem.m_aFilename, dst_size: sizeof(pPopupContext->m_aCurrentMapFolder));
3007 }
3008 pPopupContext->MapListPopulate();
3009 }
3010 else
3011 {
3012 str_format(buffer: g_Config.m_ClBackgroundEntities, buffer_size: sizeof(g_Config.m_ClBackgroundEntities), format: "%s/%s", pPopupContext->m_aCurrentMapFolder, SelectedItem.m_aFilename);
3013 pMenus->GameClient()->m_Background.LoadBackground();
3014 return CUi::POPUP_CLOSE_CURRENT;
3015 }
3016 }
3017
3018 return CUi::POPUP_KEEP_OPEN;
3019}
3020
3021void CMenus::CPopupMapPickerContext::MapListPopulate()
3022{
3023 m_vMaps.clear();
3024 char aTemp[IO_MAX_PATH_LENGTH];
3025 str_format(buffer: aTemp, buffer_size: sizeof(aTemp), format: "maps/%s", m_aCurrentMapFolder);
3026 m_pMenus->Storage()->ListDirectoryInfo(Type: IStorage::TYPE_ALL, pPath: aTemp, pfnCallback: MapListFetchCallback, pUser: this);
3027 std::stable_sort(first: m_vMaps.begin(), last: m_vMaps.end(), comp: CompareFilenameAscending);
3028 m_Selection = -1;
3029}
3030
3031int CMenus::CPopupMapPickerContext::MapListFetchCallback(const CFsFileInfo *pInfo, int IsDir, int StorageType, void *pUser)
3032{
3033 CPopupMapPickerContext *pRealUser = (CPopupMapPickerContext *)pUser;
3034 if((!IsDir && !str_endswith(str: pInfo->m_pName, suffix: ".map")) || !str_comp(a: pInfo->m_pName, b: ".") || (!str_comp(a: pInfo->m_pName, b: "..") && (!str_comp(a: pRealUser->m_aCurrentMapFolder, b: ""))))
3035 return 0;
3036
3037 CMapListItem Item;
3038 str_copy(dst&: Item.m_aFilename, src: pInfo->m_pName);
3039 Item.m_IsDirectory = IsDir;
3040
3041 pRealUser->m_vMaps.emplace_back(args&: Item);
3042
3043 return 0;
3044}
3045