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 <base/log.h> |
4 | #include <base/math.h> |
5 | #include <base/system.h> |
6 | |
7 | #include <engine/graphics.h> |
8 | #include <engine/shared/config.h> |
9 | #include <engine/shared/linereader.h> |
10 | #include <engine/shared/localization.h> |
11 | #include <engine/storage.h> |
12 | #include <engine/textrender.h> |
13 | #include <engine/updater.h> |
14 | |
15 | #include <game/generated/protocol.h> |
16 | |
17 | #include <game/client/animstate.h> |
18 | #include <game/client/components/chat.h> |
19 | #include <game/client/components/menu_background.h> |
20 | #include <game/client/components/sounds.h> |
21 | #include <game/client/gameclient.h> |
22 | #include <game/client/render.h> |
23 | #include <game/client/skin.h> |
24 | #include <game/client/ui.h> |
25 | #include <game/client/ui_listbox.h> |
26 | #include <game/client/ui_scrollregion.h> |
27 | #include <game/localization.h> |
28 | |
29 | #include "binds.h" |
30 | #include "countryflags.h" |
31 | #include "menus.h" |
32 | #include "skins.h" |
33 | |
34 | #include <array> |
35 | #include <chrono> |
36 | #include <memory> |
37 | #include <numeric> |
38 | #include <string> |
39 | #include <vector> |
40 | |
41 | using namespace FontIcons; |
42 | using namespace std::chrono_literals; |
43 | |
44 | CMenusKeyBinder CMenus::; |
45 | |
46 | CMenusKeyBinder::() |
47 | { |
48 | m_pKeyReaderId = nullptr; |
49 | m_TakeKey = false; |
50 | m_GotKey = false; |
51 | m_ModifierCombination = CBinds::MODIFIER_NONE; |
52 | } |
53 | |
54 | bool CMenusKeyBinder::(const IInput::CEvent &Event) |
55 | { |
56 | if(m_TakeKey) |
57 | { |
58 | int TriggeringEvent = (Event.m_Key == KEY_MOUSE_1) ? IInput::FLAG_PRESS : IInput::FLAG_RELEASE; |
59 | if(Event.m_Flags & TriggeringEvent) |
60 | { |
61 | m_Key = Event; |
62 | m_GotKey = true; |
63 | m_TakeKey = false; |
64 | |
65 | m_ModifierCombination = CBinds::GetModifierMask(pInput: Input()); |
66 | if(m_ModifierCombination == CBinds::GetModifierMaskOfKey(Key: Event.m_Key)) |
67 | { |
68 | m_ModifierCombination = CBinds::MODIFIER_NONE; |
69 | } |
70 | } |
71 | return true; |
72 | } |
73 | |
74 | return false; |
75 | } |
76 | |
77 | void CMenus::(CUIRect MainView) |
78 | { |
79 | char aBuf[128 + IO_MAX_PATH_LENGTH]; |
80 | CUIRect Label, Button, Left, Right, Game, ClientSettings; |
81 | MainView.HSplitTop(Cut: 150.0f, pTop: &Game, pBottom: &ClientSettings); |
82 | |
83 | // game |
84 | { |
85 | // headline |
86 | Game.HSplitTop(Cut: 30.0f, pTop: &Label, pBottom: &Game); |
87 | Ui()->DoLabel(pRect: &Label, pText: Localize(pStr: "Game" ), Size: 20.0f, Align: TEXTALIGN_ML); |
88 | Game.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &Game); |
89 | Game.VSplitMid(pLeft: &Left, pRight: nullptr, Spacing: 20.0f); |
90 | |
91 | // dynamic camera |
92 | Left.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Left); |
93 | const bool IsDyncam = g_Config.m_ClDyncam || g_Config.m_ClMouseFollowfactor > 0; |
94 | if(DoButton_CheckBox(pId: &g_Config.m_ClDyncam, pText: Localize(pStr: "Dynamic Camera" ), Checked: IsDyncam, pRect: &Button)) |
95 | { |
96 | if(IsDyncam) |
97 | { |
98 | g_Config.m_ClDyncam = 0; |
99 | g_Config.m_ClMouseFollowfactor = 0; |
100 | } |
101 | else |
102 | { |
103 | g_Config.m_ClDyncam = 1; |
104 | } |
105 | } |
106 | |
107 | // smooth dynamic camera |
108 | Left.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &Left); |
109 | Left.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Left); |
110 | if(g_Config.m_ClDyncam) |
111 | { |
112 | if(DoButton_CheckBox(pId: &g_Config.m_ClDyncamSmoothness, pText: Localize(pStr: "Smooth Dynamic Camera" ), Checked: g_Config.m_ClDyncamSmoothness, pRect: &Button)) |
113 | { |
114 | if(g_Config.m_ClDyncamSmoothness) |
115 | { |
116 | g_Config.m_ClDyncamSmoothness = 0; |
117 | } |
118 | else |
119 | { |
120 | g_Config.m_ClDyncamSmoothness = 50; |
121 | g_Config.m_ClDyncamStabilizing = 50; |
122 | } |
123 | } |
124 | } |
125 | |
126 | // weapon pickup |
127 | Left.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &Left); |
128 | Left.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Left); |
129 | if(DoButton_CheckBox(pId: &g_Config.m_ClAutoswitchWeapons, pText: Localize(pStr: "Switch weapon on pickup" ), Checked: g_Config.m_ClAutoswitchWeapons, pRect: &Button)) |
130 | g_Config.m_ClAutoswitchWeapons ^= 1; |
131 | |
132 | // weapon out of ammo autoswitch |
133 | Left.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &Left); |
134 | Left.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Left); |
135 | if(DoButton_CheckBox(pId: &g_Config.m_ClAutoswitchWeaponsOutOfAmmo, pText: Localize(pStr: "Switch weapon when out of ammo" ), Checked: g_Config.m_ClAutoswitchWeaponsOutOfAmmo, pRect: &Button)) |
136 | g_Config.m_ClAutoswitchWeaponsOutOfAmmo ^= 1; |
137 | } |
138 | |
139 | // client |
140 | { |
141 | // headline |
142 | ClientSettings.HSplitTop(Cut: 30.0f, pTop: &Label, pBottom: &ClientSettings); |
143 | Ui()->DoLabel(pRect: &Label, pText: Localize(pStr: "Client" ), Size: 20.0f, Align: TEXTALIGN_ML); |
144 | ClientSettings.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &ClientSettings); |
145 | ClientSettings.VSplitMid(pLeft: &Left, pRight: &Right, Spacing: 20.0f); |
146 | |
147 | // skip main menu |
148 | Left.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Left); |
149 | if(DoButton_CheckBox(pId: &g_Config.m_ClSkipStartMenu, pText: Localize(pStr: "Skip the main menu" ), Checked: g_Config.m_ClSkipStartMenu, pRect: &Button)) |
150 | g_Config.m_ClSkipStartMenu ^= 1; |
151 | |
152 | Left.HSplitTop(Cut: 10.0f, pTop: nullptr, pBottom: &Left); |
153 | Left.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Left); |
154 | Ui()->DoScrollbarOption(pId: &g_Config.m_ClRefreshRate, pOption: &g_Config.m_ClRefreshRate, pRect: &Button, pStr: Localize(pStr: "Refresh Rate" ), Min: 10, Max: 10000, pScale: &CUi::ms_LogarithmicScrollbarScale, Flags: CUi::SCROLLBAR_OPTION_INFINITE, pSuffix: " Hz" ); |
155 | Left.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &Left); |
156 | Left.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Left); |
157 | static int s_LowerRefreshRate; |
158 | 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)) |
159 | g_Config.m_ClRefreshRate = g_Config.m_ClRefreshRate > 480 || g_Config.m_ClRefreshRate == 0 ? 480 : 0; |
160 | |
161 | CUIRect SettingsButton; |
162 | Left.HSplitBottom(Cut: 20.0f, pTop: &Left, pBottom: &SettingsButton); |
163 | Left.HSplitBottom(Cut: 5.0f, pTop: &Left, pBottom: nullptr); |
164 | static CButtonContainer s_SettingsButtonId; |
165 | if(DoButton_Menu(pButtonContainer: &s_SettingsButtonId, pText: Localize(pStr: "Settings file" ), Checked: 0, pRect: &SettingsButton)) |
166 | { |
167 | Storage()->GetCompletePath(Type: IStorage::TYPE_SAVE, CONFIG_FILE, pBuffer: aBuf, BufferSize: sizeof(aBuf)); |
168 | Client()->ViewFile(pFilename: aBuf); |
169 | } |
170 | GameClient()->m_Tooltips.DoToolTip(pId: &s_SettingsButtonId, pNearRect: &SettingsButton, pText: Localize(pStr: "Open the settings file" )); |
171 | |
172 | CUIRect ConfigButton; |
173 | Left.HSplitBottom(Cut: 20.0f, pTop: &Left, pBottom: &ConfigButton); |
174 | Left.HSplitBottom(Cut: 5.0f, pTop: &Left, pBottom: nullptr); |
175 | static CButtonContainer s_ConfigButtonId; |
176 | if(DoButton_Menu(pButtonContainer: &s_ConfigButtonId, pText: Localize(pStr: "Config directory" ), Checked: 0, pRect: &ConfigButton)) |
177 | { |
178 | Storage()->GetCompletePath(Type: IStorage::TYPE_SAVE, pDir: "" , pBuffer: aBuf, BufferSize: sizeof(aBuf)); |
179 | Client()->ViewFile(pFilename: aBuf); |
180 | } |
181 | GameClient()->m_Tooltips.DoToolTip(pId: &s_ConfigButtonId, pNearRect: &ConfigButton, pText: Localize(pStr: "Open the directory that contains the configuration and user files" )); |
182 | |
183 | CUIRect DirectoryButton; |
184 | Left.HSplitBottom(Cut: 20.0f, pTop: &Left, pBottom: &DirectoryButton); |
185 | Left.HSplitBottom(Cut: 5.0f, pTop: &Left, pBottom: nullptr); |
186 | static CButtonContainer s_ThemesButtonId; |
187 | if(DoButton_Menu(pButtonContainer: &s_ThemesButtonId, pText: Localize(pStr: "Themes directory" ), Checked: 0, pRect: &DirectoryButton)) |
188 | { |
189 | Storage()->GetCompletePath(Type: IStorage::TYPE_SAVE, pDir: "themes" , pBuffer: aBuf, BufferSize: sizeof(aBuf)); |
190 | Storage()->CreateFolder(pFoldername: "themes" , Type: IStorage::TYPE_SAVE); |
191 | Client()->ViewFile(pFilename: aBuf); |
192 | } |
193 | GameClient()->m_Tooltips.DoToolTip(pId: &s_ThemesButtonId, pNearRect: &DirectoryButton, pText: Localize(pStr: "Open the directory to add custom themes" )); |
194 | |
195 | Left.HSplitTop(Cut: 20.0f, pTop: nullptr, pBottom: &Left); |
196 | RenderThemeSelection(MainView: Left); |
197 | |
198 | // auto demo settings |
199 | { |
200 | Right.HSplitTop(Cut: 40.0f, pTop: nullptr, pBottom: &Right); |
201 | Right.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Right); |
202 | if(DoButton_CheckBox(pId: &g_Config.m_ClAutoDemoRecord, pText: Localize(pStr: "Automatically record demos" ), Checked: g_Config.m_ClAutoDemoRecord, pRect: &Button)) |
203 | g_Config.m_ClAutoDemoRecord ^= 1; |
204 | |
205 | Right.HSplitTop(Cut: 2 * 20.0f, pTop: &Button, pBottom: &Right); |
206 | if(g_Config.m_ClAutoDemoRecord) |
207 | 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); |
208 | |
209 | Right.HSplitTop(Cut: 10.0f, pTop: nullptr, pBottom: &Right); |
210 | Right.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Right); |
211 | if(DoButton_CheckBox(pId: &g_Config.m_ClAutoScreenshot, pText: Localize(pStr: "Automatically take game over screenshot" ), Checked: g_Config.m_ClAutoScreenshot, pRect: &Button)) |
212 | g_Config.m_ClAutoScreenshot ^= 1; |
213 | |
214 | Right.HSplitTop(Cut: 2 * 20.0f, pTop: &Button, pBottom: &Right); |
215 | if(g_Config.m_ClAutoScreenshot) |
216 | 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); |
217 | } |
218 | |
219 | // auto statboard screenshot |
220 | { |
221 | Right.HSplitTop(Cut: 10.0f, pTop: nullptr, pBottom: &Right); |
222 | Right.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Right); |
223 | if(DoButton_CheckBox(pId: &g_Config.m_ClAutoStatboardScreenshot, pText: Localize(pStr: "Automatically take statboard screenshot" ), Checked: g_Config.m_ClAutoStatboardScreenshot, pRect: &Button)) |
224 | { |
225 | g_Config.m_ClAutoStatboardScreenshot ^= 1; |
226 | } |
227 | |
228 | Right.HSplitTop(Cut: 2 * 20.0f, pTop: &Button, pBottom: &Right); |
229 | if(g_Config.m_ClAutoStatboardScreenshot) |
230 | 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); |
231 | } |
232 | |
233 | // auto statboard csv |
234 | { |
235 | Right.HSplitTop(Cut: 10.0f, pTop: nullptr, pBottom: &Right); |
236 | Right.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Right); |
237 | if(DoButton_CheckBox(pId: &g_Config.m_ClAutoCSV, pText: Localize(pStr: "Automatically create statboard csv" ), Checked: g_Config.m_ClAutoCSV, pRect: &Button)) |
238 | { |
239 | g_Config.m_ClAutoCSV ^= 1; |
240 | } |
241 | |
242 | Right.HSplitTop(Cut: 2 * 20.0f, pTop: &Button, pBottom: &Right); |
243 | if(g_Config.m_ClAutoCSV) |
244 | 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); |
245 | } |
246 | } |
247 | } |
248 | |
249 | void CMenus::() |
250 | { |
251 | if(m_Dummy) |
252 | m_NeedSendDummyinfo = true; |
253 | else |
254 | m_NeedSendinfo = true; |
255 | } |
256 | |
257 | void CMenus::(CUIRect MainView) |
258 | { |
259 | CUIRect TabBar, PlayerTab, DummyTab, ChangeInfo, QuickSearch, QuickSearchClearButton; |
260 | MainView.HSplitTop(Cut: 20.0f, pTop: &TabBar, pBottom: &MainView); |
261 | TabBar.VSplitMid(pLeft: &TabBar, pRight: &ChangeInfo, Spacing: 20.f); |
262 | TabBar.VSplitMid(pLeft: &PlayerTab, pRight: &DummyTab); |
263 | MainView.HSplitTop(Cut: 10.0f, pTop: nullptr, pBottom: &MainView); |
264 | |
265 | static CButtonContainer s_PlayerTabButton; |
266 | 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)) |
267 | { |
268 | m_Dummy = false; |
269 | } |
270 | |
271 | static CButtonContainer s_DummyTabButton; |
272 | 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)) |
273 | { |
274 | m_Dummy = true; |
275 | } |
276 | |
277 | if(Client()->State() == IClient::STATE_ONLINE && m_pClient->m_NextChangeInfo && m_pClient->m_NextChangeInfo > Client()->GameTick(Conn: g_Config.m_ClDummy)) |
278 | { |
279 | char aChangeInfo[128], aTimeLeft[32]; |
280 | str_format(buffer: aTimeLeft, buffer_size: sizeof(aTimeLeft), format: Localize(pStr: "%ds left" ), (m_pClient->m_NextChangeInfo - Client()->GameTick(Conn: g_Config.m_ClDummy) + Client()->GameTickSpeed() - 1) / Client()->GameTickSpeed()); |
281 | str_format(buffer: aChangeInfo, buffer_size: sizeof(aChangeInfo), format: "%s: %s" , Localize(pStr: "Player info change cooldown" ), aTimeLeft); |
282 | Ui()->DoLabel(pRect: &ChangeInfo, pText: aChangeInfo, Size: 10.f, Align: TEXTALIGN_ML); |
283 | } |
284 | |
285 | static CLineInput s_NameInput; |
286 | static CLineInput s_ClanInput; |
287 | |
288 | int *pCountry; |
289 | if(!m_Dummy) |
290 | { |
291 | pCountry = &g_Config.m_PlayerCountry; |
292 | s_NameInput.SetBuffer(pStr: g_Config.m_PlayerName, MaxSize: sizeof(g_Config.m_PlayerName)); |
293 | s_NameInput.SetEmptyText(Client()->PlayerName()); |
294 | s_ClanInput.SetBuffer(pStr: g_Config.m_PlayerClan, MaxSize: sizeof(g_Config.m_PlayerClan)); |
295 | } |
296 | else |
297 | { |
298 | pCountry = &g_Config.m_ClDummyCountry; |
299 | s_NameInput.SetBuffer(pStr: g_Config.m_ClDummyName, MaxSize: sizeof(g_Config.m_ClDummyName)); |
300 | s_NameInput.SetEmptyText(Client()->DummyName()); |
301 | s_ClanInput.SetBuffer(pStr: g_Config.m_ClDummyClan, MaxSize: sizeof(g_Config.m_ClDummyClan)); |
302 | } |
303 | |
304 | // player name |
305 | CUIRect Button, Label; |
306 | MainView.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MainView); |
307 | Button.VSplitLeft(Cut: 80.0f, pLeft: &Label, pRight: &Button); |
308 | Button.VSplitLeft(Cut: 150.0f, pLeft: &Button, pRight: nullptr); |
309 | char aBuf[128]; |
310 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s:" , Localize(pStr: "Name" )); |
311 | Ui()->DoLabel(pRect: &Label, pText: aBuf, Size: 14.0f, Align: TEXTALIGN_ML); |
312 | if(Ui()->DoEditBox(pLineInput: &s_NameInput, pRect: &Button, FontSize: 14.0f)) |
313 | { |
314 | SetNeedSendInfo(); |
315 | } |
316 | |
317 | // player clan |
318 | MainView.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &MainView); |
319 | MainView.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MainView); |
320 | Button.VSplitLeft(Cut: 80.0f, pLeft: &Label, pRight: &Button); |
321 | Button.VSplitLeft(Cut: 150.0f, pLeft: &Button, pRight: nullptr); |
322 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s:" , Localize(pStr: "Clan" )); |
323 | Ui()->DoLabel(pRect: &Label, pText: aBuf, Size: 14.0f, Align: TEXTALIGN_ML); |
324 | if(Ui()->DoEditBox(pLineInput: &s_ClanInput, pRect: &Button, FontSize: 14.0f)) |
325 | { |
326 | SetNeedSendInfo(); |
327 | } |
328 | |
329 | // country flag selector |
330 | static CLineInputBuffered<25> s_FlagFilterInput; |
331 | |
332 | std::vector<const CCountryFlags::CCountryFlag *> vpFilteredFlags; |
333 | for(size_t i = 0; i < m_pClient->m_CountryFlags.Num(); ++i) |
334 | { |
335 | const CCountryFlags::CCountryFlag *pEntry = m_pClient->m_CountryFlags.GetByIndex(Index: i); |
336 | if(!str_find_nocase(haystack: pEntry->m_aCountryCodeString, needle: s_FlagFilterInput.GetString())) |
337 | continue; |
338 | vpFilteredFlags.push_back(x: pEntry); |
339 | } |
340 | |
341 | MainView.HSplitTop(Cut: 10.0f, pTop: nullptr, pBottom: &MainView); |
342 | MainView.HSplitBottom(Cut: 25.0f, pTop: &MainView, pBottom: &QuickSearch); |
343 | int OldSelected = -1; |
344 | static CListBox s_ListBox; |
345 | s_ListBox.DoStart(RowHeight: 48.0f, NumItems: vpFilteredFlags.size(), ItemsPerRow: 10, RowsPerScroll: 3, SelectedIndex: OldSelected, pRect: &MainView); |
346 | |
347 | for(size_t i = 0; i < vpFilteredFlags.size(); i++) |
348 | { |
349 | const CCountryFlags::CCountryFlag *pEntry = vpFilteredFlags[i]; |
350 | |
351 | if(pEntry->m_CountryCode == *pCountry) |
352 | OldSelected = i; |
353 | |
354 | const CListboxItem Item = s_ListBox.DoNextItem(pId: &pEntry->m_CountryCode, Selected: OldSelected >= 0 && (size_t)OldSelected == i); |
355 | if(!Item.m_Visible) |
356 | continue; |
357 | |
358 | CUIRect FlagRect; |
359 | Item.m_Rect.Margin(Cut: 5.0f, pOtherRect: &FlagRect); |
360 | FlagRect.HSplitBottom(Cut: 12.0f, pTop: &FlagRect, pBottom: &Label); |
361 | Label.HSplitTop(Cut: 2.0f, pTop: nullptr, pBottom: &Label); |
362 | const float OldWidth = FlagRect.w; |
363 | FlagRect.w = FlagRect.h * 2; |
364 | FlagRect.x += (OldWidth - FlagRect.w) / 2.0f; |
365 | m_pClient->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); |
366 | |
367 | if(pEntry->m_Texture.IsValid()) |
368 | { |
369 | Ui()->DoLabel(pRect: &Label, pText: pEntry->m_aCountryCodeString, Size: 10.0f, Align: TEXTALIGN_MC); |
370 | } |
371 | } |
372 | |
373 | const int NewSelected = s_ListBox.DoEnd(); |
374 | if(OldSelected != NewSelected) |
375 | { |
376 | *pCountry = vpFilteredFlags[NewSelected]->m_CountryCode; |
377 | SetNeedSendInfo(); |
378 | } |
379 | |
380 | // render quick search |
381 | QuickSearch.VSplitLeft(Cut: 240.0f, pLeft: &QuickSearch, pRight: nullptr); |
382 | QuickSearch.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &QuickSearch); |
383 | |
384 | TextRender()->SetFontPreset(EFontPreset::ICON_FONT); |
385 | TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE); |
386 | Ui()->DoLabel(pRect: &QuickSearch, pText: FONT_ICON_MAGNIFYING_GLASS, Size: 14.0f, Align: TEXTALIGN_ML); |
387 | TextRender()->SetRenderFlags(0); |
388 | TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); |
389 | |
390 | float SearchWidth = TextRender()->TextWidth(Size: 14.0f, pText: FONT_ICON_MAGNIFYING_GLASS, StrLength: -1, LineWidth: -1.0f); |
391 | QuickSearch.VSplitLeft(Cut: SearchWidth - 1.5f, pLeft: nullptr, pRight: &QuickSearch); |
392 | QuickSearch.VSplitLeft(Cut: 5.0f, pLeft: nullptr, pRight: &QuickSearch); |
393 | QuickSearch.VSplitLeft(Cut: QuickSearch.w - 10.0f, pLeft: &QuickSearch, pRight: &QuickSearchClearButton); |
394 | |
395 | TextRender()->SetRenderFlags(0); |
396 | TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); |
397 | if(Input()->KeyPress(Key: KEY_F) && Input()->ModifierIsPressed()) |
398 | { |
399 | Ui()->SetActiveItem(&s_FlagFilterInput); |
400 | s_FlagFilterInput.SelectAll(); |
401 | } |
402 | s_FlagFilterInput.SetEmptyText(Localize(pStr: "Search" )); |
403 | Ui()->DoClearableEditBox(pLineInput: &s_FlagFilterInput, pRect: &QuickSearch, FontSize: 14.0f); |
404 | } |
405 | |
406 | struct CUISkin |
407 | { |
408 | const CSkin *m_pSkin; |
409 | |
410 | CUISkin() : |
411 | m_pSkin(nullptr) {} |
412 | CUISkin(const CSkin *pSkin) : |
413 | m_pSkin(pSkin) {} |
414 | |
415 | bool operator<(const CUISkin &Other) const { return str_comp_nocase(a: m_pSkin->GetName(), b: Other.m_pSkin->GetName()) < 0; } |
416 | |
417 | bool operator<(const char *pOther) const { return str_comp_nocase(a: m_pSkin->GetName(), b: pOther) < 0; } |
418 | bool operator==(const char *pOther) const { return !str_comp_nocase(a: m_pSkin->GetName(), b: pOther); } |
419 | }; |
420 | |
421 | void CMenus::() |
422 | { |
423 | m_SkinListNeedsUpdate = true; |
424 | } |
425 | |
426 | void CMenus::RandomSkin() |
427 | { |
428 | static const float s_aSchemes[] = {1.0f / 2.0f, 1.0f / 3.0f, 1.0f / -3.0f, 1.0f / 12.0f, 1.0f / -12.0f}; // complementary, triadic, analogous |
429 | const bool UseCustomColor = !m_Dummy ? g_Config.m_ClPlayerUseCustomColor : g_Config.m_ClDummyUseCustomColor; |
430 | if(UseCustomColor) |
431 | { |
432 | float GoalSat = random_float(min: 0.3f, max: 1.0f); |
433 | float MaxBodyLht = 1.0f - GoalSat * GoalSat; // max allowed lightness before we start losing saturation |
434 | |
435 | ColorHSLA Body; |
436 | Body.h = random_float(); |
437 | Body.l = random_float(min: 0.0f, max: MaxBodyLht); |
438 | Body.s = clamp(val: GoalSat * GoalSat / (1.0f - Body.l), lo: 0.0f, hi: 1.0f); |
439 | |
440 | ColorHSLA Feet; |
441 | Feet.h = std::fmod(x: Body.h + s_aSchemes[rand() % std::size(s_aSchemes)], y: 1.0f); |
442 | Feet.l = random_float(); |
443 | Feet.s = clamp(val: GoalSat * GoalSat / (1.0f - Feet.l), lo: 0.0f, hi: 1.0f); |
444 | |
445 | unsigned *pColorBody = !m_Dummy ? &g_Config.m_ClPlayerColorBody : &g_Config.m_ClDummyColorBody; |
446 | unsigned *pColorFeet = !m_Dummy ? &g_Config.m_ClPlayerColorFeet : &g_Config.m_ClDummyColorFeet; |
447 | |
448 | *pColorBody = Body.Pack(Alpha: false); |
449 | *pColorFeet = Feet.Pack(Alpha: false); |
450 | } |
451 | |
452 | const size_t SkinNameSize = !m_Dummy ? sizeof(g_Config.m_ClPlayerSkin) : sizeof(g_Config.m_ClDummySkin); |
453 | char aRandomSkinName[24]; |
454 | str_copy(dst: aRandomSkinName, src: "default" , dst_size: SkinNameSize); |
455 | if(!m_pClient->m_Skins.GetSkinsUnsafe().empty()) |
456 | { |
457 | do |
458 | { |
459 | auto it = m_pClient->m_Skins.GetSkinsUnsafe().begin(); |
460 | std::advance(i&: it, n: rand() % m_pClient->m_Skins.GetSkinsUnsafe().size()); |
461 | str_copy(dst: aRandomSkinName, src: (*it).second->GetName(), dst_size: SkinNameSize); |
462 | } while(!str_comp(a: aRandomSkinName, b: "x_ninja" ) || !str_comp(a: aRandomSkinName, b: "x_spec" )); |
463 | } |
464 | char *pSkinName = !m_Dummy ? g_Config.m_ClPlayerSkin : g_Config.m_ClDummySkin; |
465 | str_copy(dst: pSkinName, src: aRandomSkinName, dst_size: SkinNameSize); |
466 | |
467 | SetNeedSendInfo(); |
468 | } |
469 | |
470 | void CMenus::(IConsole::IResult *pResult, void *pUserData) |
471 | { |
472 | auto *pSelf = (CMenus *)pUserData; |
473 | const char *pStr = pResult->GetString(Index: 0); |
474 | if(!CSkin::IsValidName(pName: pStr)) |
475 | { |
476 | log_error("menus/settings" , "Favorite skin name '%s' is not valid" , pStr); |
477 | log_error("menus/settings" , "%s" , CSkin::m_aSkinNameRestrictions); |
478 | return; |
479 | } |
480 | pSelf->m_SkinFavorites.emplace(args&: pStr); |
481 | pSelf->m_SkinFavoritesChanged = true; |
482 | } |
483 | |
484 | void CMenus::(IConsole::IResult *pResult, void *pUserData) |
485 | { |
486 | auto *pSelf = (CMenus *)pUserData; |
487 | const auto it = pSelf->m_SkinFavorites.find(x: pResult->GetString(Index: 0)); |
488 | if(it != pSelf->m_SkinFavorites.end()) |
489 | { |
490 | pSelf->m_SkinFavorites.erase(position: it); |
491 | pSelf->m_SkinFavoritesChanged = true; |
492 | } |
493 | } |
494 | |
495 | void CMenus::(IConfigManager *pConfigManager, void *pUserData) |
496 | { |
497 | auto *pSelf = (CMenus *)pUserData; |
498 | pSelf->OnConfigSave(pConfigManager); |
499 | } |
500 | |
501 | void CMenus::(IConfigManager *pConfigManager) |
502 | { |
503 | for(const auto &Entry : m_SkinFavorites) |
504 | { |
505 | char aBuffer[256]; |
506 | str_format(buffer: aBuffer, buffer_size: std::size(aBuffer), format: "add_favorite_skin \"%s\"" , Entry.c_str()); |
507 | pConfigManager->WriteLine(pLine: aBuffer); |
508 | } |
509 | } |
510 | |
511 | void CMenus::(CUIRect MainView) |
512 | { |
513 | CUIRect TabBar, PlayerTab, DummyTab, ChangeInfo; |
514 | MainView.HSplitTop(Cut: 20.0f, pTop: &TabBar, pBottom: &MainView); |
515 | TabBar.VSplitMid(pLeft: &TabBar, pRight: &ChangeInfo, Spacing: 20.f); |
516 | TabBar.VSplitMid(pLeft: &PlayerTab, pRight: &DummyTab); |
517 | MainView.HSplitTop(Cut: 10.0f, pTop: nullptr, pBottom: &MainView); |
518 | |
519 | static CButtonContainer s_PlayerTabButton; |
520 | 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)) |
521 | { |
522 | m_Dummy = false; |
523 | } |
524 | |
525 | static CButtonContainer s_DummyTabButton; |
526 | 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)) |
527 | { |
528 | m_Dummy = true; |
529 | } |
530 | |
531 | if(Client()->State() == IClient::STATE_ONLINE && m_pClient->m_NextChangeInfo && m_pClient->m_NextChangeInfo > Client()->GameTick(Conn: g_Config.m_ClDummy)) |
532 | { |
533 | char aChangeInfo[128], aTimeLeft[32]; |
534 | str_format(buffer: aTimeLeft, buffer_size: sizeof(aTimeLeft), format: Localize(pStr: "%ds left" ), (m_pClient->m_NextChangeInfo - Client()->GameTick(Conn: g_Config.m_ClDummy) + Client()->GameTickSpeed() - 1) / Client()->GameTickSpeed()); |
535 | str_format(buffer: aChangeInfo, buffer_size: sizeof(aChangeInfo), format: "%s: %s" , Localize(pStr: "Player info change cooldown" ), aTimeLeft); |
536 | Ui()->DoLabel(pRect: &ChangeInfo, pText: aChangeInfo, Size: 10.f, Align: TEXTALIGN_ML); |
537 | } |
538 | |
539 | char *pSkinName; |
540 | size_t SkinNameSize; |
541 | int *pUseCustomColor; |
542 | unsigned *pColorBody; |
543 | unsigned *pColorFeet; |
544 | int *pEmote; |
545 | if(!m_Dummy) |
546 | { |
547 | pSkinName = g_Config.m_ClPlayerSkin; |
548 | SkinNameSize = sizeof(g_Config.m_ClPlayerSkin); |
549 | pUseCustomColor = &g_Config.m_ClPlayerUseCustomColor; |
550 | pColorBody = &g_Config.m_ClPlayerColorBody; |
551 | pColorFeet = &g_Config.m_ClPlayerColorFeet; |
552 | pEmote = &g_Config.m_ClPlayerDefaultEyes; |
553 | } |
554 | else |
555 | { |
556 | pSkinName = g_Config.m_ClDummySkin; |
557 | SkinNameSize = sizeof(g_Config.m_ClDummySkin); |
558 | pUseCustomColor = &g_Config.m_ClDummyUseCustomColor; |
559 | pColorBody = &g_Config.m_ClDummyColorBody; |
560 | pColorFeet = &g_Config.m_ClDummyColorFeet; |
561 | pEmote = &g_Config.m_ClDummyDefaultEyes; |
562 | } |
563 | |
564 | const float EyeButtonSize = 40.0f; |
565 | const bool RenderEyesBelow = MainView.w < 750.0f; |
566 | CUIRect YourSkin, Checkboxes, SkinPrefix, Eyes, Button, Label; |
567 | MainView.HSplitTop(Cut: 90.0f, pTop: &YourSkin, pBottom: &MainView); |
568 | if(RenderEyesBelow) |
569 | { |
570 | YourSkin.VSplitLeft(Cut: MainView.w * 0.45f, pLeft: &YourSkin, pRight: &Checkboxes); |
571 | Checkboxes.VSplitLeft(Cut: MainView.w * 0.35f, pLeft: &Checkboxes, pRight: &SkinPrefix); |
572 | MainView.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &MainView); |
573 | MainView.HSplitTop(Cut: EyeButtonSize, pTop: &Eyes, pBottom: &MainView); |
574 | Eyes.VSplitRight(Cut: EyeButtonSize * NUM_EMOTES + 5.0f * (NUM_EMOTES - 1), pLeft: nullptr, pRight: &Eyes); |
575 | } |
576 | else |
577 | { |
578 | YourSkin.VSplitRight(Cut: 3 * EyeButtonSize + 2 * 5.0f, pLeft: &YourSkin, pRight: &Eyes); |
579 | const float RemainderWidth = YourSkin.w; |
580 | YourSkin.VSplitLeft(Cut: RemainderWidth * 0.4f, pLeft: &YourSkin, pRight: &Checkboxes); |
581 | Checkboxes.VSplitLeft(Cut: RemainderWidth * 0.35f, pLeft: &Checkboxes, pRight: &SkinPrefix); |
582 | SkinPrefix.VSplitRight(Cut: 20.0f, pLeft: &SkinPrefix, pRight: nullptr); |
583 | } |
584 | YourSkin.VSplitRight(Cut: 20.0f, pLeft: &YourSkin, pRight: nullptr); |
585 | Checkboxes.VSplitRight(Cut: 20.0f, pLeft: &Checkboxes, pRight: nullptr); |
586 | |
587 | // Checkboxes |
588 | Checkboxes.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Checkboxes); |
589 | if(DoButton_CheckBox(pId: &g_Config.m_ClDownloadSkins, pText: Localize(pStr: "Download skins" ), Checked: g_Config.m_ClDownloadSkins, pRect: &Button)) |
590 | { |
591 | g_Config.m_ClDownloadSkins ^= 1; |
592 | m_pClient->RefreshSkins(); |
593 | } |
594 | |
595 | Checkboxes.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Checkboxes); |
596 | if(DoButton_CheckBox(pId: &g_Config.m_ClDownloadCommunitySkins, pText: Localize(pStr: "Download community skins" ), Checked: g_Config.m_ClDownloadCommunitySkins, pRect: &Button)) |
597 | { |
598 | g_Config.m_ClDownloadCommunitySkins ^= 1; |
599 | m_pClient->RefreshSkins(); |
600 | } |
601 | |
602 | Checkboxes.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Checkboxes); |
603 | if(DoButton_CheckBox(pId: &g_Config.m_ClVanillaSkinsOnly, pText: Localize(pStr: "Vanilla skins only" ), Checked: g_Config.m_ClVanillaSkinsOnly, pRect: &Button)) |
604 | { |
605 | g_Config.m_ClVanillaSkinsOnly ^= 1; |
606 | m_pClient->RefreshSkins(); |
607 | } |
608 | |
609 | Checkboxes.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Checkboxes); |
610 | if(DoButton_CheckBox(pId: &g_Config.m_ClFatSkins, pText: Localize(pStr: "Fat skins (DDFat)" ), Checked: g_Config.m_ClFatSkins, pRect: &Button)) |
611 | { |
612 | g_Config.m_ClFatSkins ^= 1; |
613 | } |
614 | |
615 | // Skin prefix |
616 | { |
617 | SkinPrefix.HSplitTop(Cut: 20.0f, pTop: &Label, pBottom: &SkinPrefix); |
618 | Ui()->DoLabel(pRect: &Label, pText: Localize(pStr: "Skin prefix" ), Size: 14.0f, Align: TEXTALIGN_ML); |
619 | |
620 | SkinPrefix.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &SkinPrefix); |
621 | static CLineInput s_SkinPrefixInput(g_Config.m_ClSkinPrefix, sizeof(g_Config.m_ClSkinPrefix)); |
622 | Ui()->DoClearableEditBox(pLineInput: &s_SkinPrefixInput, pRect: &Button, FontSize: 14.0f); |
623 | |
624 | SkinPrefix.HSplitTop(Cut: 2.0f, pTop: nullptr, pBottom: &SkinPrefix); |
625 | |
626 | static const char *s_apSkinPrefixes[] = {"kitty" , "santa" }; |
627 | static CButtonContainer s_aPrefixButtons[std::size(s_apSkinPrefixes)]; |
628 | for(size_t i = 0; i < std::size(s_apSkinPrefixes); i++) |
629 | { |
630 | SkinPrefix.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &SkinPrefix); |
631 | Button.HMargin(Cut: 2.0f, pOtherRect: &Button); |
632 | if(DoButton_Menu(pButtonContainer: &s_aPrefixButtons[i], pText: s_apSkinPrefixes[i], Checked: 0, pRect: &Button)) |
633 | { |
634 | str_copy(dst&: g_Config.m_ClSkinPrefix, src: s_apSkinPrefixes[i]); |
635 | } |
636 | } |
637 | } |
638 | |
639 | // Player skin area |
640 | CUIRect CustomColorsButton, RandomSkinButton; |
641 | YourSkin.HSplitTop(Cut: 20.0f, pTop: &Label, pBottom: &YourSkin); |
642 | YourSkin.HSplitBottom(Cut: 20.0f, pTop: &YourSkin, pBottom: &CustomColorsButton); |
643 | CustomColorsButton.VSplitRight(Cut: 30.0f, pLeft: &CustomColorsButton, pRight: &RandomSkinButton); |
644 | CustomColorsButton.VSplitRight(Cut: 20.0f, pLeft: &CustomColorsButton, pRight: nullptr); |
645 | YourSkin.VSplitLeft(Cut: 65.0f, pLeft: &YourSkin, pRight: &Button); |
646 | Button.VSplitLeft(Cut: 5.0f, pLeft: nullptr, pRight: &Button); |
647 | Button.HMargin(Cut: (Button.h - 20.0f) / 2.0f, pOtherRect: &Button); |
648 | |
649 | char aBuf[128 + IO_MAX_PATH_LENGTH]; |
650 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s:" , Localize(pStr: "Your skin" )); |
651 | Ui()->DoLabel(pRect: &Label, pText: aBuf, Size: 14.0f, Align: TEXTALIGN_ML); |
652 | |
653 | // Note: get the skin info after the settings buttons, because they can trigger a refresh |
654 | // which invalidates the skin. |
655 | const CSkin *pSkin = m_pClient->m_Skins.Find(pName: pSkinName); |
656 | CTeeRenderInfo OwnSkinInfo; |
657 | OwnSkinInfo.m_OriginalRenderSkin = pSkin->m_OriginalSkin; |
658 | OwnSkinInfo.m_ColorableRenderSkin = pSkin->m_ColorableSkin; |
659 | OwnSkinInfo.m_SkinMetrics = pSkin->m_Metrics; |
660 | OwnSkinInfo.m_CustomColoredSkin = *pUseCustomColor; |
661 | if(*pUseCustomColor) |
662 | { |
663 | OwnSkinInfo.m_ColorBody = color_cast<ColorRGBA>(hsl: ColorHSLA(*pColorBody).UnclampLighting()); |
664 | OwnSkinInfo.m_ColorFeet = color_cast<ColorRGBA>(hsl: ColorHSLA(*pColorFeet).UnclampLighting()); |
665 | } |
666 | else |
667 | { |
668 | OwnSkinInfo.m_ColorBody = ColorRGBA(1.0f, 1.0f, 1.0f); |
669 | OwnSkinInfo.m_ColorFeet = ColorRGBA(1.0f, 1.0f, 1.0f); |
670 | } |
671 | OwnSkinInfo.m_Size = 50.0f; |
672 | |
673 | // Tee |
674 | { |
675 | vec2 OffsetToMid; |
676 | CRenderTools::GetRenderTeeOffsetToRenderedTee(pAnim: CAnimState::GetIdle(), pInfo: &OwnSkinInfo, TeeOffsetToMid&: OffsetToMid); |
677 | const vec2 TeeRenderPos = vec2(YourSkin.x + YourSkin.w / 2.0f, YourSkin.y + YourSkin.h / 2.0f + OffsetToMid.y); |
678 | RenderTools()->RenderTee(pAnim: CAnimState::GetIdle(), pInfo: &OwnSkinInfo, Emote: *pEmote, Dir: vec2(1.0f, 0.0f), Pos: TeeRenderPos); |
679 | } |
680 | |
681 | // Skin name |
682 | static CLineInput s_SkinInput; |
683 | s_SkinInput.SetBuffer(pStr: pSkinName, MaxSize: SkinNameSize); |
684 | s_SkinInput.SetEmptyText("default" ); |
685 | if(Ui()->DoClearableEditBox(pLineInput: &s_SkinInput, pRect: &Button, FontSize: 14.0f)) |
686 | { |
687 | SetNeedSendInfo(); |
688 | } |
689 | |
690 | // Random skin button |
691 | static CButtonContainer s_RandomSkinButton; |
692 | 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}; |
693 | static int s_CurrentDie = rand() % std::size(s_apDice); |
694 | TextRender()->SetFontPreset(EFontPreset::ICON_FONT); |
695 | TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE); |
696 | if(DoButton_Menu(pButtonContainer: &s_RandomSkinButton, pText: s_apDice[s_CurrentDie], Checked: 0, pRect: &RandomSkinButton, pImageName: nullptr, Corners: IGraphics::CORNER_ALL, Rounding: 5.0f, FontFactor: -0.2f)) |
697 | { |
698 | RandomSkin(); |
699 | s_CurrentDie = rand() % std::size(s_apDice); |
700 | } |
701 | TextRender()->SetRenderFlags(0); |
702 | TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); |
703 | GameClient()->m_Tooltips.DoToolTip(pId: &s_RandomSkinButton, pNearRect: &RandomSkinButton, pText: Localize(pStr: "Create a random skin" )); |
704 | |
705 | // Custom colors button |
706 | if(DoButton_CheckBox(pId: pUseCustomColor, pText: Localize(pStr: "Custom colors" ), Checked: *pUseCustomColor, pRect: &CustomColorsButton)) |
707 | { |
708 | *pUseCustomColor = *pUseCustomColor ? 0 : 1; |
709 | SetNeedSendInfo(); |
710 | } |
711 | |
712 | // Default eyes |
713 | { |
714 | CTeeRenderInfo EyeSkinInfo = OwnSkinInfo; |
715 | EyeSkinInfo.m_Size = EyeButtonSize; |
716 | vec2 OffsetToMid; |
717 | CRenderTools::GetRenderTeeOffsetToRenderedTee(pAnim: CAnimState::GetIdle(), pInfo: &EyeSkinInfo, TeeOffsetToMid&: OffsetToMid); |
718 | |
719 | CUIRect EyesRow; |
720 | Eyes.HSplitTop(Cut: EyeButtonSize, pTop: &EyesRow, pBottom: &Eyes); |
721 | static CButtonContainer s_aEyeButtons[NUM_EMOTES]; |
722 | for(int CurrentEyeEmote = 0; CurrentEyeEmote < NUM_EMOTES; CurrentEyeEmote++) |
723 | { |
724 | EyesRow.VSplitLeft(Cut: EyeButtonSize, pLeft: &Button, pRight: &EyesRow); |
725 | EyesRow.VSplitLeft(Cut: 5.0f, pLeft: nullptr, pRight: &EyesRow); |
726 | if(!RenderEyesBelow && (CurrentEyeEmote + 1) % 3 == 0) |
727 | { |
728 | Eyes.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &Eyes); |
729 | Eyes.HSplitTop(Cut: EyeButtonSize, pTop: &EyesRow, pBottom: &Eyes); |
730 | } |
731 | |
732 | const ColorRGBA EyeButtonColor = ColorRGBA(1.0f, 1.0f, 1.0f, 0.25f + (*pEmote == CurrentEyeEmote ? 0.25f : 0.0f)); |
733 | if(DoButton_Menu(pButtonContainer: &s_aEyeButtons[CurrentEyeEmote], pText: "" , Checked: 0, pRect: &Button, pImageName: nullptr, Corners: IGraphics::CORNER_ALL, Rounding: 5.0f, FontFactor: 0.0f, Color: EyeButtonColor)) |
734 | { |
735 | *pEmote = CurrentEyeEmote; |
736 | if((int)m_Dummy == g_Config.m_ClDummy) |
737 | GameClient()->m_Emoticon.EyeEmote(EyeEmote: CurrentEyeEmote); |
738 | } |
739 | GameClient()->m_Tooltips.DoToolTip(pId: &s_aEyeButtons[CurrentEyeEmote], pNearRect: &Button, pText: Localize(pStr: "Choose default eyes when joining a server" )); |
740 | 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)); |
741 | } |
742 | } |
743 | |
744 | // Custom color pickers |
745 | MainView.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &MainView); |
746 | if(*pUseCustomColor) |
747 | { |
748 | CUIRect CustomColors; |
749 | MainView.HSplitTop(Cut: 95.0f, pTop: &CustomColors, pBottom: &MainView); |
750 | CUIRect aRects[2]; |
751 | CustomColors.VSplitMid(pLeft: &aRects[0], pRight: &aRects[1], Spacing: 20.0f); |
752 | |
753 | unsigned *apColors[] = {pColorBody, pColorFeet}; |
754 | const char *apParts[] = {Localize(pStr: "Body" ), Localize(pStr: "Feet" )}; |
755 | |
756 | for(int i = 0; i < 2; i++) |
757 | { |
758 | aRects[i].HSplitTop(Cut: 20.0f, pTop: &Label, pBottom: &aRects[i]); |
759 | Ui()->DoLabel(pRect: &Label, pText: apParts[i], Size: 14.0f, Align: TEXTALIGN_ML); |
760 | |
761 | const unsigned PrevColor = *apColors[i]; |
762 | RenderHSLScrollbars(pRect: &aRects[i], pColor: apColors[i], Alpha: false, ClampedLight: true); |
763 | if(PrevColor != *apColors[i]) |
764 | { |
765 | SetNeedSendInfo(); |
766 | } |
767 | } |
768 | } |
769 | MainView.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &MainView); |
770 | |
771 | // Layout bottom controls and use remainder for skin selector |
772 | CUIRect QuickSearch, DatabaseButton, DirectoryButton, RefreshButton; |
773 | MainView.HSplitBottom(Cut: 20.0f, pTop: &MainView, pBottom: &QuickSearch); |
774 | MainView.HSplitBottom(Cut: 5.0f, pTop: &MainView, pBottom: nullptr); |
775 | QuickSearch.VSplitLeft(Cut: 240.0f, pLeft: &QuickSearch, pRight: &DatabaseButton); |
776 | QuickSearch.VSplitRight(Cut: 10.0f, pLeft: &QuickSearch, pRight: nullptr); |
777 | DatabaseButton.VSplitLeft(Cut: 150.0f, pLeft: &DatabaseButton, pRight: &DirectoryButton); |
778 | DirectoryButton.VSplitRight(Cut: 175.0f, pLeft: nullptr, pRight: &DirectoryButton); |
779 | DirectoryButton.VSplitRight(Cut: 25.0f, pLeft: &DirectoryButton, pRight: &RefreshButton); |
780 | DirectoryButton.VSplitRight(Cut: 10.0f, pLeft: &DirectoryButton, pRight: nullptr); |
781 | |
782 | // Skin selector |
783 | static std::vector<CUISkin> s_vSkinList; |
784 | static std::vector<CUISkin> s_vSkinListHelper; |
785 | static std::vector<CUISkin> s_vFavoriteSkinListHelper; |
786 | static int s_SkinCount = 0; |
787 | static CListBox s_ListBox; |
788 | |
789 | // be nice to the CPU |
790 | static auto s_SkinLastRebuildTime = time_get_nanoseconds(); |
791 | const auto CurTime = time_get_nanoseconds(); |
792 | if(m_SkinListNeedsUpdate || m_pClient->m_Skins.Num() != s_SkinCount || m_SkinFavoritesChanged || (m_pClient->m_Skins.IsDownloadingSkins() && (CurTime - s_SkinLastRebuildTime > 500ms))) |
793 | { |
794 | s_SkinLastRebuildTime = CurTime; |
795 | s_vSkinList.clear(); |
796 | s_vSkinListHelper.clear(); |
797 | s_vFavoriteSkinListHelper.clear(); |
798 | // set skin count early, since Find of the skin class might load |
799 | // a downloading skin |
800 | s_SkinCount = m_pClient->m_Skins.Num(); |
801 | m_SkinFavoritesChanged = false; |
802 | |
803 | auto &&SkinNotFiltered = [&](const CSkin *pSkinToBeSelected) { |
804 | // filter quick search |
805 | if(g_Config.m_ClSkinFilterString[0] != '\0' && !str_utf8_find_nocase(haystack: pSkinToBeSelected->GetName(), needle: g_Config.m_ClSkinFilterString)) |
806 | return false; |
807 | |
808 | // no special skins |
809 | if((pSkinToBeSelected->GetName()[0] == 'x' && pSkinToBeSelected->GetName()[1] == '_')) |
810 | return false; |
811 | |
812 | return true; |
813 | }; |
814 | |
815 | for(const auto &it : m_SkinFavorites) |
816 | { |
817 | const CSkin *pSkinToBeSelected = m_pClient->m_Skins.FindOrNullptr(pName: it.c_str(), IgnorePrefix: true); |
818 | |
819 | if(pSkinToBeSelected == nullptr || !SkinNotFiltered(pSkinToBeSelected)) |
820 | continue; |
821 | |
822 | s_vFavoriteSkinListHelper.emplace_back(args&: pSkinToBeSelected); |
823 | } |
824 | for(const auto &SkinIt : m_pClient->m_Skins.GetSkinsUnsafe()) |
825 | { |
826 | const auto &pSkinToBeSelected = SkinIt.second; |
827 | if(!SkinNotFiltered(pSkinToBeSelected.get())) |
828 | continue; |
829 | |
830 | if(std::find(first: m_SkinFavorites.begin(), last: m_SkinFavorites.end(), val: pSkinToBeSelected->GetName()) == m_SkinFavorites.end()) |
831 | s_vSkinListHelper.emplace_back(args: pSkinToBeSelected.get()); |
832 | } |
833 | std::sort(first: s_vSkinListHelper.begin(), last: s_vSkinListHelper.end()); |
834 | std::sort(first: s_vFavoriteSkinListHelper.begin(), last: s_vFavoriteSkinListHelper.end()); |
835 | s_vSkinList = s_vFavoriteSkinListHelper; |
836 | s_vSkinList.insert(position: s_vSkinList.end(), first: s_vSkinListHelper.begin(), last: s_vSkinListHelper.end()); |
837 | m_SkinListNeedsUpdate = false; |
838 | } |
839 | |
840 | int OldSelected = -1; |
841 | s_ListBox.DoStart(RowHeight: 50.0f, NumItems: s_vSkinList.size(), ItemsPerRow: 4, RowsPerScroll: 1, SelectedIndex: OldSelected, pRect: &MainView); |
842 | for(size_t i = 0; i < s_vSkinList.size(); ++i) |
843 | { |
844 | const CSkin *pSkinToBeDraw = s_vSkinList[i].m_pSkin; |
845 | if(str_comp(a: pSkinToBeDraw->GetName(), b: pSkinName) == 0) |
846 | OldSelected = i; |
847 | |
848 | const CListboxItem Item = s_ListBox.DoNextItem(pId: pSkinToBeDraw, Selected: OldSelected >= 0 && (size_t)OldSelected == i); |
849 | if(!Item.m_Visible) |
850 | continue; |
851 | |
852 | Item.m_Rect.VSplitLeft(Cut: 60.0f, pLeft: &Button, pRight: &Label); |
853 | |
854 | CTeeRenderInfo Info = OwnSkinInfo; |
855 | Info.m_CustomColoredSkin = *pUseCustomColor; |
856 | Info.m_OriginalRenderSkin = pSkinToBeDraw->m_OriginalSkin; |
857 | Info.m_ColorableRenderSkin = pSkinToBeDraw->m_ColorableSkin; |
858 | Info.m_SkinMetrics = pSkinToBeDraw->m_Metrics; |
859 | |
860 | vec2 OffsetToMid; |
861 | CRenderTools::GetRenderTeeOffsetToRenderedTee(pAnim: CAnimState::GetIdle(), pInfo: &Info, TeeOffsetToMid&: OffsetToMid); |
862 | const vec2 TeeRenderPos = vec2(Button.x + Button.w / 2.0f, Button.y + Button.h / 2 + OffsetToMid.y); |
863 | RenderTools()->RenderTee(pAnim: CAnimState::GetIdle(), pInfo: &Info, Emote: *pEmote, Dir: vec2(1.0f, 0.0f), Pos: TeeRenderPos); |
864 | |
865 | SLabelProperties Props; |
866 | Props.m_MaxWidth = Label.w - 5.0f; |
867 | Ui()->DoLabel(pRect: &Label, pText: pSkinToBeDraw->GetName(), Size: 12.0f, Align: TEXTALIGN_ML, LabelProps: Props); |
868 | |
869 | if(g_Config.m_Debug) |
870 | { |
871 | const ColorRGBA BloodColor = *pUseCustomColor ? color_cast<ColorRGBA>(hsl: ColorHSLA(*pColorBody).UnclampLighting()) : pSkinToBeDraw->m_BloodColor; |
872 | Graphics()->TextureClear(); |
873 | Graphics()->QuadsBegin(); |
874 | Graphics()->SetColor(r: BloodColor.r, g: BloodColor.g, b: BloodColor.b, a: 1.0f); |
875 | IGraphics::CQuadItem QuadItem(Label.x, Label.y, 12.0f, 12.0f); |
876 | Graphics()->QuadsDrawTL(pArray: &QuadItem, Num: 1); |
877 | Graphics()->QuadsEnd(); |
878 | } |
879 | |
880 | // render skin favorite icon |
881 | { |
882 | const auto SkinItFav = m_SkinFavorites.find(x: pSkinToBeDraw->GetName()); |
883 | const bool IsFav = SkinItFav != m_SkinFavorites.end(); |
884 | CUIRect FavIcon; |
885 | Item.m_Rect.HSplitTop(Cut: 20.0f, pTop: &FavIcon, pBottom: nullptr); |
886 | FavIcon.VSplitRight(Cut: 20.0f, pLeft: nullptr, pRight: &FavIcon); |
887 | if(DoButton_Favorite(pButtonId: &pSkinToBeDraw->m_Metrics.m_Body, pParentId: pSkinToBeDraw, Checked: IsFav, pRect: &FavIcon)) |
888 | { |
889 | if(IsFav) |
890 | { |
891 | m_SkinFavorites.erase(position: SkinItFav); |
892 | } |
893 | else |
894 | { |
895 | m_SkinFavorites.emplace(args: pSkinToBeDraw->GetName()); |
896 | } |
897 | m_SkinListNeedsUpdate = true; |
898 | } |
899 | } |
900 | } |
901 | |
902 | const int NewSelected = s_ListBox.DoEnd(); |
903 | if(OldSelected != NewSelected) |
904 | { |
905 | str_copy(dst: pSkinName, src: s_vSkinList[NewSelected].m_pSkin->GetName(), dst_size: SkinNameSize); |
906 | SetNeedSendInfo(); |
907 | } |
908 | |
909 | // Quick search |
910 | { |
911 | TextRender()->SetFontPreset(EFontPreset::ICON_FONT); |
912 | TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE); |
913 | Ui()->DoLabel(pRect: &QuickSearch, pText: FONT_ICON_MAGNIFYING_GLASS, Size: 14.0f, Align: TEXTALIGN_ML); |
914 | float SearchWidth = TextRender()->TextWidth(Size: 14.0f, pText: FONT_ICON_MAGNIFYING_GLASS, StrLength: -1, LineWidth: -1.0f); |
915 | TextRender()->SetRenderFlags(0); |
916 | TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); |
917 | QuickSearch.VSplitLeft(Cut: SearchWidth + 5.0f, pLeft: nullptr, pRight: &QuickSearch); |
918 | static CLineInput s_SkinFilterInput(g_Config.m_ClSkinFilterString, sizeof(g_Config.m_ClSkinFilterString)); |
919 | if(Input()->KeyPress(Key: KEY_F) && Input()->ModifierIsPressed()) |
920 | { |
921 | Ui()->SetActiveItem(&s_SkinFilterInput); |
922 | s_SkinFilterInput.SelectAll(); |
923 | } |
924 | s_SkinFilterInput.SetEmptyText(Localize(pStr: "Search" )); |
925 | if(Ui()->DoClearableEditBox(pLineInput: &s_SkinFilterInput, pRect: &QuickSearch, FontSize: 14.0f)) |
926 | m_SkinListNeedsUpdate = true; |
927 | } |
928 | |
929 | static CButtonContainer s_SkinDatabaseButton; |
930 | if(DoButton_Menu(pButtonContainer: &s_SkinDatabaseButton, pText: Localize(pStr: "Skin Database" ), Checked: 0, pRect: &DatabaseButton)) |
931 | { |
932 | Client()->ViewLink(pLink: "https://ddnet.org/skins/" ); |
933 | } |
934 | |
935 | static CButtonContainer s_DirectoryButton; |
936 | if(DoButton_Menu(pButtonContainer: &s_DirectoryButton, pText: Localize(pStr: "Skins directory" ), Checked: 0, pRect: &DirectoryButton)) |
937 | { |
938 | Storage()->GetCompletePath(Type: IStorage::TYPE_SAVE, pDir: "skins" , pBuffer: aBuf, BufferSize: sizeof(aBuf)); |
939 | Storage()->CreateFolder(pFoldername: "skins" , Type: IStorage::TYPE_SAVE); |
940 | Client()->ViewFile(pFilename: aBuf); |
941 | } |
942 | GameClient()->m_Tooltips.DoToolTip(pId: &s_DirectoryButton, pNearRect: &DirectoryButton, pText: Localize(pStr: "Open the directory to add custom skins" )); |
943 | |
944 | TextRender()->SetFontPreset(EFontPreset::ICON_FONT); |
945 | TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE); |
946 | static CButtonContainer s_SkinRefreshButton; |
947 | 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())) |
948 | { |
949 | // reset render flags for possible loading screen |
950 | TextRender()->SetRenderFlags(0); |
951 | TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); |
952 | m_pClient->RefreshSkins(); |
953 | } |
954 | TextRender()->SetRenderFlags(0); |
955 | TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); |
956 | } |
957 | |
958 | typedef struct |
959 | { |
960 | const char *m_pName; |
961 | const char *m_pCommand; |
962 | int m_KeyId; |
963 | int m_ModifierCombination; |
964 | } CKeyInfo; |
965 | |
966 | static CKeyInfo gs_aKeys[] = |
967 | { |
968 | {.m_pName: Localizable(pStr: "Move left" ), .m_pCommand: "+left" , .m_KeyId: 0, .m_ModifierCombination: 0}, |
969 | {.m_pName: Localizable(pStr: "Move right" ), .m_pCommand: "+right" , .m_KeyId: 0, .m_ModifierCombination: 0}, |
970 | {.m_pName: Localizable(pStr: "Jump" ), .m_pCommand: "+jump" , .m_KeyId: 0, .m_ModifierCombination: 0}, |
971 | {.m_pName: Localizable(pStr: "Fire" ), .m_pCommand: "+fire" , .m_KeyId: 0, .m_ModifierCombination: 0}, |
972 | {.m_pName: Localizable(pStr: "Hook" ), .m_pCommand: "+hook" , .m_KeyId: 0, .m_ModifierCombination: 0}, |
973 | {.m_pName: Localizable(pStr: "Hook collisions" ), .m_pCommand: "+showhookcoll" , .m_KeyId: 0, .m_ModifierCombination: 0}, |
974 | {.m_pName: Localizable(pStr: "Pause" ), .m_pCommand: "say /pause" , .m_KeyId: 0, .m_ModifierCombination: 0}, |
975 | {.m_pName: Localizable(pStr: "Kill" ), .m_pCommand: "kill" , .m_KeyId: 0, .m_ModifierCombination: 0}, |
976 | {.m_pName: Localizable(pStr: "Zoom in" ), .m_pCommand: "zoom+" , .m_KeyId: 0, .m_ModifierCombination: 0}, |
977 | {.m_pName: Localizable(pStr: "Zoom out" ), .m_pCommand: "zoom-" , .m_KeyId: 0, .m_ModifierCombination: 0}, |
978 | {.m_pName: Localizable(pStr: "Default zoom" ), .m_pCommand: "zoom" , .m_KeyId: 0, .m_ModifierCombination: 0}, |
979 | {.m_pName: Localizable(pStr: "Show others" ), .m_pCommand: "say /showothers" , .m_KeyId: 0, .m_ModifierCombination: 0}, |
980 | {.m_pName: Localizable(pStr: "Show all" ), .m_pCommand: "say /showall" , .m_KeyId: 0, .m_ModifierCombination: 0}, |
981 | {.m_pName: Localizable(pStr: "Toggle dyncam" ), .m_pCommand: "toggle cl_dyncam 0 1" , .m_KeyId: 0, .m_ModifierCombination: 0}, |
982 | {.m_pName: Localizable(pStr: "Toggle ghost" ), .m_pCommand: "toggle cl_race_show_ghost 0 1" , .m_KeyId: 0, .m_ModifierCombination: 0}, |
983 | |
984 | {.m_pName: Localizable(pStr: "Hammer" ), .m_pCommand: "+weapon1" , .m_KeyId: 0, .m_ModifierCombination: 0}, |
985 | {.m_pName: Localizable(pStr: "Pistol" ), .m_pCommand: "+weapon2" , .m_KeyId: 0, .m_ModifierCombination: 0}, |
986 | {.m_pName: Localizable(pStr: "Shotgun" ), .m_pCommand: "+weapon3" , .m_KeyId: 0, .m_ModifierCombination: 0}, |
987 | {.m_pName: Localizable(pStr: "Grenade" ), .m_pCommand: "+weapon4" , .m_KeyId: 0, .m_ModifierCombination: 0}, |
988 | {.m_pName: Localizable(pStr: "Laser" ), .m_pCommand: "+weapon5" , .m_KeyId: 0, .m_ModifierCombination: 0}, |
989 | {.m_pName: Localizable(pStr: "Next weapon" ), .m_pCommand: "+nextweapon" , .m_KeyId: 0, .m_ModifierCombination: 0}, |
990 | {.m_pName: Localizable(pStr: "Prev. weapon" ), .m_pCommand: "+prevweapon" , .m_KeyId: 0, .m_ModifierCombination: 0}, |
991 | |
992 | {.m_pName: Localizable(pStr: "Vote yes" ), .m_pCommand: "vote yes" , .m_KeyId: 0, .m_ModifierCombination: 0}, |
993 | {.m_pName: Localizable(pStr: "Vote no" ), .m_pCommand: "vote no" , .m_KeyId: 0, .m_ModifierCombination: 0}, |
994 | |
995 | {.m_pName: Localizable(pStr: "Chat" ), .m_pCommand: "+show_chat; chat all" , .m_KeyId: 0, .m_ModifierCombination: 0}, |
996 | {.m_pName: Localizable(pStr: "Team chat" ), .m_pCommand: "+show_chat; chat team" , .m_KeyId: 0, .m_ModifierCombination: 0}, |
997 | {.m_pName: Localizable(pStr: "Converse" ), .m_pCommand: "+show_chat; chat all /c " , .m_KeyId: 0, .m_ModifierCombination: 0}, |
998 | {.m_pName: Localizable(pStr: "Chat command" ), .m_pCommand: "+show_chat; chat all /" , .m_KeyId: 0, .m_ModifierCombination: 0}, |
999 | {.m_pName: Localizable(pStr: "Show chat" ), .m_pCommand: "+show_chat" , .m_KeyId: 0, .m_ModifierCombination: 0}, |
1000 | |
1001 | {.m_pName: Localizable(pStr: "Toggle dummy" ), .m_pCommand: "toggle cl_dummy 0 1" , .m_KeyId: 0, .m_ModifierCombination: 0}, |
1002 | {.m_pName: Localizable(pStr: "Dummy copy" ), .m_pCommand: "toggle cl_dummy_copy_moves 0 1" , .m_KeyId: 0, .m_ModifierCombination: 0}, |
1003 | {.m_pName: Localizable(pStr: "Hammerfly dummy" ), .m_pCommand: "toggle cl_dummy_hammer 0 1" , .m_KeyId: 0, .m_ModifierCombination: 0}, |
1004 | |
1005 | {.m_pName: Localizable(pStr: "Emoticon" ), .m_pCommand: "+emote" , .m_KeyId: 0, .m_ModifierCombination: 0}, |
1006 | {.m_pName: Localizable(pStr: "Spectator mode" ), .m_pCommand: "+spectate" , .m_KeyId: 0, .m_ModifierCombination: 0}, |
1007 | {.m_pName: Localizable(pStr: "Spectate next" ), .m_pCommand: "spectate_next" , .m_KeyId: 0, .m_ModifierCombination: 0}, |
1008 | {.m_pName: Localizable(pStr: "Spectate previous" ), .m_pCommand: "spectate_previous" , .m_KeyId: 0, .m_ModifierCombination: 0}, |
1009 | {.m_pName: Localizable(pStr: "Console" ), .m_pCommand: "toggle_local_console" , .m_KeyId: 0, .m_ModifierCombination: 0}, |
1010 | {.m_pName: Localizable(pStr: "Remote console" ), .m_pCommand: "toggle_remote_console" , .m_KeyId: 0, .m_ModifierCombination: 0}, |
1011 | {.m_pName: Localizable(pStr: "Screenshot" ), .m_pCommand: "screenshot" , .m_KeyId: 0, .m_ModifierCombination: 0}, |
1012 | {.m_pName: Localizable(pStr: "Scoreboard" ), .m_pCommand: "+scoreboard" , .m_KeyId: 0, .m_ModifierCombination: 0}, |
1013 | {.m_pName: Localizable(pStr: "Statboard" ), .m_pCommand: "+statboard" , .m_KeyId: 0, .m_ModifierCombination: 0}, |
1014 | {.m_pName: Localizable(pStr: "Lock team" ), .m_pCommand: "say /lock" , .m_KeyId: 0, .m_ModifierCombination: 0}, |
1015 | {.m_pName: Localizable(pStr: "Show entities" ), .m_pCommand: "toggle cl_overlay_entities 0 100" , .m_KeyId: 0, .m_ModifierCombination: 0}, |
1016 | {.m_pName: Localizable(pStr: "Show HUD" ), .m_pCommand: "toggle cl_showhud 0 1" , .m_KeyId: 0, .m_ModifierCombination: 0}, |
1017 | }; |
1018 | |
1019 | void CMenus::(int Start, int Stop, CUIRect View) |
1020 | { |
1021 | for(int i = Start; i < Stop; i++) |
1022 | { |
1023 | const CKeyInfo &Key = gs_aKeys[i]; |
1024 | |
1025 | CUIRect Button, Label; |
1026 | View.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &View); |
1027 | Button.VSplitLeft(Cut: 135.0f, pLeft: &Label, pRight: &Button); |
1028 | |
1029 | char aBuf[64]; |
1030 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s:" , Localize(pStr: Key.m_pName)); |
1031 | |
1032 | Ui()->DoLabel(pRect: &Label, pText: aBuf, Size: 13.0f, Align: TEXTALIGN_ML); |
1033 | int OldId = Key.m_KeyId, OldModifierCombination = Key.m_ModifierCombination, NewModifierCombination; |
1034 | int NewId = DoKeyReader(pId: &Key.m_KeyId, pRect: &Button, Key: OldId, ModifierCombination: OldModifierCombination, pNewModifierCombination: &NewModifierCombination); |
1035 | if(NewId != OldId || NewModifierCombination != OldModifierCombination) |
1036 | { |
1037 | if(OldId != 0 || NewId == 0) |
1038 | m_pClient->m_Binds.Bind(KeyId: OldId, pStr: "" , FreeOnly: false, ModifierCombination: OldModifierCombination); |
1039 | if(NewId != 0) |
1040 | m_pClient->m_Binds.Bind(KeyId: NewId, pStr: Key.m_pCommand, FreeOnly: false, ModifierCombination: NewModifierCombination); |
1041 | } |
1042 | |
1043 | View.HSplitTop(Cut: 2.0f, pTop: 0, pBottom: &View); |
1044 | } |
1045 | } |
1046 | |
1047 | float CMenus::(CUIRect View) |
1048 | { |
1049 | bool JoystickEnabled = g_Config.m_InpControllerEnable; |
1050 | int NumJoysticks = Input()->NumJoysticks(); |
1051 | int NumOptions = 1; // expandable header |
1052 | if(JoystickEnabled) |
1053 | { |
1054 | NumOptions++; // message or joystick name/selection |
1055 | if(NumJoysticks > 0) |
1056 | { |
1057 | NumOptions += 3; // mode, ui sens, tolerance |
1058 | if(!g_Config.m_InpControllerAbsolute) |
1059 | NumOptions++; // ingame sens |
1060 | NumOptions += Input()->GetActiveJoystick()->GetNumAxes() + 1; // axis selection + header |
1061 | } |
1062 | } |
1063 | const float ButtonHeight = 20.0f; |
1064 | const float Spacing = 2.0f; |
1065 | const float BackgroundHeight = NumOptions * (ButtonHeight + Spacing) + (NumOptions == 1 ? 0.0f : Spacing); |
1066 | if(View.h < BackgroundHeight) |
1067 | return BackgroundHeight; |
1068 | |
1069 | View.HSplitTop(Cut: BackgroundHeight, pTop: &View, pBottom: nullptr); |
1070 | |
1071 | CUIRect Button; |
1072 | View.HSplitTop(Cut: Spacing, pTop: nullptr, pBottom: &View); |
1073 | View.HSplitTop(Cut: ButtonHeight, pTop: &Button, pBottom: &View); |
1074 | if(DoButton_CheckBox(pId: &g_Config.m_InpControllerEnable, pText: Localize(pStr: "Enable controller" ), Checked: g_Config.m_InpControllerEnable, pRect: &Button)) |
1075 | { |
1076 | g_Config.m_InpControllerEnable ^= 1; |
1077 | } |
1078 | if(JoystickEnabled) |
1079 | { |
1080 | if(NumJoysticks > 0) |
1081 | { |
1082 | // show joystick device selection if more than one available or just the joystick name if there is only one |
1083 | { |
1084 | CUIRect JoystickDropDown; |
1085 | View.HSplitTop(Cut: Spacing, pTop: nullptr, pBottom: &View); |
1086 | View.HSplitTop(Cut: ButtonHeight, pTop: &JoystickDropDown, pBottom: &View); |
1087 | if(NumJoysticks > 1) |
1088 | { |
1089 | static std::vector<std::string> s_vJoystickNames; |
1090 | static std::vector<const char *> s_vpJoystickNames; |
1091 | s_vJoystickNames.resize(new_size: NumJoysticks); |
1092 | s_vpJoystickNames.resize(new_size: NumJoysticks); |
1093 | |
1094 | for(int i = 0; i < NumJoysticks; ++i) |
1095 | { |
1096 | char aBuf[256]; |
1097 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s %d: %s" , Localize(pStr: "Controller" ), i, Input()->GetJoystick(Index: i)->GetName()); |
1098 | s_vJoystickNames[i] = aBuf; |
1099 | s_vpJoystickNames[i] = s_vJoystickNames[i].c_str(); |
1100 | } |
1101 | |
1102 | static CUi::SDropDownState s_JoystickDropDownState; |
1103 | static CScrollRegion s_JoystickDropDownScrollRegion; |
1104 | s_JoystickDropDownState.m_SelectionPopupContext.m_pScrollRegion = &s_JoystickDropDownScrollRegion; |
1105 | const int CurrentJoystick = Input()->GetActiveJoystick()->GetIndex(); |
1106 | const int NewJoystick = Ui()->DoDropDown(pRect: &JoystickDropDown, CurSelection: CurrentJoystick, pStrs: s_vpJoystickNames.data(), Num: s_vpJoystickNames.size(), State&: s_JoystickDropDownState); |
1107 | if(NewJoystick != CurrentJoystick) |
1108 | { |
1109 | Input()->SetActiveJoystick(NewJoystick); |
1110 | } |
1111 | } |
1112 | else |
1113 | { |
1114 | char aBuf[256]; |
1115 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s 0: %s" , Localize(pStr: "Controller" ), Input()->GetJoystick(Index: 0)->GetName()); |
1116 | Ui()->DoLabel(pRect: &JoystickDropDown, pText: aBuf, Size: 13.0f, Align: TEXTALIGN_ML); |
1117 | } |
1118 | } |
1119 | |
1120 | { |
1121 | View.HSplitTop(Cut: Spacing, pTop: nullptr, pBottom: &View); |
1122 | View.HSplitTop(Cut: ButtonHeight, pTop: &Button, pBottom: &View); |
1123 | CUIRect Label, ButtonRelative, ButtonAbsolute; |
1124 | Button.VSplitMid(pLeft: &Label, pRight: &Button, Spacing: 10.0f); |
1125 | Button.HMargin(Cut: 2.0f, pOtherRect: &Button); |
1126 | Button.VSplitMid(pLeft: &ButtonRelative, pRight: &ButtonAbsolute); |
1127 | Ui()->DoLabel(pRect: &Label, pText: Localize(pStr: "Ingame controller mode" ), Size: 13.0f, Align: TEXTALIGN_ML); |
1128 | CButtonContainer s_RelativeButton; |
1129 | if(DoButton_Menu(pButtonContainer: &s_RelativeButton, pText: Localize(pStr: "Relative" , pContext: "Ingame controller mode" ), Checked: g_Config.m_InpControllerAbsolute == 0, pRect: &ButtonRelative, pImageName: nullptr, Corners: IGraphics::CORNER_L)) |
1130 | { |
1131 | g_Config.m_InpControllerAbsolute = 0; |
1132 | } |
1133 | CButtonContainer s_AbsoluteButton; |
1134 | if(DoButton_Menu(pButtonContainer: &s_AbsoluteButton, pText: Localize(pStr: "Absolute" , pContext: "Ingame controller mode" ), Checked: g_Config.m_InpControllerAbsolute == 1, pRect: &ButtonAbsolute, pImageName: nullptr, Corners: IGraphics::CORNER_R)) |
1135 | { |
1136 | g_Config.m_InpControllerAbsolute = 1; |
1137 | } |
1138 | } |
1139 | |
1140 | if(!g_Config.m_InpControllerAbsolute) |
1141 | { |
1142 | View.HSplitTop(Cut: Spacing, pTop: nullptr, pBottom: &View); |
1143 | View.HSplitTop(Cut: ButtonHeight, pTop: &Button, pBottom: &View); |
1144 | Ui()->DoScrollbarOption(pId: &g_Config.m_InpControllerSens, pOption: &g_Config.m_InpControllerSens, pRect: &Button, pStr: Localize(pStr: "Ingame controller sens." ), Min: 1, Max: 500, pScale: &CUi::ms_LogarithmicScrollbarScale, Flags: CUi::SCROLLBAR_OPTION_NOCLAMPVALUE); |
1145 | } |
1146 | |
1147 | View.HSplitTop(Cut: Spacing, pTop: nullptr, pBottom: &View); |
1148 | View.HSplitTop(Cut: ButtonHeight, pTop: &Button, pBottom: &View); |
1149 | Ui()->DoScrollbarOption(pId: &g_Config.m_UiControllerSens, pOption: &g_Config.m_UiControllerSens, pRect: &Button, pStr: Localize(pStr: "UI controller sens." ), Min: 1, Max: 500, pScale: &CUi::ms_LogarithmicScrollbarScale, Flags: CUi::SCROLLBAR_OPTION_NOCLAMPVALUE); |
1150 | |
1151 | View.HSplitTop(Cut: Spacing, pTop: nullptr, pBottom: &View); |
1152 | View.HSplitTop(Cut: ButtonHeight, pTop: &Button, pBottom: &View); |
1153 | Ui()->DoScrollbarOption(pId: &g_Config.m_InpControllerTolerance, pOption: &g_Config.m_InpControllerTolerance, pRect: &Button, pStr: Localize(pStr: "Controller jitter tolerance" ), Min: 0, Max: 50); |
1154 | |
1155 | View.HSplitTop(Cut: Spacing, pTop: nullptr, pBottom: &View); |
1156 | View.Draw(Color: ColorRGBA(0.0f, 0.0f, 0.0f, 0.125f), Corners: IGraphics::CORNER_ALL, Rounding: 5.0f); |
1157 | DoJoystickAxisPicker(View); |
1158 | } |
1159 | else |
1160 | { |
1161 | View.HSplitTop(Cut: View.h - ButtonHeight, pTop: nullptr, pBottom: &View); |
1162 | View.HSplitTop(Cut: ButtonHeight, pTop: &Button, pBottom: &View); |
1163 | Ui()->DoLabel(pRect: &Button, pText: Localize(pStr: "No controller found. Plug in a controller." ), Size: 13.0f, Align: TEXTALIGN_ML); |
1164 | } |
1165 | } |
1166 | |
1167 | return BackgroundHeight; |
1168 | } |
1169 | |
1170 | void CMenus::(CUIRect View) |
1171 | { |
1172 | const float FontSize = 13.0f; |
1173 | const float RowHeight = 20.0f; |
1174 | const float SpacingH = 2.0f; |
1175 | const float AxisWidth = 0.2f * View.w; |
1176 | const float StatusWidth = 0.4f * View.w; |
1177 | const float AimBindWidth = 90.0f; |
1178 | const float SpacingV = (View.w - AxisWidth - StatusWidth - AimBindWidth) / 2.0f; |
1179 | |
1180 | CUIRect Row, Axis, Status, AimBind; |
1181 | View.HSplitTop(Cut: SpacingH, pTop: nullptr, pBottom: &View); |
1182 | View.HSplitTop(Cut: RowHeight, pTop: &Row, pBottom: &View); |
1183 | Row.VSplitLeft(Cut: AxisWidth, pLeft: &Axis, pRight: &Row); |
1184 | Row.VSplitLeft(Cut: SpacingV, pLeft: nullptr, pRight: &Row); |
1185 | Row.VSplitLeft(Cut: StatusWidth, pLeft: &Status, pRight: &Row); |
1186 | Row.VSplitLeft(Cut: SpacingV, pLeft: nullptr, pRight: &Row); |
1187 | Row.VSplitLeft(Cut: AimBindWidth, pLeft: &AimBind, pRight: &Row); |
1188 | |
1189 | Ui()->DoLabel(pRect: &Axis, pText: Localize(pStr: "Axis" ), Size: FontSize, Align: TEXTALIGN_MC); |
1190 | Ui()->DoLabel(pRect: &Status, pText: Localize(pStr: "Status" ), Size: FontSize, Align: TEXTALIGN_MC); |
1191 | Ui()->DoLabel(pRect: &AimBind, pText: Localize(pStr: "Aim bind" ), Size: FontSize, Align: TEXTALIGN_MC); |
1192 | |
1193 | IInput::IJoystick *pJoystick = Input()->GetActiveJoystick(); |
1194 | static int s_aActive[NUM_JOYSTICK_AXES][2]; |
1195 | for(int i = 0; i < minimum<int>(a: pJoystick->GetNumAxes(), b: NUM_JOYSTICK_AXES); i++) |
1196 | { |
1197 | const bool Active = g_Config.m_InpControllerX == i || g_Config.m_InpControllerY == i; |
1198 | |
1199 | View.HSplitTop(Cut: SpacingH, pTop: nullptr, pBottom: &View); |
1200 | View.HSplitTop(Cut: RowHeight, pTop: &Row, pBottom: &View); |
1201 | Row.Draw(Color: ColorRGBA(0.0f, 0.0f, 0.0f, 0.125f), Corners: IGraphics::CORNER_ALL, Rounding: 5.0f); |
1202 | Row.VSplitLeft(Cut: AxisWidth, pLeft: &Axis, pRight: &Row); |
1203 | Row.VSplitLeft(Cut: SpacingV, pLeft: nullptr, pRight: &Row); |
1204 | Row.VSplitLeft(Cut: StatusWidth, pLeft: &Status, pRight: &Row); |
1205 | Row.VSplitLeft(Cut: SpacingV, pLeft: nullptr, pRight: &Row); |
1206 | Row.VSplitLeft(Cut: AimBindWidth, pLeft: &AimBind, pRight: &Row); |
1207 | |
1208 | // Axis label |
1209 | char aBuf[16]; |
1210 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%d" , i + 1); |
1211 | if(Active) |
1212 | TextRender()->TextColor(rgb: TextRender()->DefaultTextColor()); |
1213 | else |
1214 | TextRender()->TextColor(r: 0.7f, g: 0.7f, b: 0.7f, a: 1.0f); |
1215 | Ui()->DoLabel(pRect: &Axis, pText: aBuf, Size: FontSize, Align: TEXTALIGN_MC); |
1216 | |
1217 | // Axis status |
1218 | Status.HMargin(Cut: 7.0f, pOtherRect: &Status); |
1219 | DoJoystickBar(pRect: &Status, Current: (pJoystick->GetAxisValue(Axis: i) + 1.0f) / 2.0f, Tolerance: g_Config.m_InpControllerTolerance / 50.0f, Active); |
1220 | |
1221 | // Bind to X/Y |
1222 | CUIRect AimBindX, AimBindY; |
1223 | AimBind.VSplitMid(pLeft: &AimBindX, pRight: &AimBindY); |
1224 | if(DoButton_CheckBox(pId: &s_aActive[i][0], pText: "X" , Checked: g_Config.m_InpControllerX == i, pRect: &AimBindX)) |
1225 | { |
1226 | if(g_Config.m_InpControllerY == i) |
1227 | g_Config.m_InpControllerY = g_Config.m_InpControllerX; |
1228 | g_Config.m_InpControllerX = i; |
1229 | } |
1230 | if(DoButton_CheckBox(pId: &s_aActive[i][1], pText: "Y" , Checked: g_Config.m_InpControllerY == i, pRect: &AimBindY)) |
1231 | { |
1232 | if(g_Config.m_InpControllerX == i) |
1233 | g_Config.m_InpControllerX = g_Config.m_InpControllerY; |
1234 | g_Config.m_InpControllerY = i; |
1235 | } |
1236 | } |
1237 | TextRender()->TextColor(rgb: TextRender()->DefaultTextColor()); |
1238 | } |
1239 | |
1240 | void CMenus::(const CUIRect *pRect, float Current, float Tolerance, bool Active) |
1241 | { |
1242 | CUIRect Handle; |
1243 | pRect->VSplitLeft(Cut: pRect->h, pLeft: &Handle, pRight: nullptr); // Slider size |
1244 | Handle.x += (pRect->w - Handle.w) * Current; |
1245 | |
1246 | pRect->Draw(Color: ColorRGBA(1.0f, 1.0f, 1.0f, Active ? 0.25f : 0.125f), Corners: IGraphics::CORNER_ALL, Rounding: pRect->h / 2.0f); |
1247 | |
1248 | CUIRect ToleranceArea = *pRect; |
1249 | ToleranceArea.w *= Tolerance; |
1250 | ToleranceArea.x += (pRect->w - ToleranceArea.w) / 2.0f; |
1251 | const ColorRGBA ToleranceColor = Active ? ColorRGBA(0.8f, 0.35f, 0.35f, 1.0f) : ColorRGBA(0.7f, 0.5f, 0.5f, 1.0f); |
1252 | ToleranceArea.Draw(Color: ToleranceColor, Corners: IGraphics::CORNER_ALL, Rounding: ToleranceArea.h / 2.0f); |
1253 | |
1254 | const ColorRGBA SliderColor = Active ? ColorRGBA(0.95f, 0.95f, 0.95f, 1.0f) : ColorRGBA(0.8f, 0.8f, 0.8f, 1.0f); |
1255 | Handle.Draw(Color: SliderColor, Corners: IGraphics::CORNER_ALL, Rounding: Handle.h / 2.0f); |
1256 | } |
1257 | |
1258 | void CMenus::(CUIRect MainView) |
1259 | { |
1260 | // this is kinda slow, but whatever |
1261 | for(auto &Key : gs_aKeys) |
1262 | Key.m_KeyId = Key.m_ModifierCombination = 0; |
1263 | |
1264 | for(int Mod = 0; Mod < CBinds::MODIFIER_COMBINATION_COUNT; Mod++) |
1265 | { |
1266 | for(int KeyId = 0; KeyId < KEY_LAST; KeyId++) |
1267 | { |
1268 | const char *pBind = m_pClient->m_Binds.Get(KeyId, ModifierCombination: Mod); |
1269 | if(!pBind[0]) |
1270 | continue; |
1271 | |
1272 | for(auto &Key : gs_aKeys) |
1273 | if(str_comp(a: pBind, b: Key.m_pCommand) == 0) |
1274 | { |
1275 | Key.m_KeyId = KeyId; |
1276 | Key.m_ModifierCombination = Mod; |
1277 | break; |
1278 | } |
1279 | } |
1280 | } |
1281 | |
1282 | // scrollable controls |
1283 | static float s_JoystickSettingsHeight = 0.0f; // we calculate this later and don't render until enough space is available |
1284 | static CScrollRegion s_ScrollRegion; |
1285 | vec2 ScrollOffset(0.0f, 0.0f); |
1286 | CScrollRegionParams ScrollParams; |
1287 | ScrollParams.m_ScrollUnit = 120.0f; |
1288 | s_ScrollRegion.Begin(pClipRect: &MainView, pOutOffset: &ScrollOffset, pParams: &ScrollParams); |
1289 | MainView.y += ScrollOffset.y; |
1290 | |
1291 | const float FontSize = 14.0f; |
1292 | const float Margin = 10.0f; |
1293 | const float = FontSize + 5.0f + Margin; |
1294 | |
1295 | CUIRect MouseSettings, MovementSettings, WeaponSettings, VotingSettings, ChatSettings, DummySettings, MiscSettings, JoystickSettings, ResetButton, Button; |
1296 | MainView.VSplitMid(pLeft: &MouseSettings, pRight: &VotingSettings); |
1297 | |
1298 | // mouse settings |
1299 | { |
1300 | MouseSettings.VMargin(Cut: 5.0f, pOtherRect: &MouseSettings); |
1301 | MouseSettings.HSplitTop(Cut: 80.0f, pTop: &MouseSettings, pBottom: &JoystickSettings); |
1302 | if(s_ScrollRegion.AddRect(Rect: MouseSettings)) |
1303 | { |
1304 | MouseSettings.Draw(Color: ColorRGBA(1, 1, 1, 0.25f), Corners: IGraphics::CORNER_ALL, Rounding: 10.0f); |
1305 | MouseSettings.VMargin(Cut: 10.0f, pOtherRect: &MouseSettings); |
1306 | |
1307 | MouseSettings.HSplitTop(Cut: HeaderHeight, pTop: &Button, pBottom: &MouseSettings); |
1308 | Ui()->DoLabel(pRect: &Button, pText: Localize(pStr: "Mouse" ), Size: FontSize, Align: TEXTALIGN_ML); |
1309 | |
1310 | MouseSettings.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MouseSettings); |
1311 | Ui()->DoScrollbarOption(pId: &g_Config.m_InpMousesens, pOption: &g_Config.m_InpMousesens, pRect: &Button, pStr: Localize(pStr: "Ingame mouse sens." ), Min: 1, Max: 500, pScale: &CUi::ms_LogarithmicScrollbarScale, Flags: CUi::SCROLLBAR_OPTION_NOCLAMPVALUE); |
1312 | |
1313 | MouseSettings.HSplitTop(Cut: 2.0f, pTop: nullptr, pBottom: &MouseSettings); |
1314 | |
1315 | MouseSettings.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MouseSettings); |
1316 | Ui()->DoScrollbarOption(pId: &g_Config.m_UiMousesens, pOption: &g_Config.m_UiMousesens, pRect: &Button, pStr: Localize(pStr: "UI mouse sens." ), Min: 1, Max: 500, pScale: &CUi::ms_LogarithmicScrollbarScale, Flags: CUi::SCROLLBAR_OPTION_NOCLAMPVALUE); |
1317 | } |
1318 | } |
1319 | |
1320 | // joystick settings |
1321 | { |
1322 | JoystickSettings.HSplitTop(Cut: Margin, pTop: nullptr, pBottom: &JoystickSettings); |
1323 | JoystickSettings.HSplitTop(Cut: s_JoystickSettingsHeight + HeaderHeight + Margin, pTop: &JoystickSettings, pBottom: &MovementSettings); |
1324 | if(s_ScrollRegion.AddRect(Rect: JoystickSettings)) |
1325 | { |
1326 | JoystickSettings.Draw(Color: ColorRGBA(1, 1, 1, 0.25f), Corners: IGraphics::CORNER_ALL, Rounding: 10.0f); |
1327 | JoystickSettings.VMargin(Cut: Margin, pOtherRect: &JoystickSettings); |
1328 | |
1329 | JoystickSettings.HSplitTop(Cut: HeaderHeight, pTop: &Button, pBottom: &JoystickSettings); |
1330 | Ui()->DoLabel(pRect: &Button, pText: Localize(pStr: "Controller" ), Size: FontSize, Align: TEXTALIGN_ML); |
1331 | |
1332 | s_JoystickSettingsHeight = RenderSettingsControlsJoystick(View: JoystickSettings); |
1333 | } |
1334 | } |
1335 | |
1336 | // movement settings |
1337 | { |
1338 | MovementSettings.HSplitTop(Cut: Margin, pTop: nullptr, pBottom: &MovementSettings); |
1339 | MovementSettings.HSplitTop(Cut: 365.0f, pTop: &MovementSettings, pBottom: &WeaponSettings); |
1340 | if(s_ScrollRegion.AddRect(Rect: MovementSettings)) |
1341 | { |
1342 | MovementSettings.Draw(Color: ColorRGBA(1, 1, 1, 0.25f), Corners: IGraphics::CORNER_ALL, Rounding: 10.0f); |
1343 | MovementSettings.VMargin(Cut: Margin, pOtherRect: &MovementSettings); |
1344 | |
1345 | MovementSettings.HSplitTop(Cut: HeaderHeight, pTop: &Button, pBottom: &MovementSettings); |
1346 | Ui()->DoLabel(pRect: &Button, pText: Localize(pStr: "Movement" ), Size: FontSize, Align: TEXTALIGN_ML); |
1347 | |
1348 | DoSettingsControlsButtons(Start: 0, Stop: 15, View: MovementSettings); |
1349 | } |
1350 | } |
1351 | |
1352 | // weapon settings |
1353 | { |
1354 | WeaponSettings.HSplitTop(Cut: Margin, pTop: nullptr, pBottom: &WeaponSettings); |
1355 | WeaponSettings.HSplitTop(Cut: 190.0f, pTop: &WeaponSettings, pBottom: &ResetButton); |
1356 | if(s_ScrollRegion.AddRect(Rect: WeaponSettings)) |
1357 | { |
1358 | WeaponSettings.Draw(Color: ColorRGBA(1, 1, 1, 0.25f), Corners: IGraphics::CORNER_ALL, Rounding: 10.0f); |
1359 | WeaponSettings.VMargin(Cut: Margin, pOtherRect: &WeaponSettings); |
1360 | |
1361 | WeaponSettings.HSplitTop(Cut: HeaderHeight, pTop: &Button, pBottom: &WeaponSettings); |
1362 | Ui()->DoLabel(pRect: &Button, pText: Localize(pStr: "Weapon" ), Size: FontSize, Align: TEXTALIGN_ML); |
1363 | |
1364 | DoSettingsControlsButtons(Start: 15, Stop: 22, View: WeaponSettings); |
1365 | } |
1366 | } |
1367 | |
1368 | // defaults |
1369 | { |
1370 | ResetButton.HSplitTop(Cut: Margin, pTop: nullptr, pBottom: &ResetButton); |
1371 | ResetButton.HSplitTop(Cut: 40.0f, pTop: &ResetButton, pBottom: nullptr); |
1372 | if(s_ScrollRegion.AddRect(Rect: ResetButton)) |
1373 | { |
1374 | ResetButton.Draw(Color: ColorRGBA(1, 1, 1, 0.25f), Corners: IGraphics::CORNER_ALL, Rounding: 10.0f); |
1375 | ResetButton.Margin(Cut: 10.0f, pOtherRect: &ResetButton); |
1376 | static CButtonContainer s_DefaultButton; |
1377 | if(DoButton_Menu(pButtonContainer: &s_DefaultButton, pText: Localize(pStr: "Reset to defaults" ), Checked: 0, pRect: &ResetButton)) |
1378 | { |
1379 | PopupConfirm(pTitle: Localize(pStr: "Reset controls" ), pMessage: Localize(pStr: "Are you sure that you want to reset the controls to their defaults?" ), |
1380 | pConfirmButtonLabel: Localize(pStr: "Reset" ), pCancelButtonLabel: Localize(pStr: "Cancel" ), pfnConfirmButtonCallback: &CMenus::ResetSettingsControls); |
1381 | } |
1382 | } |
1383 | } |
1384 | |
1385 | // voting settings |
1386 | { |
1387 | VotingSettings.VMargin(Cut: 5.0f, pOtherRect: &VotingSettings); |
1388 | VotingSettings.HSplitTop(Cut: 80.0f, pTop: &VotingSettings, pBottom: &ChatSettings); |
1389 | if(s_ScrollRegion.AddRect(Rect: VotingSettings)) |
1390 | { |
1391 | VotingSettings.Draw(Color: ColorRGBA(1, 1, 1, 0.25f), Corners: IGraphics::CORNER_ALL, Rounding: 10.0f); |
1392 | VotingSettings.VMargin(Cut: Margin, pOtherRect: &VotingSettings); |
1393 | |
1394 | VotingSettings.HSplitTop(Cut: HeaderHeight, pTop: &Button, pBottom: &VotingSettings); |
1395 | Ui()->DoLabel(pRect: &Button, pText: Localize(pStr: "Voting" ), Size: FontSize, Align: TEXTALIGN_ML); |
1396 | |
1397 | DoSettingsControlsButtons(Start: 22, Stop: 24, View: VotingSettings); |
1398 | } |
1399 | } |
1400 | |
1401 | // chat settings |
1402 | { |
1403 | ChatSettings.HSplitTop(Cut: Margin, pTop: nullptr, pBottom: &ChatSettings); |
1404 | ChatSettings.HSplitTop(Cut: 145.0f, pTop: &ChatSettings, pBottom: &DummySettings); |
1405 | if(s_ScrollRegion.AddRect(Rect: ChatSettings)) |
1406 | { |
1407 | ChatSettings.Draw(Color: ColorRGBA(1, 1, 1, 0.25f), Corners: IGraphics::CORNER_ALL, Rounding: 10.0f); |
1408 | ChatSettings.VMargin(Cut: Margin, pOtherRect: &ChatSettings); |
1409 | |
1410 | ChatSettings.HSplitTop(Cut: HeaderHeight, pTop: &Button, pBottom: &ChatSettings); |
1411 | Ui()->DoLabel(pRect: &Button, pText: Localize(pStr: "Chat" ), Size: FontSize, Align: TEXTALIGN_ML); |
1412 | |
1413 | DoSettingsControlsButtons(Start: 24, Stop: 29, View: ChatSettings); |
1414 | } |
1415 | } |
1416 | |
1417 | // dummy settings |
1418 | { |
1419 | DummySettings.HSplitTop(Cut: Margin, pTop: nullptr, pBottom: &DummySettings); |
1420 | DummySettings.HSplitTop(Cut: 100.0f, pTop: &DummySettings, pBottom: &MiscSettings); |
1421 | if(s_ScrollRegion.AddRect(Rect: DummySettings)) |
1422 | { |
1423 | DummySettings.Draw(Color: ColorRGBA(1, 1, 1, 0.25f), Corners: IGraphics::CORNER_ALL, Rounding: 10.0f); |
1424 | DummySettings.VMargin(Cut: Margin, pOtherRect: &DummySettings); |
1425 | |
1426 | DummySettings.HSplitTop(Cut: HeaderHeight, pTop: &Button, pBottom: &DummySettings); |
1427 | Ui()->DoLabel(pRect: &Button, pText: Localize(pStr: "Dummy" ), Size: FontSize, Align: TEXTALIGN_ML); |
1428 | |
1429 | DoSettingsControlsButtons(Start: 29, Stop: 32, View: DummySettings); |
1430 | } |
1431 | } |
1432 | |
1433 | // misc settings |
1434 | { |
1435 | MiscSettings.HSplitTop(Cut: Margin, pTop: nullptr, pBottom: &MiscSettings); |
1436 | MiscSettings.HSplitTop(Cut: 300.0f, pTop: &MiscSettings, pBottom: 0); |
1437 | if(s_ScrollRegion.AddRect(Rect: MiscSettings)) |
1438 | { |
1439 | MiscSettings.Draw(Color: ColorRGBA(1, 1, 1, 0.25f), Corners: IGraphics::CORNER_ALL, Rounding: 10.0f); |
1440 | MiscSettings.VMargin(Cut: Margin, pOtherRect: &MiscSettings); |
1441 | |
1442 | MiscSettings.HSplitTop(Cut: HeaderHeight, pTop: &Button, pBottom: &MiscSettings); |
1443 | Ui()->DoLabel(pRect: &Button, pText: Localize(pStr: "Miscellaneous" ), Size: FontSize, Align: TEXTALIGN_ML); |
1444 | |
1445 | DoSettingsControlsButtons(Start: 32, Stop: 44, View: MiscSettings); |
1446 | } |
1447 | } |
1448 | |
1449 | s_ScrollRegion.End(); |
1450 | } |
1451 | |
1452 | void CMenus::() |
1453 | { |
1454 | m_pClient->m_Binds.SetDefaults(); |
1455 | |
1456 | g_Config.m_InpMousesens = 200; |
1457 | g_Config.m_UiMousesens = 200; |
1458 | |
1459 | g_Config.m_InpControllerEnable = 0; |
1460 | g_Config.m_InpControllerGUID[0] = '\0'; |
1461 | g_Config.m_InpControllerAbsolute = 0; |
1462 | g_Config.m_InpControllerSens = 100; |
1463 | g_Config.m_InpControllerX = 0; |
1464 | g_Config.m_InpControllerY = 1; |
1465 | g_Config.m_InpControllerTolerance = 5; |
1466 | g_Config.m_UiControllerSens = 100; |
1467 | } |
1468 | |
1469 | void CMenus::(CUIRect MainView) |
1470 | { |
1471 | CUIRect Button; |
1472 | char aBuf[128]; |
1473 | bool CheckSettings = false; |
1474 | |
1475 | static const int MAX_RESOLUTIONS = 256; |
1476 | static CVideoMode s_aModes[MAX_RESOLUTIONS]; |
1477 | static int s_NumNodes = Graphics()->GetVideoModes(pModes: s_aModes, MaxModes: MAX_RESOLUTIONS, Screen: g_Config.m_GfxScreen); |
1478 | static int s_GfxFsaaSamples = g_Config.m_GfxFsaaSamples; |
1479 | static bool s_GfxBackendChanged = false; |
1480 | static bool s_GfxGpuChanged = false; |
1481 | static int s_GfxHighdpi = g_Config.m_GfxHighdpi; |
1482 | |
1483 | static int s_InitDisplayAllVideoModes = g_Config.m_GfxDisplayAllVideoModes; |
1484 | |
1485 | static bool s_WasInit = false; |
1486 | static bool s_ModesReload = false; |
1487 | if(!s_WasInit) |
1488 | { |
1489 | s_WasInit = true; |
1490 | |
1491 | Graphics()->AddWindowPropChangeListener(pFunc: []() { |
1492 | s_ModesReload = true; |
1493 | }); |
1494 | } |
1495 | |
1496 | if(s_ModesReload || g_Config.m_GfxDisplayAllVideoModes != s_InitDisplayAllVideoModes) |
1497 | { |
1498 | s_NumNodes = Graphics()->GetVideoModes(pModes: s_aModes, MaxModes: MAX_RESOLUTIONS, Screen: g_Config.m_GfxScreen); |
1499 | s_ModesReload = false; |
1500 | s_InitDisplayAllVideoModes = g_Config.m_GfxDisplayAllVideoModes; |
1501 | } |
1502 | |
1503 | CUIRect ModeList, ModeLabel; |
1504 | MainView.VSplitLeft(Cut: 350.0f, pLeft: &MainView, pRight: &ModeList); |
1505 | ModeList.HSplitTop(Cut: 24.0f, pTop: &ModeLabel, pBottom: &ModeList); |
1506 | MainView.VSplitLeft(Cut: 340.0f, pLeft: &MainView, pRight: 0); |
1507 | |
1508 | // display mode list |
1509 | static CListBox s_ListBox; |
1510 | static const float sc_RowHeightResList = 22.0f; |
1511 | static const float = 12.0f; |
1512 | static const float sc_FontSizeResList = 10.0f; |
1513 | |
1514 | { |
1515 | int G = std::gcd(m: g_Config.m_GfxScreenWidth, n: g_Config.m_GfxScreenHeight); |
1516 | 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); |
1517 | Ui()->DoLabel(pRect: &ModeLabel, pText: aBuf, Size: sc_FontSizeResListHeader, Align: TEXTALIGN_MC); |
1518 | } |
1519 | |
1520 | int OldSelected = -1; |
1521 | s_ListBox.SetActive(!Ui()->IsPopupOpen()); |
1522 | s_ListBox.DoStart(RowHeight: sc_RowHeightResList, NumItems: s_NumNodes, ItemsPerRow: 1, RowsPerScroll: 3, SelectedIndex: OldSelected, pRect: &ModeList); |
1523 | |
1524 | for(int i = 0; i < s_NumNodes; ++i) |
1525 | { |
1526 | const int Depth = s_aModes[i].m_Red + s_aModes[i].m_Green + s_aModes[i].m_Blue > 16 ? 24 : 16; |
1527 | if(g_Config.m_GfxColorDepth == Depth && |
1528 | g_Config.m_GfxScreenWidth == s_aModes[i].m_WindowWidth && |
1529 | g_Config.m_GfxScreenHeight == s_aModes[i].m_WindowHeight && |
1530 | g_Config.m_GfxScreenRefreshRate == s_aModes[i].m_RefreshRate) |
1531 | { |
1532 | OldSelected = i; |
1533 | } |
1534 | |
1535 | const CListboxItem Item = s_ListBox.DoNextItem(pId: &s_aModes[i], Selected: OldSelected == i); |
1536 | if(!Item.m_Visible) |
1537 | continue; |
1538 | |
1539 | int G = std::gcd(m: s_aModes[i].m_CanvasWidth, n: s_aModes[i].m_CanvasHeight); |
1540 | 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_CanvasWidth / G, s_aModes[i].m_CanvasHeight / G); |
1541 | Ui()->DoLabel(pRect: &Item.m_Rect, pText: aBuf, Size: sc_FontSizeResList, Align: TEXTALIGN_ML); |
1542 | } |
1543 | |
1544 | const int NewSelected = s_ListBox.DoEnd(); |
1545 | if(OldSelected != NewSelected) |
1546 | { |
1547 | const int Depth = s_aModes[NewSelected].m_Red + s_aModes[NewSelected].m_Green + s_aModes[NewSelected].m_Blue > 16 ? 24 : 16; |
1548 | g_Config.m_GfxColorDepth = Depth; |
1549 | g_Config.m_GfxScreenWidth = s_aModes[NewSelected].m_WindowWidth; |
1550 | g_Config.m_GfxScreenHeight = s_aModes[NewSelected].m_WindowHeight; |
1551 | g_Config.m_GfxScreenRefreshRate = s_aModes[NewSelected].m_RefreshRate; |
1552 | Graphics()->ResizeToScreen(); |
1553 | } |
1554 | |
1555 | // switches |
1556 | CUIRect WindowModeDropDown; |
1557 | MainView.HSplitTop(Cut: 20.0f, pTop: &WindowModeDropDown, pBottom: &MainView); |
1558 | |
1559 | const char *apWindowModes[] = {Localize(pStr: "Windowed" ), Localize(pStr: "Windowed borderless" ), Localize(pStr: "Windowed fullscreen" ), Localize(pStr: "Desktop fullscreen" ), Localize(pStr: "Fullscreen" )}; |
1560 | static const int s_NumWindowMode = std::size(apWindowModes); |
1561 | |
1562 | 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)); |
1563 | |
1564 | static CUi::SDropDownState s_WindowModeDropDownState; |
1565 | static CScrollRegion s_WindowModeDropDownScrollRegion; |
1566 | s_WindowModeDropDownState.m_SelectionPopupContext.m_pScrollRegion = &s_WindowModeDropDownScrollRegion; |
1567 | const int NewWindowMode = Ui()->DoDropDown(pRect: &WindowModeDropDown, CurSelection: OldWindowMode, pStrs: apWindowModes, Num: s_NumWindowMode, State&: s_WindowModeDropDownState); |
1568 | if(OldWindowMode != NewWindowMode) |
1569 | { |
1570 | if(NewWindowMode == 0) |
1571 | Client()->SetWindowParams(FullscreenMode: 0, IsBorderless: false); |
1572 | else if(NewWindowMode == 1) |
1573 | Client()->SetWindowParams(FullscreenMode: 0, IsBorderless: true); |
1574 | else if(NewWindowMode == 2) |
1575 | Client()->SetWindowParams(FullscreenMode: 3, IsBorderless: false); |
1576 | else if(NewWindowMode == 3) |
1577 | Client()->SetWindowParams(FullscreenMode: 2, IsBorderless: false); |
1578 | else if(NewWindowMode == 4) |
1579 | Client()->SetWindowParams(FullscreenMode: 1, IsBorderless: false); |
1580 | } |
1581 | |
1582 | if(Graphics()->GetNumScreens() > 1) |
1583 | { |
1584 | CUIRect ScreenDropDown; |
1585 | MainView.HSplitTop(Cut: 2.0f, pTop: nullptr, pBottom: &MainView); |
1586 | MainView.HSplitTop(Cut: 20.0f, pTop: &ScreenDropDown, pBottom: &MainView); |
1587 | |
1588 | const int NumScreens = Graphics()->GetNumScreens(); |
1589 | static std::vector<std::string> s_vScreenNames; |
1590 | static std::vector<const char *> s_vpScreenNames; |
1591 | s_vScreenNames.resize(new_size: NumScreens); |
1592 | s_vpScreenNames.resize(new_size: NumScreens); |
1593 | |
1594 | for(int i = 0; i < NumScreens; ++i) |
1595 | { |
1596 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s %d: %s" , Localize(pStr: "Screen" ), i, Graphics()->GetScreenName(Screen: i)); |
1597 | s_vScreenNames[i] = aBuf; |
1598 | s_vpScreenNames[i] = s_vScreenNames[i].c_str(); |
1599 | } |
1600 | |
1601 | static CUi::SDropDownState s_ScreenDropDownState; |
1602 | static CScrollRegion s_ScreenDropDownScrollRegion; |
1603 | s_ScreenDropDownState.m_SelectionPopupContext.m_pScrollRegion = &s_ScreenDropDownScrollRegion; |
1604 | const int NewScreen = Ui()->DoDropDown(pRect: &ScreenDropDown, CurSelection: g_Config.m_GfxScreen, pStrs: s_vpScreenNames.data(), Num: s_vpScreenNames.size(), State&: s_ScreenDropDownState); |
1605 | if(NewScreen != g_Config.m_GfxScreen) |
1606 | Client()->SwitchWindowScreen(Index: NewScreen); |
1607 | } |
1608 | |
1609 | MainView.HSplitTop(Cut: 2.0f, pTop: nullptr, pBottom: &MainView); |
1610 | MainView.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MainView); |
1611 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s (%s)" , Localize(pStr: "V-Sync" ), Localize(pStr: "may cause delay" )); |
1612 | if(DoButton_CheckBox(pId: &g_Config.m_GfxVsync, pText: aBuf, Checked: g_Config.m_GfxVsync, pRect: &Button)) |
1613 | { |
1614 | Client()->ToggleWindowVSync(); |
1615 | } |
1616 | |
1617 | bool MultiSamplingChanged = false; |
1618 | MainView.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MainView); |
1619 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s (%s)" , Localize(pStr: "FSAA samples" ), Localize(pStr: "may cause delay" )); |
1620 | int GfxFsaaSamplesMouseButton = DoButton_CheckBox_Number(pId: &g_Config.m_GfxFsaaSamples, pText: aBuf, Checked: g_Config.m_GfxFsaaSamples, pRect: &Button); |
1621 | int CurFSAA = g_Config.m_GfxFsaaSamples == 0 ? 1 : g_Config.m_GfxFsaaSamples; |
1622 | if(GfxFsaaSamplesMouseButton == 1) // inc |
1623 | { |
1624 | g_Config.m_GfxFsaaSamples = std::pow(x: 2, y: (int)std::log2(x: CurFSAA) + 1); |
1625 | if(g_Config.m_GfxFsaaSamples > 64) |
1626 | g_Config.m_GfxFsaaSamples = 0; |
1627 | MultiSamplingChanged = true; |
1628 | } |
1629 | else if(GfxFsaaSamplesMouseButton == 2) // dec |
1630 | { |
1631 | if(CurFSAA == 1) |
1632 | g_Config.m_GfxFsaaSamples = 64; |
1633 | else if(CurFSAA == 2) |
1634 | g_Config.m_GfxFsaaSamples = 0; |
1635 | else |
1636 | g_Config.m_GfxFsaaSamples = std::pow(x: 2, y: (int)std::log2(x: CurFSAA) - 1); |
1637 | MultiSamplingChanged = true; |
1638 | } |
1639 | |
1640 | uint32_t MultiSamplingCountBackend = 0; |
1641 | if(MultiSamplingChanged) |
1642 | { |
1643 | if(Graphics()->SetMultiSampling(ReqMultiSamplingCount: g_Config.m_GfxFsaaSamples, MultiSamplingCountBackend)) |
1644 | { |
1645 | // try again with 0 if mouse click was increasing multi sampling |
1646 | // else just accept the current value as is |
1647 | if((uint32_t)g_Config.m_GfxFsaaSamples > MultiSamplingCountBackend && GfxFsaaSamplesMouseButton == 1) |
1648 | Graphics()->SetMultiSampling(ReqMultiSamplingCount: 0, MultiSamplingCountBackend); |
1649 | g_Config.m_GfxFsaaSamples = (int)MultiSamplingCountBackend; |
1650 | } |
1651 | else |
1652 | { |
1653 | CheckSettings = true; |
1654 | } |
1655 | } |
1656 | |
1657 | MainView.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MainView); |
1658 | if(DoButton_CheckBox(pId: &g_Config.m_GfxHighDetail, pText: Localize(pStr: "High Detail" ), Checked: g_Config.m_GfxHighDetail, pRect: &Button)) |
1659 | g_Config.m_GfxHighDetail ^= 1; |
1660 | GameClient()->m_Tooltips.DoToolTip(pId: &g_Config.m_GfxHighDetail, pNearRect: &Button, pText: Localize(pStr: "Allows maps to render with more detail" )); |
1661 | |
1662 | MainView.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MainView); |
1663 | if(DoButton_CheckBox(pId: &g_Config.m_GfxHighdpi, pText: Localize(pStr: "Use high DPI" ), Checked: g_Config.m_GfxHighdpi, pRect: &Button)) |
1664 | { |
1665 | CheckSettings = true; |
1666 | g_Config.m_GfxHighdpi ^= 1; |
1667 | } |
1668 | |
1669 | MainView.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MainView); |
1670 | 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, pSuffix: " Hz" ); |
1671 | |
1672 | MainView.HSplitTop(Cut: 2.0f, pTop: nullptr, pBottom: &MainView); |
1673 | static CButtonContainer s_UiColorResetId; |
1674 | 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); |
1675 | |
1676 | // Backend list |
1677 | struct |
1678 | { |
1679 | int m_Major = 0; |
1680 | int m_Minor = 0; |
1681 | int m_Patch = 0; |
1682 | const char *m_pBackendName = "" ; |
1683 | bool m_Found = false; |
1684 | }; |
1685 | std::array<std::array<SMenuBackendInfo, EGraphicsDriverAgeType::GRAPHICS_DRIVER_AGE_TYPE_COUNT>, EBackendType::BACKEND_TYPE_COUNT> aaSupportedBackends{}; |
1686 | uint32_t FoundBackendCount = 0; |
1687 | for(uint32_t i = 0; i < BACKEND_TYPE_COUNT; ++i) |
1688 | { |
1689 | if(EBackendType(i) == BACKEND_TYPE_AUTO) |
1690 | continue; |
1691 | for(uint32_t n = 0; n < GRAPHICS_DRIVER_AGE_TYPE_COUNT; ++n) |
1692 | { |
1693 | auto &Info = aaSupportedBackends[i][n]; |
1694 | 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))) |
1695 | { |
1696 | // don't count blocked opengl drivers |
1697 | if(EBackendType(i) != BACKEND_TYPE_OPENGL || EGraphicsDriverAgeType(n) == GRAPHICS_DRIVER_AGE_TYPE_LEGACY || g_Config.m_GfxDriverIsBlocked == 0) |
1698 | { |
1699 | Info.m_Found = true; |
1700 | ++FoundBackendCount; |
1701 | } |
1702 | } |
1703 | } |
1704 | } |
1705 | |
1706 | if(FoundBackendCount > 1) |
1707 | { |
1708 | CUIRect Text, BackendDropDown; |
1709 | MainView.HSplitTop(Cut: 10.0f, pTop: nullptr, pBottom: &MainView); |
1710 | MainView.HSplitTop(Cut: 20.0f, pTop: &Text, pBottom: &MainView); |
1711 | MainView.HSplitTop(Cut: 2.0f, pTop: nullptr, pBottom: &MainView); |
1712 | MainView.HSplitTop(Cut: 20.0f, pTop: &BackendDropDown, pBottom: &MainView); |
1713 | Ui()->DoLabel(pRect: &Text, pText: Localize(pStr: "Renderer" ), Size: 16.0f, Align: TEXTALIGN_MC); |
1714 | |
1715 | static std::vector<std::string> s_vBackendIdNames; |
1716 | static std::vector<const char *> s_vpBackendIdNamesCStr; |
1717 | static std::vector<SMenuBackendInfo> s_vBackendInfos; |
1718 | |
1719 | size_t BackendCount = FoundBackendCount + 1; |
1720 | s_vBackendIdNames.resize(new_size: BackendCount); |
1721 | s_vpBackendIdNamesCStr.resize(new_size: BackendCount); |
1722 | s_vBackendInfos.resize(new_size: BackendCount); |
1723 | |
1724 | char aTmpBackendName[256]; |
1725 | |
1726 | auto IsInfoDefault = [](const SMenuBackendInfo &CheckInfo) { |
1727 | 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; |
1728 | }; |
1729 | |
1730 | int OldSelectedBackend = -1; |
1731 | uint32_t CurCounter = 0; |
1732 | for(uint32_t i = 0; i < BACKEND_TYPE_COUNT; ++i) |
1733 | { |
1734 | for(uint32_t n = 0; n < GRAPHICS_DRIVER_AGE_TYPE_COUNT; ++n) |
1735 | { |
1736 | auto &Info = aaSupportedBackends[i][n]; |
1737 | if(Info.m_Found) |
1738 | { |
1739 | bool IsDefault = IsInfoDefault(Info); |
1740 | 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" ) : "" ); |
1741 | s_vBackendIdNames[CurCounter] = aTmpBackendName; |
1742 | s_vpBackendIdNamesCStr[CurCounter] = s_vBackendIdNames[CurCounter].c_str(); |
1743 | 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) |
1744 | { |
1745 | OldSelectedBackend = CurCounter; |
1746 | } |
1747 | |
1748 | s_vBackendInfos[CurCounter] = Info; |
1749 | ++CurCounter; |
1750 | } |
1751 | } |
1752 | } |
1753 | |
1754 | if(OldSelectedBackend != -1) |
1755 | { |
1756 | // no custom selected |
1757 | BackendCount -= 1; |
1758 | } |
1759 | else |
1760 | { |
1761 | // custom selected one |
1762 | 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); |
1763 | s_vBackendIdNames[CurCounter] = aTmpBackendName; |
1764 | s_vpBackendIdNamesCStr[CurCounter] = s_vBackendIdNames[CurCounter].c_str(); |
1765 | OldSelectedBackend = CurCounter; |
1766 | |
1767 | s_vBackendInfos[CurCounter].m_pBackendName = "custom" ; |
1768 | s_vBackendInfos[CurCounter].m_Major = g_Config.m_GfxGLMajor; |
1769 | s_vBackendInfos[CurCounter].m_Minor = g_Config.m_GfxGLMinor; |
1770 | s_vBackendInfos[CurCounter].m_Patch = g_Config.m_GfxGLPatch; |
1771 | } |
1772 | |
1773 | static int s_OldSelectedBackend = -1; |
1774 | if(s_OldSelectedBackend == -1) |
1775 | s_OldSelectedBackend = OldSelectedBackend; |
1776 | |
1777 | static CUi::SDropDownState s_BackendDropDownState; |
1778 | static CScrollRegion s_BackendDropDownScrollRegion; |
1779 | s_BackendDropDownState.m_SelectionPopupContext.m_pScrollRegion = &s_BackendDropDownScrollRegion; |
1780 | const int NewBackend = Ui()->DoDropDown(pRect: &BackendDropDown, CurSelection: OldSelectedBackend, pStrs: s_vpBackendIdNamesCStr.data(), Num: BackendCount, State&: s_BackendDropDownState); |
1781 | if(OldSelectedBackend != NewBackend) |
1782 | { |
1783 | str_copy(dst&: g_Config.m_GfxBackend, src: s_vBackendInfos[NewBackend].m_pBackendName); |
1784 | g_Config.m_GfxGLMajor = s_vBackendInfos[NewBackend].m_Major; |
1785 | g_Config.m_GfxGLMinor = s_vBackendInfos[NewBackend].m_Minor; |
1786 | g_Config.m_GfxGLPatch = s_vBackendInfos[NewBackend].m_Patch; |
1787 | |
1788 | CheckSettings = true; |
1789 | s_GfxBackendChanged = s_OldSelectedBackend != NewBackend; |
1790 | } |
1791 | } |
1792 | |
1793 | // GPU list |
1794 | const auto &GpuList = Graphics()->GetGpus(); |
1795 | if(GpuList.m_vGpus.size() > 1) |
1796 | { |
1797 | CUIRect Text, GpuDropDown; |
1798 | MainView.HSplitTop(Cut: 10.0f, pTop: nullptr, pBottom: &MainView); |
1799 | MainView.HSplitTop(Cut: 20.0f, pTop: &Text, pBottom: &MainView); |
1800 | MainView.HSplitTop(Cut: 2.0f, pTop: nullptr, pBottom: &MainView); |
1801 | MainView.HSplitTop(Cut: 20.0f, pTop: &GpuDropDown, pBottom: &MainView); |
1802 | Ui()->DoLabel(pRect: &Text, pText: Localize(pStr: "Graphics card" ), Size: 16.0f, Align: TEXTALIGN_MC); |
1803 | |
1804 | static std::vector<const char *> s_vpGpuIdNames; |
1805 | |
1806 | size_t GpuCount = GpuList.m_vGpus.size() + 1; |
1807 | s_vpGpuIdNames.resize(new_size: GpuCount); |
1808 | |
1809 | char aCurDeviceName[256 + 4]; |
1810 | |
1811 | int OldSelectedGpu = -1; |
1812 | for(size_t i = 0; i < GpuCount; ++i) |
1813 | { |
1814 | if(i == 0) |
1815 | { |
1816 | str_format(buffer: aCurDeviceName, buffer_size: sizeof(aCurDeviceName), format: "%s (%s)" , Localize(pStr: "auto" ), GpuList.m_AutoGpu.m_aName); |
1817 | s_vpGpuIdNames[i] = aCurDeviceName; |
1818 | if(str_comp(a: "auto" , b: g_Config.m_GfxGpuName) == 0) |
1819 | { |
1820 | OldSelectedGpu = 0; |
1821 | } |
1822 | } |
1823 | else |
1824 | { |
1825 | s_vpGpuIdNames[i] = GpuList.m_vGpus[i - 1].m_aName; |
1826 | if(str_comp(a: GpuList.m_vGpus[i - 1].m_aName, b: g_Config.m_GfxGpuName) == 0) |
1827 | { |
1828 | OldSelectedGpu = i; |
1829 | } |
1830 | } |
1831 | } |
1832 | |
1833 | static int s_OldSelectedGpu = -1; |
1834 | if(s_OldSelectedGpu == -1) |
1835 | s_OldSelectedGpu = OldSelectedGpu; |
1836 | |
1837 | static CUi::SDropDownState s_GpuDropDownState; |
1838 | static CScrollRegion s_GpuDropDownScrollRegion; |
1839 | s_GpuDropDownState.m_SelectionPopupContext.m_pScrollRegion = &s_GpuDropDownScrollRegion; |
1840 | const int NewGpu = Ui()->DoDropDown(pRect: &GpuDropDown, CurSelection: OldSelectedGpu, pStrs: s_vpGpuIdNames.data(), Num: GpuCount, State&: s_GpuDropDownState); |
1841 | if(OldSelectedGpu != NewGpu) |
1842 | { |
1843 | if(NewGpu == 0) |
1844 | str_copy(dst&: g_Config.m_GfxGpuName, src: "auto" ); |
1845 | else |
1846 | str_copy(dst&: g_Config.m_GfxGpuName, src: GpuList.m_vGpus[NewGpu - 1].m_aName); |
1847 | CheckSettings = true; |
1848 | s_GfxGpuChanged = NewGpu != s_OldSelectedGpu; |
1849 | } |
1850 | } |
1851 | |
1852 | // check if the new settings require a restart |
1853 | if(CheckSettings) |
1854 | { |
1855 | m_NeedRestartGraphics = !(s_GfxFsaaSamples == g_Config.m_GfxFsaaSamples && |
1856 | !s_GfxBackendChanged && |
1857 | !s_GfxGpuChanged && |
1858 | s_GfxHighdpi == g_Config.m_GfxHighdpi); |
1859 | } |
1860 | } |
1861 | |
1862 | void CMenus::(CUIRect MainView) |
1863 | { |
1864 | static int s_SndEnable = g_Config.m_SndEnable; |
1865 | |
1866 | CUIRect Button; |
1867 | MainView.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MainView); |
1868 | if(DoButton_CheckBox(pId: &g_Config.m_SndEnable, pText: Localize(pStr: "Use sounds" ), Checked: g_Config.m_SndEnable, pRect: &Button)) |
1869 | { |
1870 | g_Config.m_SndEnable ^= 1; |
1871 | UpdateMusicState(); |
1872 | m_NeedRestartSound = g_Config.m_SndEnable && !s_SndEnable; |
1873 | } |
1874 | |
1875 | if(!g_Config.m_SndEnable) |
1876 | return; |
1877 | |
1878 | MainView.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MainView); |
1879 | if(DoButton_CheckBox(pId: &g_Config.m_SndMusic, pText: Localize(pStr: "Play background music" ), Checked: g_Config.m_SndMusic, pRect: &Button)) |
1880 | { |
1881 | g_Config.m_SndMusic ^= 1; |
1882 | UpdateMusicState(); |
1883 | } |
1884 | |
1885 | MainView.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MainView); |
1886 | if(DoButton_CheckBox(pId: &g_Config.m_SndNonactiveMute, pText: Localize(pStr: "Mute when not active" ), Checked: g_Config.m_SndNonactiveMute, pRect: &Button)) |
1887 | g_Config.m_SndNonactiveMute ^= 1; |
1888 | |
1889 | MainView.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MainView); |
1890 | if(DoButton_CheckBox(pId: &g_Config.m_SndGame, pText: Localize(pStr: "Enable game sounds" ), Checked: g_Config.m_SndGame, pRect: &Button)) |
1891 | g_Config.m_SndGame ^= 1; |
1892 | |
1893 | MainView.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MainView); |
1894 | if(DoButton_CheckBox(pId: &g_Config.m_SndGun, pText: Localize(pStr: "Enable gun sound" ), Checked: g_Config.m_SndGun, pRect: &Button)) |
1895 | g_Config.m_SndGun ^= 1; |
1896 | |
1897 | MainView.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MainView); |
1898 | 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)) |
1899 | g_Config.m_SndLongPain ^= 1; |
1900 | |
1901 | MainView.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MainView); |
1902 | if(DoButton_CheckBox(pId: &g_Config.m_SndServerMessage, pText: Localize(pStr: "Enable server message sound" ), Checked: g_Config.m_SndServerMessage, pRect: &Button)) |
1903 | g_Config.m_SndServerMessage ^= 1; |
1904 | |
1905 | MainView.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MainView); |
1906 | if(DoButton_CheckBox(pId: &g_Config.m_SndChat, pText: Localize(pStr: "Enable regular chat sound" ), Checked: g_Config.m_SndChat, pRect: &Button)) |
1907 | g_Config.m_SndChat ^= 1; |
1908 | |
1909 | MainView.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MainView); |
1910 | if(DoButton_CheckBox(pId: &g_Config.m_SndTeamChat, pText: Localize(pStr: "Enable team chat sound" ), Checked: g_Config.m_SndTeamChat, pRect: &Button)) |
1911 | g_Config.m_SndTeamChat ^= 1; |
1912 | |
1913 | MainView.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MainView); |
1914 | if(DoButton_CheckBox(pId: &g_Config.m_SndHighlight, pText: Localize(pStr: "Enable highlighted chat sound" ), Checked: g_Config.m_SndHighlight, pRect: &Button)) |
1915 | g_Config.m_SndHighlight ^= 1; |
1916 | |
1917 | // volume slider |
1918 | { |
1919 | MainView.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &MainView); |
1920 | MainView.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MainView); |
1921 | 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: "%" ); |
1922 | } |
1923 | |
1924 | // volume slider game sounds |
1925 | { |
1926 | MainView.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &MainView); |
1927 | MainView.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MainView); |
1928 | Ui()->DoScrollbarOption(pId: &g_Config.m_SndGameSoundVolume, pOption: &g_Config.m_SndGameSoundVolume, pRect: &Button, pStr: Localize(pStr: "Game sound volume" ), Min: 0, Max: 100, pScale: &CUi::ms_LogarithmicScrollbarScale, Flags: 0u, pSuffix: "%" ); |
1929 | } |
1930 | |
1931 | // volume slider gui sounds |
1932 | { |
1933 | MainView.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &MainView); |
1934 | MainView.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MainView); |
1935 | Ui()->DoScrollbarOption(pId: &g_Config.m_SndChatSoundVolume, pOption: &g_Config.m_SndChatSoundVolume, pRect: &Button, pStr: Localize(pStr: "Chat sound volume" ), Min: 0, Max: 100, pScale: &CUi::ms_LogarithmicScrollbarScale, Flags: 0u, pSuffix: "%" ); |
1936 | } |
1937 | |
1938 | // volume slider map sounds |
1939 | { |
1940 | MainView.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &MainView); |
1941 | MainView.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MainView); |
1942 | Ui()->DoScrollbarOption(pId: &g_Config.m_SndMapSoundVolume, pOption: &g_Config.m_SndMapSoundVolume, pRect: &Button, pStr: Localize(pStr: "Map sound volume" ), Min: 0, Max: 100, pScale: &CUi::ms_LogarithmicScrollbarScale, Flags: 0u, pSuffix: "%" ); |
1943 | } |
1944 | |
1945 | // volume slider background music |
1946 | { |
1947 | MainView.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &MainView); |
1948 | MainView.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MainView); |
1949 | 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: "%" ); |
1950 | } |
1951 | } |
1952 | |
1953 | bool CMenus::(CUIRect MainView) |
1954 | { |
1955 | static int s_SelectedLanguage = -2; // -2 = unloaded, -1 = unset |
1956 | static CListBox s_ListBox; |
1957 | |
1958 | if(s_SelectedLanguage == -2) |
1959 | { |
1960 | s_SelectedLanguage = -1; |
1961 | for(size_t i = 0; i < g_Localization.Languages().size(); i++) |
1962 | { |
1963 | if(str_comp(a: g_Localization.Languages()[i].m_FileName.c_str(), b: g_Config.m_ClLanguagefile) == 0) |
1964 | { |
1965 | s_SelectedLanguage = i; |
1966 | s_ListBox.ScrollToSelected(); |
1967 | break; |
1968 | } |
1969 | } |
1970 | } |
1971 | |
1972 | const int OldSelected = s_SelectedLanguage; |
1973 | |
1974 | s_ListBox.DoStart(RowHeight: 24.0f, NumItems: g_Localization.Languages().size(), ItemsPerRow: 1, RowsPerScroll: 3, SelectedIndex: s_SelectedLanguage, pRect: &MainView); |
1975 | |
1976 | for(const auto &Language : g_Localization.Languages()) |
1977 | { |
1978 | 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())); |
1979 | if(!Item.m_Visible) |
1980 | continue; |
1981 | |
1982 | CUIRect FlagRect, Label; |
1983 | Item.m_Rect.VSplitLeft(Cut: Item.m_Rect.h * 2.0f, pLeft: &FlagRect, pRight: &Label); |
1984 | FlagRect.VMargin(Cut: 6.0f, pOtherRect: &FlagRect); |
1985 | FlagRect.HMargin(Cut: 3.0f, pOtherRect: &FlagRect); |
1986 | m_pClient->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); |
1987 | |
1988 | Ui()->DoLabel(pRect: &Label, pText: Language.m_Name.c_str(), Size: 16.0f, Align: TEXTALIGN_ML); |
1989 | } |
1990 | |
1991 | s_SelectedLanguage = s_ListBox.DoEnd(); |
1992 | |
1993 | if(OldSelected != s_SelectedLanguage) |
1994 | { |
1995 | str_copy(dst&: g_Config.m_ClLanguagefile, src: g_Localization.Languages()[s_SelectedLanguage].m_FileName.c_str()); |
1996 | GameClient()->OnLanguageChange(); |
1997 | } |
1998 | |
1999 | return s_ListBox.WasItemActivated(); |
2000 | } |
2001 | |
2002 | void CMenus::(CUIRect MainView) |
2003 | { |
2004 | // render background |
2005 | CUIRect Button, TabBar, RestartBar; |
2006 | MainView.VSplitRight(Cut: 120.0f, pLeft: &MainView, pRight: &TabBar); |
2007 | MainView.Draw(Color: ms_ColorTabbarActive, Corners: IGraphics::CORNER_B, Rounding: 10.0f); |
2008 | MainView.Margin(Cut: 20.0f, pOtherRect: &MainView); |
2009 | |
2010 | const bool NeedRestart = m_NeedRestartGraphics || m_NeedRestartSound || m_NeedRestartUpdate; |
2011 | if(NeedRestart) |
2012 | { |
2013 | MainView.HSplitBottom(Cut: 20.0f, pTop: &MainView, pBottom: &RestartBar); |
2014 | MainView.HSplitBottom(Cut: 10.0f, pTop: &MainView, pBottom: nullptr); |
2015 | } |
2016 | |
2017 | TabBar.HSplitTop(Cut: 50.0f, pTop: &Button, pBottom: &TabBar); |
2018 | Button.Draw(Color: ms_ColorTabbarActive, Corners: IGraphics::CORNER_BR, Rounding: 10.0f); |
2019 | |
2020 | const char *apTabs[SETTINGS_LENGTH] = { |
2021 | Localize(pStr: "Language" ), |
2022 | Localize(pStr: "General" ), |
2023 | Localize(pStr: "Player" ), |
2024 | Localize(pStr: "Tee" ), |
2025 | Localize(pStr: "Appearance" ), |
2026 | Localize(pStr: "Controls" ), |
2027 | Localize(pStr: "Graphics" ), |
2028 | Localize(pStr: "Sound" ), |
2029 | Localize(pStr: "DDNet" ), |
2030 | Localize(pStr: "Assets" )}; |
2031 | static CButtonContainer s_aTabButtons[SETTINGS_LENGTH]; |
2032 | |
2033 | for(int i = 0; i < SETTINGS_LENGTH; i++) |
2034 | { |
2035 | TabBar.HSplitTop(Cut: 10.0f, pTop: nullptr, pBottom: &TabBar); |
2036 | TabBar.HSplitTop(Cut: 26.0f, pTop: &Button, pBottom: &TabBar); |
2037 | 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])) |
2038 | g_Config.m_UiSettingsPage = i; |
2039 | } |
2040 | |
2041 | if(g_Config.m_UiSettingsPage == SETTINGS_LANGUAGE) |
2042 | { |
2043 | GameClient()->m_MenuBackground.ChangePosition(PositionNumber: CMenuBackground::POS_SETTINGS_LANGUAGE); |
2044 | RenderLanguageSelection(MainView); |
2045 | } |
2046 | else if(g_Config.m_UiSettingsPage == SETTINGS_GENERAL) |
2047 | { |
2048 | GameClient()->m_MenuBackground.ChangePosition(PositionNumber: CMenuBackground::POS_SETTINGS_GENERAL); |
2049 | RenderSettingsGeneral(MainView); |
2050 | } |
2051 | else if(g_Config.m_UiSettingsPage == SETTINGS_PLAYER) |
2052 | { |
2053 | GameClient()->m_MenuBackground.ChangePosition(PositionNumber: CMenuBackground::POS_SETTINGS_PLAYER); |
2054 | RenderSettingsPlayer(MainView); |
2055 | } |
2056 | else if(g_Config.m_UiSettingsPage == SETTINGS_TEE) |
2057 | { |
2058 | GameClient()->m_MenuBackground.ChangePosition(PositionNumber: CMenuBackground::POS_SETTINGS_TEE); |
2059 | RenderSettingsTee(MainView); |
2060 | } |
2061 | else if(g_Config.m_UiSettingsPage == SETTINGS_APPEARANCE) |
2062 | { |
2063 | GameClient()->m_MenuBackground.ChangePosition(PositionNumber: CMenuBackground::POS_SETTINGS_APPEARANCE); |
2064 | RenderSettingsAppearance(MainView); |
2065 | } |
2066 | else if(g_Config.m_UiSettingsPage == SETTINGS_CONTROLS) |
2067 | { |
2068 | GameClient()->m_MenuBackground.ChangePosition(PositionNumber: CMenuBackground::POS_SETTINGS_CONTROLS); |
2069 | RenderSettingsControls(MainView); |
2070 | } |
2071 | else if(g_Config.m_UiSettingsPage == SETTINGS_GRAPHICS) |
2072 | { |
2073 | GameClient()->m_MenuBackground.ChangePosition(PositionNumber: CMenuBackground::POS_SETTINGS_GRAPHICS); |
2074 | RenderSettingsGraphics(MainView); |
2075 | } |
2076 | else if(g_Config.m_UiSettingsPage == SETTINGS_SOUND) |
2077 | { |
2078 | GameClient()->m_MenuBackground.ChangePosition(PositionNumber: CMenuBackground::POS_SETTINGS_SOUND); |
2079 | RenderSettingsSound(MainView); |
2080 | } |
2081 | else if(g_Config.m_UiSettingsPage == SETTINGS_DDNET) |
2082 | { |
2083 | GameClient()->m_MenuBackground.ChangePosition(PositionNumber: CMenuBackground::POS_SETTINGS_DDNET); |
2084 | RenderSettingsDDNet(MainView); |
2085 | } |
2086 | else if(g_Config.m_UiSettingsPage == SETTINGS_ASSETS) |
2087 | { |
2088 | GameClient()->m_MenuBackground.ChangePosition(PositionNumber: CMenuBackground::POS_SETTINGS_ASSETS); |
2089 | RenderSettingsCustom(MainView); |
2090 | } |
2091 | else |
2092 | { |
2093 | dbg_assert(false, "ui_settings_page invalid" ); |
2094 | } |
2095 | |
2096 | if(NeedRestart) |
2097 | { |
2098 | CUIRect RestartWarning, RestartButton; |
2099 | RestartBar.VSplitRight(Cut: 125.0f, pLeft: &RestartWarning, pRight: &RestartButton); |
2100 | RestartWarning.VSplitRight(Cut: 10.0f, pLeft: &RestartWarning, pRight: nullptr); |
2101 | if(m_NeedRestartUpdate) |
2102 | { |
2103 | TextRender()->TextColor(r: 1.0f, g: 0.4f, b: 0.4f, a: 1.0f); |
2104 | Ui()->DoLabel(pRect: &RestartWarning, pText: Localize(pStr: "DDNet Client needs to be restarted to complete update!" ), Size: 14.0f, Align: TEXTALIGN_ML); |
2105 | TextRender()->TextColor(r: 1.0f, g: 1.0f, b: 1.0f, a: 1.0f); |
2106 | } |
2107 | else |
2108 | { |
2109 | Ui()->DoLabel(pRect: &RestartWarning, pText: Localize(pStr: "You must restart the game for all settings to take effect." ), Size: 14.0f, Align: TEXTALIGN_ML); |
2110 | } |
2111 | |
2112 | static CButtonContainer s_RestartButton; |
2113 | if(DoButton_Menu(pButtonContainer: &s_RestartButton, pText: Localize(pStr: "Restart" ), Checked: 0, pRect: &RestartButton)) |
2114 | { |
2115 | if(Client()->State() == IClient::STATE_ONLINE || m_pClient->Editor()->HasUnsavedData()) |
2116 | { |
2117 | m_Popup = POPUP_RESTART; |
2118 | } |
2119 | else |
2120 | { |
2121 | Client()->Restart(); |
2122 | } |
2123 | } |
2124 | } |
2125 | } |
2126 | |
2127 | ColorHSLA CMenus::(CUIRect *pRect, unsigned int *pColor, bool Alpha, bool ClampedLight) |
2128 | { |
2129 | ColorHSLA Color(*pColor, Alpha); |
2130 | CUIRect Preview, Button, Label; |
2131 | char aBuf[32]; |
2132 | float *apComponent[] = {&Color.h, &Color.s, &Color.l, &Color.a}; |
2133 | const char *apLabels[] = {Localize(pStr: "Hue" ), Localize(pStr: "Sat." ), Localize(pStr: "Lht." ), Localize(pStr: "Alpha" )}; |
2134 | |
2135 | float SizePerEntry = 20.0f; |
2136 | float MarginPerEntry = 5.0f; |
2137 | |
2138 | float OffY = (SizePerEntry + MarginPerEntry) * (3 + (Alpha ? 1 : 0)) - 40.0f; |
2139 | pRect->VSplitLeft(Cut: 40.0f, pLeft: &Preview, pRight: pRect); |
2140 | Preview.HSplitTop(Cut: OffY / 2.0f, NULL, pBottom: &Preview); |
2141 | Preview.HSplitTop(Cut: 40.0f, pTop: &Preview, NULL); |
2142 | |
2143 | Graphics()->TextureClear(); |
2144 | { |
2145 | const float SizeBorder = 5.0f; |
2146 | Graphics()->SetColor(ColorRGBA(0.15f, 0.15f, 0.15f, 1)); |
2147 | int TmpCont = Graphics()->CreateRectQuadContainer(x: Preview.x - SizeBorder / 2.0f, y: Preview.y - SizeBorder / 2.0f, w: Preview.w + SizeBorder, h: Preview.h + SizeBorder, r: 4.0f + SizeBorder / 2.0f, Corners: IGraphics::CORNER_ALL); |
2148 | Graphics()->RenderQuadContainer(ContainerIndex: TmpCont, QuadDrawNum: -1); |
2149 | Graphics()->DeleteQuadContainer(ContainerIndex&: TmpCont); |
2150 | } |
2151 | ColorHSLA RenderColorHSLA(Color.r, Color.g, Color.b, Color.a); |
2152 | if(ClampedLight) |
2153 | RenderColorHSLA = RenderColorHSLA.UnclampLighting(); |
2154 | Graphics()->SetColor(color_cast<ColorRGBA>(hsl: RenderColorHSLA)); |
2155 | int TmpCont = Graphics()->CreateRectQuadContainer(x: Preview.x, y: Preview.y, w: Preview.w, h: Preview.h, r: 4.0f, Corners: IGraphics::CORNER_ALL); |
2156 | Graphics()->RenderQuadContainer(ContainerIndex: TmpCont, QuadDrawNum: -1); |
2157 | Graphics()->DeleteQuadContainer(ContainerIndex&: TmpCont); |
2158 | |
2159 | auto &&RenderHSLColorsRect = [&](CUIRect *pColorRect) { |
2160 | Graphics()->TextureClear(); |
2161 | Graphics()->TrianglesBegin(); |
2162 | |
2163 | float CurXOff = pColorRect->x; |
2164 | float SizeColor = pColorRect->w / 6; |
2165 | |
2166 | // red to yellow |
2167 | { |
2168 | IGraphics::CColorVertex Array[4] = { |
2169 | IGraphics::CColorVertex(0, 1, 0, 0, 1), |
2170 | IGraphics::CColorVertex(1, 1, 1, 0, 1), |
2171 | IGraphics::CColorVertex(2, 1, 0, 0, 1), |
2172 | IGraphics::CColorVertex(3, 1, 1, 0, 1)}; |
2173 | Graphics()->SetColorVertex(pArray: Array, Num: 4); |
2174 | |
2175 | IGraphics::CFreeformItem Freeform( |
2176 | CurXOff, pColorRect->y, |
2177 | CurXOff + SizeColor, pColorRect->y, |
2178 | CurXOff, pColorRect->y + pColorRect->h, |
2179 | CurXOff + SizeColor, pColorRect->y + pColorRect->h); |
2180 | Graphics()->QuadsDrawFreeform(pArray: &Freeform, Num: 1); |
2181 | } |
2182 | |
2183 | // yellow to green |
2184 | CurXOff += SizeColor; |
2185 | { |
2186 | IGraphics::CColorVertex Array[4] = { |
2187 | IGraphics::CColorVertex(0, 1, 1, 0, 1), |
2188 | IGraphics::CColorVertex(1, 0, 1, 0, 1), |
2189 | IGraphics::CColorVertex(2, 1, 1, 0, 1), |
2190 | IGraphics::CColorVertex(3, 0, 1, 0, 1)}; |
2191 | Graphics()->SetColorVertex(pArray: Array, Num: 4); |
2192 | |
2193 | IGraphics::CFreeformItem Freeform( |
2194 | CurXOff, pColorRect->y, |
2195 | CurXOff + SizeColor, pColorRect->y, |
2196 | CurXOff, pColorRect->y + pColorRect->h, |
2197 | CurXOff + SizeColor, pColorRect->y + pColorRect->h); |
2198 | Graphics()->QuadsDrawFreeform(pArray: &Freeform, Num: 1); |
2199 | } |
2200 | |
2201 | CurXOff += SizeColor; |
2202 | // green to turquoise |
2203 | { |
2204 | IGraphics::CColorVertex Array[4] = { |
2205 | IGraphics::CColorVertex(0, 0, 1, 0, 1), |
2206 | IGraphics::CColorVertex(1, 0, 1, 1, 1), |
2207 | IGraphics::CColorVertex(2, 0, 1, 0, 1), |
2208 | IGraphics::CColorVertex(3, 0, 1, 1, 1)}; |
2209 | Graphics()->SetColorVertex(pArray: Array, Num: 4); |
2210 | |
2211 | IGraphics::CFreeformItem Freeform( |
2212 | CurXOff, pColorRect->y, |
2213 | CurXOff + SizeColor, pColorRect->y, |
2214 | CurXOff, pColorRect->y + pColorRect->h, |
2215 | CurXOff + SizeColor, pColorRect->y + pColorRect->h); |
2216 | Graphics()->QuadsDrawFreeform(pArray: &Freeform, Num: 1); |
2217 | } |
2218 | |
2219 | CurXOff += SizeColor; |
2220 | // turquoise to blue |
2221 | { |
2222 | IGraphics::CColorVertex Array[4] = { |
2223 | IGraphics::CColorVertex(0, 0, 1, 1, 1), |
2224 | IGraphics::CColorVertex(1, 0, 0, 1, 1), |
2225 | IGraphics::CColorVertex(2, 0, 1, 1, 1), |
2226 | IGraphics::CColorVertex(3, 0, 0, 1, 1)}; |
2227 | Graphics()->SetColorVertex(pArray: Array, Num: 4); |
2228 | |
2229 | IGraphics::CFreeformItem Freeform( |
2230 | CurXOff, pColorRect->y, |
2231 | CurXOff + SizeColor, pColorRect->y, |
2232 | CurXOff, pColorRect->y + pColorRect->h, |
2233 | CurXOff + SizeColor, pColorRect->y + pColorRect->h); |
2234 | Graphics()->QuadsDrawFreeform(pArray: &Freeform, Num: 1); |
2235 | } |
2236 | |
2237 | CurXOff += SizeColor; |
2238 | // blue to purple |
2239 | { |
2240 | IGraphics::CColorVertex Array[4] = { |
2241 | IGraphics::CColorVertex(0, 0, 0, 1, 1), |
2242 | IGraphics::CColorVertex(1, 1, 0, 1, 1), |
2243 | IGraphics::CColorVertex(2, 0, 0, 1, 1), |
2244 | IGraphics::CColorVertex(3, 1, 0, 1, 1)}; |
2245 | Graphics()->SetColorVertex(pArray: Array, Num: 4); |
2246 | |
2247 | IGraphics::CFreeformItem Freeform( |
2248 | CurXOff, pColorRect->y, |
2249 | CurXOff + SizeColor, pColorRect->y, |
2250 | CurXOff, pColorRect->y + pColorRect->h, |
2251 | CurXOff + SizeColor, pColorRect->y + pColorRect->h); |
2252 | Graphics()->QuadsDrawFreeform(pArray: &Freeform, Num: 1); |
2253 | } |
2254 | |
2255 | CurXOff += SizeColor; |
2256 | // purple to red |
2257 | { |
2258 | IGraphics::CColorVertex Array[4] = { |
2259 | IGraphics::CColorVertex(0, 1, 0, 1, 1), |
2260 | IGraphics::CColorVertex(1, 1, 0, 0, 1), |
2261 | IGraphics::CColorVertex(2, 1, 0, 1, 1), |
2262 | IGraphics::CColorVertex(3, 1, 0, 0, 1)}; |
2263 | Graphics()->SetColorVertex(pArray: Array, Num: 4); |
2264 | |
2265 | IGraphics::CFreeformItem Freeform( |
2266 | CurXOff, pColorRect->y, |
2267 | CurXOff + SizeColor, pColorRect->y, |
2268 | CurXOff, pColorRect->y + pColorRect->h, |
2269 | CurXOff + SizeColor, pColorRect->y + pColorRect->h); |
2270 | Graphics()->QuadsDrawFreeform(pArray: &Freeform, Num: 1); |
2271 | } |
2272 | |
2273 | Graphics()->TrianglesEnd(); |
2274 | }; |
2275 | |
2276 | auto &&RenderHSLSatRect = [&](CUIRect *pColorRect, ColorRGBA &CurColor) { |
2277 | Graphics()->TextureClear(); |
2278 | Graphics()->TrianglesBegin(); |
2279 | |
2280 | float CurXOff = pColorRect->x; |
2281 | float SizeColor = pColorRect->w; |
2282 | |
2283 | ColorHSLA RightColor = color_cast<ColorHSLA>(rgb: CurColor); |
2284 | ColorHSLA LeftColor = color_cast<ColorHSLA>(rgb: CurColor); |
2285 | |
2286 | LeftColor.g = 0; |
2287 | RightColor.g = 1; |
2288 | |
2289 | ColorRGBA RightColorRGBA = color_cast<ColorRGBA>(hsl: RightColor); |
2290 | ColorRGBA LeftColorRGBA = color_cast<ColorRGBA>(hsl: LeftColor); |
2291 | |
2292 | // saturation |
2293 | { |
2294 | IGraphics::CColorVertex Array[4] = { |
2295 | IGraphics::CColorVertex(0, LeftColorRGBA.r, LeftColorRGBA.g, LeftColorRGBA.b, 1), |
2296 | IGraphics::CColorVertex(1, RightColorRGBA.r, RightColorRGBA.g, RightColorRGBA.b, 1), |
2297 | IGraphics::CColorVertex(2, LeftColorRGBA.r, LeftColorRGBA.g, LeftColorRGBA.b, 1), |
2298 | IGraphics::CColorVertex(3, RightColorRGBA.r, RightColorRGBA.g, RightColorRGBA.b, 1)}; |
2299 | Graphics()->SetColorVertex(pArray: Array, Num: 4); |
2300 | |
2301 | IGraphics::CFreeformItem Freeform( |
2302 | CurXOff, pColorRect->y, |
2303 | CurXOff + SizeColor, pColorRect->y, |
2304 | CurXOff, pColorRect->y + pColorRect->h, |
2305 | CurXOff + SizeColor, pColorRect->y + pColorRect->h); |
2306 | Graphics()->QuadsDrawFreeform(pArray: &Freeform, Num: 1); |
2307 | } |
2308 | |
2309 | Graphics()->TrianglesEnd(); |
2310 | }; |
2311 | |
2312 | auto &&RenderHSLLightRect = [&](CUIRect *pColorRect, ColorRGBA &CurColorSat) { |
2313 | Graphics()->TextureClear(); |
2314 | Graphics()->TrianglesBegin(); |
2315 | |
2316 | float CurXOff = pColorRect->x; |
2317 | float SizeColor = pColorRect->w / (ClampedLight ? 1.0f : 2.0f); |
2318 | |
2319 | ColorHSLA RightColor = color_cast<ColorHSLA>(rgb: CurColorSat); |
2320 | ColorHSLA LeftColor = color_cast<ColorHSLA>(rgb: CurColorSat); |
2321 | |
2322 | LeftColor.b = ColorHSLA::DARKEST_LGT; |
2323 | RightColor.b = 1; |
2324 | |
2325 | ColorRGBA RightColorRGBA = color_cast<ColorRGBA>(hsl: RightColor); |
2326 | ColorRGBA LeftColorRGBA = color_cast<ColorRGBA>(hsl: LeftColor); |
2327 | |
2328 | if(!ClampedLight) |
2329 | CurXOff += SizeColor; |
2330 | |
2331 | // light |
2332 | { |
2333 | IGraphics::CColorVertex Array[4] = { |
2334 | IGraphics::CColorVertex(0, LeftColorRGBA.r, LeftColorRGBA.g, LeftColorRGBA.b, 1), |
2335 | IGraphics::CColorVertex(1, RightColorRGBA.r, RightColorRGBA.g, RightColorRGBA.b, 1), |
2336 | IGraphics::CColorVertex(2, LeftColorRGBA.r, LeftColorRGBA.g, LeftColorRGBA.b, 1), |
2337 | IGraphics::CColorVertex(3, RightColorRGBA.r, RightColorRGBA.g, RightColorRGBA.b, 1)}; |
2338 | Graphics()->SetColorVertex(pArray: Array, Num: 4); |
2339 | |
2340 | IGraphics::CFreeformItem Freeform( |
2341 | CurXOff, pColorRect->y, |
2342 | CurXOff + SizeColor, pColorRect->y, |
2343 | CurXOff, pColorRect->y + pColorRect->h, |
2344 | CurXOff + SizeColor, pColorRect->y + pColorRect->h); |
2345 | Graphics()->QuadsDrawFreeform(pArray: &Freeform, Num: 1); |
2346 | } |
2347 | |
2348 | if(!ClampedLight) |
2349 | { |
2350 | CurXOff -= SizeColor; |
2351 | LeftColor.b = 0; |
2352 | RightColor.b = ColorHSLA::DARKEST_LGT; |
2353 | |
2354 | RightColorRGBA = color_cast<ColorRGBA>(hsl: RightColor); |
2355 | LeftColorRGBA = color_cast<ColorRGBA>(hsl: LeftColor); |
2356 | |
2357 | IGraphics::CColorVertex Array[4] = { |
2358 | IGraphics::CColorVertex(0, LeftColorRGBA.r, LeftColorRGBA.g, LeftColorRGBA.b, 1), |
2359 | IGraphics::CColorVertex(1, RightColorRGBA.r, RightColorRGBA.g, RightColorRGBA.b, 1), |
2360 | IGraphics::CColorVertex(2, LeftColorRGBA.r, LeftColorRGBA.g, LeftColorRGBA.b, 1), |
2361 | IGraphics::CColorVertex(3, RightColorRGBA.r, RightColorRGBA.g, RightColorRGBA.b, 1)}; |
2362 | Graphics()->SetColorVertex(pArray: Array, Num: 4); |
2363 | |
2364 | IGraphics::CFreeformItem Freeform( |
2365 | CurXOff, pColorRect->y, |
2366 | CurXOff + SizeColor, pColorRect->y, |
2367 | CurXOff, pColorRect->y + pColorRect->h, |
2368 | CurXOff + SizeColor, pColorRect->y + pColorRect->h); |
2369 | Graphics()->QuadsDrawFreeform(pArray: &Freeform, Num: 1); |
2370 | } |
2371 | |
2372 | Graphics()->TrianglesEnd(); |
2373 | }; |
2374 | |
2375 | auto &&RenderHSLAlphaRect = [&](CUIRect *pColorRect, ColorRGBA &CurColorFull) { |
2376 | Graphics()->TextureClear(); |
2377 | Graphics()->TrianglesBegin(); |
2378 | |
2379 | float CurXOff = pColorRect->x; |
2380 | float SizeColor = pColorRect->w; |
2381 | |
2382 | ColorHSLA RightColor = color_cast<ColorHSLA>(rgb: CurColorFull); |
2383 | ColorHSLA LeftColor = color_cast<ColorHSLA>(rgb: CurColorFull); |
2384 | |
2385 | LeftColor.a = 0; |
2386 | RightColor.a = 1; |
2387 | |
2388 | ColorRGBA RightColorRGBA = color_cast<ColorRGBA>(hsl: RightColor); |
2389 | ColorRGBA LeftColorRGBA = color_cast<ColorRGBA>(hsl: LeftColor); |
2390 | |
2391 | // alpha |
2392 | { |
2393 | IGraphics::CColorVertex Array[4] = { |
2394 | IGraphics::CColorVertex(0, LeftColorRGBA.r, LeftColorRGBA.g, LeftColorRGBA.b, LeftColorRGBA.a), |
2395 | IGraphics::CColorVertex(1, RightColorRGBA.r, RightColorRGBA.g, RightColorRGBA.b, RightColorRGBA.a), |
2396 | IGraphics::CColorVertex(2, LeftColorRGBA.r, LeftColorRGBA.g, LeftColorRGBA.b, LeftColorRGBA.a), |
2397 | IGraphics::CColorVertex(3, RightColorRGBA.r, RightColorRGBA.g, RightColorRGBA.b, RightColorRGBA.a)}; |
2398 | Graphics()->SetColorVertex(pArray: Array, Num: 4); |
2399 | |
2400 | IGraphics::CFreeformItem Freeform( |
2401 | CurXOff, pColorRect->y, |
2402 | CurXOff + SizeColor, pColorRect->y, |
2403 | CurXOff, pColorRect->y + pColorRect->h, |
2404 | CurXOff + SizeColor, pColorRect->y + pColorRect->h); |
2405 | Graphics()->QuadsDrawFreeform(pArray: &Freeform, Num: 1); |
2406 | } |
2407 | |
2408 | Graphics()->TrianglesEnd(); |
2409 | }; |
2410 | |
2411 | for(int i = 0; i < 3 + Alpha; i++) |
2412 | { |
2413 | pRect->HSplitTop(Cut: SizePerEntry, pTop: &Button, pBottom: pRect); |
2414 | pRect->HSplitTop(Cut: MarginPerEntry, NULL, pBottom: pRect); |
2415 | Button.VSplitLeft(Cut: 10.0f, pLeft: 0, pRight: &Button); |
2416 | Button.VSplitLeft(Cut: 100.0f, pLeft: &Label, pRight: &Button); |
2417 | |
2418 | Button.Draw(Color: ColorRGBA(0.15f, 0.15f, 0.15f, 1.0f), Corners: IGraphics::CORNER_ALL, Rounding: 1.0f); |
2419 | |
2420 | CUIRect Rail; |
2421 | Button.Margin(Cut: 2.0f, pOtherRect: &Rail); |
2422 | |
2423 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s: %03d" , apLabels[i], (int)(*apComponent[i] * 255)); |
2424 | Ui()->DoLabel(pRect: &Label, pText: aBuf, Size: 14.0f, Align: TEXTALIGN_ML); |
2425 | |
2426 | ColorHSLA CurColorPureHSLA(RenderColorHSLA.r, 1, 0.5f, 1); |
2427 | ColorRGBA CurColorPure = color_cast<ColorRGBA>(hsl: CurColorPureHSLA); |
2428 | ColorRGBA ColorInner(1, 1, 1, 0.25f); |
2429 | |
2430 | if(i == 0) |
2431 | { |
2432 | ColorInner = CurColorPure; |
2433 | RenderHSLColorsRect(&Rail); |
2434 | } |
2435 | else if(i == 1) |
2436 | { |
2437 | RenderHSLSatRect(&Rail, CurColorPure); |
2438 | ColorInner = color_cast<ColorRGBA>(hsl: ColorHSLA(CurColorPureHSLA.r, *apComponent[1], CurColorPureHSLA.b, 1)); |
2439 | } |
2440 | else if(i == 2) |
2441 | { |
2442 | ColorRGBA CurColorSat = color_cast<ColorRGBA>(hsl: ColorHSLA(CurColorPureHSLA.r, *apComponent[1], 0.5f, 1)); |
2443 | RenderHSLLightRect(&Rail, CurColorSat); |
2444 | float LightVal = *apComponent[2]; |
2445 | if(ClampedLight) |
2446 | LightVal = ColorHSLA::DARKEST_LGT + LightVal * (1.0f - ColorHSLA::DARKEST_LGT); |
2447 | ColorInner = color_cast<ColorRGBA>(hsl: ColorHSLA(CurColorPureHSLA.r, *apComponent[1], LightVal, 1)); |
2448 | } |
2449 | else if(i == 3) |
2450 | { |
2451 | ColorRGBA CurColorFull = color_cast<ColorRGBA>(hsl: ColorHSLA(CurColorPureHSLA.r, *apComponent[1], *apComponent[2], 1)); |
2452 | RenderHSLAlphaRect(&Rail, CurColorFull); |
2453 | float LightVal = *apComponent[2]; |
2454 | if(ClampedLight) |
2455 | LightVal = ColorHSLA::DARKEST_LGT + LightVal * (1.0f - ColorHSLA::DARKEST_LGT); |
2456 | ColorInner = color_cast<ColorRGBA>(hsl: ColorHSLA(CurColorPureHSLA.r, *apComponent[1], LightVal, *apComponent[3])); |
2457 | } |
2458 | |
2459 | *apComponent[i] = Ui()->DoScrollbarH(pId: &((char *)pColor)[i], pRect: &Button, Current: *apComponent[i], pColorInner: &ColorInner); |
2460 | } |
2461 | |
2462 | *pColor = Color.Pack(Alpha); |
2463 | return Color; |
2464 | } |
2465 | |
2466 | enum |
2467 | { |
2468 | APPEARANCE_TAB_HUD = 0, |
2469 | APPEARANCE_TAB_CHAT = 1, |
2470 | APPEARANCE_TAB_NAME_PLATE = 2, |
2471 | APPEARANCE_TAB_HOOK_COLLISION = 3, |
2472 | APPEARANCE_TAB_INFO_MESSAGES = 4, |
2473 | APPEARANCE_TAB_LASER = 5, |
2474 | NUMBER_OF_APPEARANCE_TABS = 6, |
2475 | }; |
2476 | |
2477 | void CMenus::(CUIRect MainView) |
2478 | { |
2479 | char aBuf[128]; |
2480 | static int s_CurTab = 0; |
2481 | |
2482 | CUIRect TabBar, LeftView, RightView, Button, Label; |
2483 | |
2484 | MainView.HSplitTop(Cut: 20.0f, pTop: &TabBar, pBottom: &MainView); |
2485 | const float TabWidth = TabBar.w / NUMBER_OF_APPEARANCE_TABS; |
2486 | static CButtonContainer s_aPageTabs[NUMBER_OF_APPEARANCE_TABS] = {}; |
2487 | const char *apTabNames[NUMBER_OF_APPEARANCE_TABS] = { |
2488 | Localize(pStr: "HUD" ), |
2489 | Localize(pStr: "Chat" ), |
2490 | Localize(pStr: "Name Plate" ), |
2491 | Localize(pStr: "Hook Collisions" ), |
2492 | Localize(pStr: "Info Messages" ), |
2493 | Localize(pStr: "Laser" )}; |
2494 | |
2495 | for(int Tab = APPEARANCE_TAB_HUD; Tab < NUMBER_OF_APPEARANCE_TABS; ++Tab) |
2496 | { |
2497 | TabBar.VSplitLeft(Cut: TabWidth, pLeft: &Button, pRight: &TabBar); |
2498 | const int Corners = Tab == APPEARANCE_TAB_HUD ? IGraphics::CORNER_L : Tab == NUMBER_OF_APPEARANCE_TABS - 1 ? IGraphics::CORNER_R : IGraphics::CORNER_NONE; |
2499 | 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)) |
2500 | { |
2501 | s_CurTab = Tab; |
2502 | } |
2503 | } |
2504 | |
2505 | MainView.HSplitTop(Cut: 10.0f, pTop: nullptr, pBottom: &MainView); |
2506 | |
2507 | const float LineSize = 20.0f; |
2508 | const float ColorPickerLineSize = 25.0f; |
2509 | const float HeadlineFontSize = 20.0f; |
2510 | const float HeadlineHeight = 30.0f; |
2511 | const float MarginSmall = 5.0f; |
2512 | const float MarginBetweenViews = 20.0f; |
2513 | |
2514 | const float ColorPickerLabelSize = 13.0f; |
2515 | const float ColorPickerLineSpacing = 5.0f; |
2516 | |
2517 | if(s_CurTab == APPEARANCE_TAB_HUD) |
2518 | { |
2519 | MainView.VSplitMid(pLeft: &LeftView, pRight: &RightView, Spacing: MarginBetweenViews); |
2520 | |
2521 | // ***** HUD ***** // |
2522 | LeftView.HSplitTop(Cut: HeadlineHeight, pTop: &Label, pBottom: &LeftView); |
2523 | Ui()->DoLabel(pRect: &Label, pText: Localize(pStr: "HUD" ), Size: HeadlineFontSize, Align: TEXTALIGN_ML); |
2524 | LeftView.HSplitTop(Cut: MarginSmall, pTop: nullptr, pBottom: &LeftView); |
2525 | |
2526 | // Switch of the entire HUD |
2527 | DoButton_CheckBoxAutoVMarginAndSet(pId: &g_Config.m_ClShowhud, pText: Localize(pStr: "Show ingame HUD" ), pValue: &g_Config.m_ClShowhud, pRect: &LeftView, VMargin: LineSize); |
2528 | |
2529 | // Switches of the various normal HUD elements |
2530 | DoButton_CheckBoxAutoVMarginAndSet(pId: &g_Config.m_ClShowhudHealthAmmo, pText: Localize(pStr: "Show health, shields and ammo" ), pValue: &g_Config.m_ClShowhudHealthAmmo, pRect: &LeftView, VMargin: LineSize); |
2531 | DoButton_CheckBoxAutoVMarginAndSet(pId: &g_Config.m_ClShowhudScore, pText: Localize(pStr: "Show score" ), pValue: &g_Config.m_ClShowhudScore, pRect: &LeftView, VMargin: LineSize); |
2532 | DoButton_CheckBoxAutoVMarginAndSet(pId: &g_Config.m_ClShowLocalTimeAlways, pText: Localize(pStr: "Show local time always" ), pValue: &g_Config.m_ClShowLocalTimeAlways, pRect: &LeftView, VMargin: LineSize); |
2533 | |
2534 | // Settings of the HUD element for votes |
2535 | DoButton_CheckBoxAutoVMarginAndSet(pId: &g_Config.m_ClShowVotesAfterVoting, pText: Localize(pStr: "Show votes window after voting" ), pValue: &g_Config.m_ClShowVotesAfterVoting, pRect: &LeftView, VMargin: LineSize); |
2536 | |
2537 | // ***** DDRace HUD ***** // |
2538 | RightView.HSplitTop(Cut: HeadlineHeight, pTop: &Label, pBottom: &RightView); |
2539 | Ui()->DoLabel(pRect: &Label, pText: Localize(pStr: "DDRace HUD" ), Size: HeadlineFontSize, Align: TEXTALIGN_ML); |
2540 | RightView.HSplitTop(Cut: MarginSmall, pTop: nullptr, pBottom: &RightView); |
2541 | |
2542 | // Switches of various DDRace HUD elements |
2543 | DoButton_CheckBoxAutoVMarginAndSet(pId: &g_Config.m_ClShowIds, pText: Localize(pStr: "Show client IDs in scoreboard" ), pValue: &g_Config.m_ClShowIds, pRect: &RightView, VMargin: LineSize); |
2544 | DoButton_CheckBoxAutoVMarginAndSet(pId: &g_Config.m_ClShowhudDDRace, pText: Localize(pStr: "Show DDRace HUD" ), pValue: &g_Config.m_ClShowhudDDRace, pRect: &RightView, VMargin: LineSize); |
2545 | if(g_Config.m_ClShowhudDDRace) |
2546 | { |
2547 | DoButton_CheckBoxAutoVMarginAndSet(pId: &g_Config.m_ClShowhudJumpsIndicator, pText: Localize(pStr: "Show jumps indicator" ), pValue: &g_Config.m_ClShowhudJumpsIndicator, pRect: &RightView, VMargin: LineSize); |
2548 | } |
2549 | else |
2550 | { |
2551 | RightView.HSplitTop(Cut: LineSize, pTop: nullptr, pBottom: &RightView); // Create empty space for hidden option |
2552 | } |
2553 | |
2554 | // Switch for dummy actions display |
2555 | DoButton_CheckBoxAutoVMarginAndSet(pId: &g_Config.m_ClShowhudDummyActions, pText: Localize(pStr: "Show dummy actions" ), pValue: &g_Config.m_ClShowhudDummyActions, pRect: &RightView, VMargin: LineSize); |
2556 | |
2557 | // Player movement information display settings |
2558 | DoButton_CheckBoxAutoVMarginAndSet(pId: &g_Config.m_ClShowhudPlayerPosition, pText: Localize(pStr: "Show player position" ), pValue: &g_Config.m_ClShowhudPlayerPosition, pRect: &RightView, VMargin: LineSize); |
2559 | DoButton_CheckBoxAutoVMarginAndSet(pId: &g_Config.m_ClShowhudPlayerSpeed, pText: Localize(pStr: "Show player speed" ), pValue: &g_Config.m_ClShowhudPlayerSpeed, pRect: &RightView, VMargin: LineSize); |
2560 | DoButton_CheckBoxAutoVMarginAndSet(pId: &g_Config.m_ClShowhudPlayerAngle, pText: Localize(pStr: "Show player target angle" ), pValue: &g_Config.m_ClShowhudPlayerAngle, pRect: &RightView, VMargin: LineSize); |
2561 | |
2562 | // Freeze bar settings |
2563 | DoButton_CheckBoxAutoVMarginAndSet(pId: &g_Config.m_ClShowFreezeBars, pText: Localize(pStr: "Show freeze bars" ), pValue: &g_Config.m_ClShowFreezeBars, pRect: &RightView, VMargin: LineSize); |
2564 | RightView.HSplitTop(Cut: 2 * LineSize, pTop: &Button, pBottom: &RightView); |
2565 | if(g_Config.m_ClShowFreezeBars) |
2566 | { |
2567 | 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: "%" ); |
2568 | } |
2569 | } |
2570 | else if(s_CurTab == APPEARANCE_TAB_CHAT) |
2571 | { |
2572 | CChat &Chat = GameClient()->m_Chat; |
2573 | CUIRect TopView, PreviewView; |
2574 | MainView.HSplitBottom(Cut: 220.0f, pTop: &TopView, pBottom: &PreviewView); |
2575 | TopView.HSplitBottom(Cut: MarginBetweenViews, pTop: &TopView, pBottom: nullptr); |
2576 | TopView.VSplitMid(pLeft: &LeftView, pRight: &RightView, Spacing: MarginBetweenViews); |
2577 | |
2578 | // ***** Chat ***** // |
2579 | LeftView.HSplitTop(Cut: HeadlineHeight, pTop: &Label, pBottom: &LeftView); |
2580 | Ui()->DoLabel(pRect: &Label, pText: Localize(pStr: "Chat" ), Size: HeadlineFontSize, Align: TEXTALIGN_ML); |
2581 | LeftView.HSplitTop(Cut: MarginSmall, pTop: nullptr, pBottom: &LeftView); |
2582 | |
2583 | // General chat settings |
2584 | LeftView.HSplitTop(Cut: LineSize, pTop: &Button, pBottom: &LeftView); |
2585 | if(DoButton_CheckBox(pId: &g_Config.m_ClShowChat, pText: Localize(pStr: "Show chat" ), Checked: g_Config.m_ClShowChat, pRect: &Button)) |
2586 | { |
2587 | g_Config.m_ClShowChat = g_Config.m_ClShowChat ? 0 : 1; |
2588 | } |
2589 | LeftView.HSplitTop(Cut: LineSize, pTop: &Button, pBottom: &LeftView); |
2590 | if(g_Config.m_ClShowChat) |
2591 | { |
2592 | static int s_ShowChat = 0; |
2593 | if(DoButton_CheckBox(pId: &s_ShowChat, pText: Localize(pStr: "Always show chat" ), Checked: g_Config.m_ClShowChat == 2, pRect: &Button)) |
2594 | g_Config.m_ClShowChat = g_Config.m_ClShowChat != 2 ? 2 : 1; |
2595 | } |
2596 | |
2597 | 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); |
2598 | 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); |
2599 | 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); |
2600 | |
2601 | if(DoButton_CheckBoxAutoVMarginAndSet(pId: &g_Config.m_ClChatOld, pText: Localize(pStr: "Use old chat style" ), pValue: &g_Config.m_ClChatOld, pRect: &LeftView, VMargin: LineSize)) |
2602 | GameClient()->m_Chat.RebuildChat(); |
2603 | |
2604 | LeftView.HSplitTop(Cut: 2 * LineSize, pTop: &Button, pBottom: &LeftView); |
2605 | 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, pScale: &CUi::ms_LinearScrollbarScale, Flags: CUi::SCROLLBAR_OPTION_MULTILINE)) |
2606 | { |
2607 | Chat.EnsureCoherentWidth(); |
2608 | Chat.RebuildChat(); |
2609 | } |
2610 | |
2611 | LeftView.HSplitTop(Cut: 2 * LineSize, pTop: &Button, pBottom: &LeftView); |
2612 | if(Ui()->DoScrollbarOption(pId: &g_Config.m_ClChatWidth, pOption: &g_Config.m_ClChatWidth, pRect: &Button, pStr: Localize(pStr: "Chat width" ), Min: 120, Max: 400, pScale: &CUi::ms_LinearScrollbarScale, Flags: CUi::SCROLLBAR_OPTION_MULTILINE)) |
2613 | { |
2614 | Chat.EnsureCoherentFontSize(); |
2615 | Chat.RebuildChat(); |
2616 | } |
2617 | |
2618 | // ***** Messages ***** // |
2619 | RightView.HSplitTop(Cut: HeadlineHeight, pTop: &Label, pBottom: &RightView); |
2620 | Ui()->DoLabel(pRect: &Label, pText: Localize(pStr: "Messages" ), Size: HeadlineFontSize, Align: TEXTALIGN_ML); |
2621 | RightView.HSplitTop(Cut: MarginSmall, pTop: nullptr, pBottom: &RightView); |
2622 | |
2623 | // Message Colors and extra settings |
2624 | static CButtonContainer s_SystemMessageColor; |
2625 | 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); |
2626 | static CButtonContainer s_HighlightedMessageColor; |
2627 | 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)); |
2628 | static CButtonContainer s_TeamMessageColor; |
2629 | 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)); |
2630 | static CButtonContainer s_FriendMessageColor; |
2631 | 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); |
2632 | static CButtonContainer s_NormalMessageColor; |
2633 | 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)); |
2634 | |
2635 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s (echo)" , Localize(pStr: "Client message" )); |
2636 | static CButtonContainer s_ClientMessageColor; |
2637 | 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)); |
2638 | |
2639 | // ***** Chat Preview ***** // |
2640 | PreviewView.HSplitTop(Cut: HeadlineHeight, pTop: &Label, pBottom: &PreviewView); |
2641 | Ui()->DoLabel(pRect: &Label, pText: Localize(pStr: "Preview" ), Size: HeadlineFontSize, Align: TEXTALIGN_ML); |
2642 | PreviewView.HSplitTop(Cut: MarginSmall, pTop: nullptr, pBottom: &PreviewView); |
2643 | |
2644 | // Use the rest of the view for preview |
2645 | PreviewView.Draw(Color: ColorRGBA(1, 1, 1, 0.1f), Corners: IGraphics::CORNER_ALL, Rounding: 5.0f); |
2646 | PreviewView.Margin(Cut: MarginSmall, pOtherRect: &PreviewView); |
2647 | |
2648 | ColorRGBA SystemColor = color_cast<ColorRGBA, ColorHSLA>(hsl: ColorHSLA(g_Config.m_ClMessageSystemColor)); |
2649 | ColorRGBA HighlightedColor = color_cast<ColorRGBA, ColorHSLA>(hsl: ColorHSLA(g_Config.m_ClMessageHighlightColor)); |
2650 | ColorRGBA TeamColor = color_cast<ColorRGBA, ColorHSLA>(hsl: ColorHSLA(g_Config.m_ClMessageTeamColor)); |
2651 | ColorRGBA FriendColor = color_cast<ColorRGBA, ColorHSLA>(hsl: ColorHSLA(g_Config.m_ClMessageFriendColor)); |
2652 | ColorRGBA NormalColor = color_cast<ColorRGBA, ColorHSLA>(hsl: ColorHSLA(g_Config.m_ClMessageColor)); |
2653 | ColorRGBA ClientColor = color_cast<ColorRGBA, ColorHSLA>(hsl: ColorHSLA(g_Config.m_ClMessageClientColor)); |
2654 | ColorRGBA DefaultNameColor(0.8f, 0.8f, 0.8f, 1.0f); |
2655 | |
2656 | const float RealFontSize = Chat.FontSize() * 2; |
2657 | const float RealMsgPaddingX = (!g_Config.m_ClChatOld ? Chat.MessagePaddingX() : 0) * 2; |
2658 | const float RealMsgPaddingY = (!g_Config.m_ClChatOld ? Chat.MessagePaddingY() : 0) * 2; |
2659 | const float RealMsgPaddingTee = (!g_Config.m_ClChatOld ? Chat.MessageTeeSize() + CChat::MESSAGE_TEE_PADDING_RIGHT : 0) * 2; |
2660 | const float RealOffsetY = RealFontSize + RealMsgPaddingY; |
2661 | |
2662 | const float X = RealMsgPaddingX / 2.0f + PreviewView.x; |
2663 | float Y = PreviewView.y; |
2664 | float LineWidth = g_Config.m_ClChatWidth * 2 - (RealMsgPaddingX * 1.5f) - RealMsgPaddingTee; |
2665 | |
2666 | str_copy(dst&: aBuf, src: Client()->PlayerName()); |
2667 | |
2668 | const CAnimState *pIdleState = CAnimState::GetIdle(); |
2669 | const float RealTeeSize = Chat.MessageTeeSize() * 2; |
2670 | const float RealTeeSizeHalved = Chat.MessageTeeSize(); |
2671 | constexpr float TWSkinUnreliableOffset = -0.25f; |
2672 | const float OffsetTeeY = RealTeeSizeHalved; |
2673 | const float FullHeightMinusTee = RealOffsetY - RealTeeSize; |
2674 | |
2675 | struct SPreviewLine |
2676 | { |
2677 | int m_ClientId; |
2678 | bool m_Team; |
2679 | char m_aName[64]; |
2680 | char m_aText[256]; |
2681 | bool m_Friend; |
2682 | bool m_Player; |
2683 | bool m_Client; |
2684 | bool m_Highlighted; |
2685 | int m_TimesRepeated; |
2686 | |
2687 | CTeeRenderInfo m_RenderInfo; |
2688 | }; |
2689 | |
2690 | static std::vector<SPreviewLine> s_vLines; |
2691 | |
2692 | const auto *pDefaultSkin = GameClient()->m_Skins.Find(pName: "default" ); |
2693 | enum ELineFlag |
2694 | { |
2695 | FLAG_TEAM = 1 << 0, |
2696 | FLAG_FRIEND = 1 << 1, |
2697 | FLAG_HIGHLIGHT = 1 << 2, |
2698 | FLAG_CLIENT = 1 << 3 |
2699 | }; |
2700 | enum |
2701 | { |
2702 | PREVIEW_SYS, |
2703 | PREVIEW_HIGHLIGHT, |
2704 | PREVIEW_TEAM, |
2705 | PREVIEW_FRIEND, |
2706 | PREVIEW_SPAMMER, |
2707 | PREVIEW_CLIENT |
2708 | }; |
2709 | auto &&SetPreviewLine = [](int Index, int ClientId, const char *pName, const char *pText, int Flag, int Repeats) { |
2710 | SPreviewLine *pLine; |
2711 | if((int)s_vLines.size() <= Index) |
2712 | { |
2713 | s_vLines.emplace_back(); |
2714 | pLine = &s_vLines.back(); |
2715 | } |
2716 | else |
2717 | { |
2718 | pLine = &s_vLines[Index]; |
2719 | } |
2720 | pLine->m_ClientId = ClientId; |
2721 | pLine->m_Team = Flag & FLAG_TEAM; |
2722 | pLine->m_Friend = Flag & FLAG_FRIEND; |
2723 | pLine->m_Player = ClientId >= 0; |
2724 | pLine->m_Highlighted = Flag & FLAG_HIGHLIGHT; |
2725 | pLine->m_Client = Flag & FLAG_CLIENT; |
2726 | pLine->m_TimesRepeated = Repeats; |
2727 | str_copy(dst&: pLine->m_aName, src: pName); |
2728 | str_copy(dst&: pLine->m_aText, src: pText); |
2729 | }; |
2730 | auto &&SetLineSkin = [RealTeeSize, &pDefaultSkin](int Index, const CSkin *pSkin) { |
2731 | if(Index >= (int)s_vLines.size()) |
2732 | return; |
2733 | s_vLines[Index].m_RenderInfo.m_Size = RealTeeSize; |
2734 | s_vLines[Index].m_RenderInfo.m_CustomColoredSkin = false; |
2735 | if(pSkin != nullptr) |
2736 | s_vLines[Index].m_RenderInfo.m_OriginalRenderSkin = pSkin->m_OriginalSkin; |
2737 | else if(pDefaultSkin != nullptr) |
2738 | s_vLines[Index].m_RenderInfo.m_OriginalRenderSkin = pDefaultSkin->m_OriginalSkin; |
2739 | }; |
2740 | |
2741 | auto &&RenderPreview = [&](int LineIndex, int x, int y, bool Render = true) { |
2742 | if(LineIndex >= (int)s_vLines.size()) |
2743 | return vec2(0, 0); |
2744 | CTextCursor LocalCursor; |
2745 | TextRender()->SetCursor(pCursor: &LocalCursor, x, y, FontSize: RealFontSize, Flags: Render ? TEXTFLAG_RENDER : 0); |
2746 | LocalCursor.m_LineWidth = LineWidth; |
2747 | const auto &Line = s_vLines[LineIndex]; |
2748 | |
2749 | char aName[64 + 12] = "" ; |
2750 | |
2751 | if(g_Config.m_ClShowIds && Line.m_ClientId >= 0 && Line.m_aName[0] != '\0') |
2752 | { |
2753 | if(Line.m_ClientId < 10) |
2754 | str_format(buffer: aName, buffer_size: sizeof(aName), format: " %d: " , Line.m_ClientId); |
2755 | else |
2756 | str_format(buffer: aName, buffer_size: sizeof(aName), format: "%d: " , Line.m_ClientId); |
2757 | } |
2758 | |
2759 | str_append(dst&: aName, src: Line.m_aName); |
2760 | |
2761 | char aCount[12]; |
2762 | if(Line.m_ClientId < 0) |
2763 | str_format(buffer: aCount, buffer_size: sizeof(aCount), format: "[%d] " , Line.m_TimesRepeated + 1); |
2764 | else |
2765 | str_format(buffer: aCount, buffer_size: sizeof(aCount), format: " [%d]" , Line.m_TimesRepeated + 1); |
2766 | |
2767 | if(Line.m_Player) |
2768 | { |
2769 | LocalCursor.m_X += RealMsgPaddingTee; |
2770 | |
2771 | if(Line.m_Friend && g_Config.m_ClMessageFriend) |
2772 | { |
2773 | if(Render) |
2774 | TextRender()->TextColor(rgb: FriendColor); |
2775 | TextRender()->TextEx(pCursor: &LocalCursor, pText: "♥ " , Length: -1); |
2776 | } |
2777 | } |
2778 | |
2779 | ColorRGBA NameColor; |
2780 | if(Line.m_Team) |
2781 | NameColor = CalculateNameColor(TextColorHSL: color_cast<ColorHSLA>(rgb: TeamColor)); |
2782 | else if(Line.m_Player) |
2783 | NameColor = DefaultNameColor; |
2784 | else if(Line.m_Client) |
2785 | NameColor = ClientColor; |
2786 | else |
2787 | NameColor = SystemColor; |
2788 | |
2789 | if(Render) |
2790 | TextRender()->TextColor(rgb: NameColor); |
2791 | |
2792 | TextRender()->TextEx(pCursor: &LocalCursor, pText: aName, Length: -1); |
2793 | |
2794 | if(Line.m_TimesRepeated > 0) |
2795 | { |
2796 | if(Render) |
2797 | TextRender()->TextColor(r: 1.0f, g: 1.0f, b: 1.0f, a: 0.3f); |
2798 | TextRender()->TextEx(pCursor: &LocalCursor, pText: aCount, Length: -1); |
2799 | } |
2800 | |
2801 | if(Line.m_ClientId >= 0 && Line.m_aName[0] != '\0') |
2802 | { |
2803 | if(Render) |
2804 | TextRender()->TextColor(rgb: NameColor); |
2805 | TextRender()->TextEx(pCursor: &LocalCursor, pText: ": " , Length: -1); |
2806 | } |
2807 | |
2808 | CTextCursor AppendCursor = LocalCursor; |
2809 | AppendCursor.m_LongestLineWidth = 0.0f; |
2810 | if(!g_Config.m_ClChatOld) |
2811 | { |
2812 | AppendCursor.m_StartX = LocalCursor.m_X; |
2813 | AppendCursor.m_LineWidth -= LocalCursor.m_LongestLineWidth; |
2814 | } |
2815 | |
2816 | if(Render) |
2817 | { |
2818 | if(Line.m_Highlighted) |
2819 | TextRender()->TextColor(rgb: HighlightedColor); |
2820 | else if(Line.m_Team) |
2821 | TextRender()->TextColor(rgb: TeamColor); |
2822 | else if(Line.m_Player) |
2823 | TextRender()->TextColor(rgb: NormalColor); |
2824 | } |
2825 | |
2826 | TextRender()->TextEx(pCursor: &AppendCursor, pText: Line.m_aText, Length: -1); |
2827 | if(Render) |
2828 | TextRender()->TextColor(rgb: TextRender()->DefaultTextColor()); |
2829 | |
2830 | return vec2{LocalCursor.m_LongestLineWidth + AppendCursor.m_LongestLineWidth, AppendCursor.Height() + RealMsgPaddingY}; |
2831 | }; |
2832 | |
2833 | // Set preview lines |
2834 | { |
2835 | char aLineBuilder[128]; |
2836 | |
2837 | str_format(buffer: aLineBuilder, buffer_size: sizeof(aLineBuilder), format: "'%s' entered and joined the game" , aBuf); |
2838 | SetPreviewLine(PREVIEW_SYS, -1, "*** " , aLineBuilder, 0, 0); |
2839 | |
2840 | str_format(buffer: aLineBuilder, buffer_size: sizeof(aLineBuilder), format: "Hey, how are you %s?" , aBuf); |
2841 | SetPreviewLine(PREVIEW_HIGHLIGHT, 7, "Random Tee" , aLineBuilder, FLAG_HIGHLIGHT, 0); |
2842 | |
2843 | SetPreviewLine(PREVIEW_TEAM, 11, "Your Teammate" , "Let's speedrun this!" , FLAG_TEAM, 0); |
2844 | SetPreviewLine(PREVIEW_FRIEND, 8, "Friend" , "Hello there" , FLAG_FRIEND, 0); |
2845 | SetPreviewLine(PREVIEW_SPAMMER, 9, "Spammer" , "Hey fools, I'm spamming here!" , 0, 5); |
2846 | SetPreviewLine(PREVIEW_CLIENT, -1, "— " , "Echo command executed" , FLAG_CLIENT, 0); |
2847 | } |
2848 | |
2849 | SetLineSkin(1, GameClient()->m_Skins.FindOrNullptr(pName: "pinky" )); |
2850 | SetLineSkin(2, pDefaultSkin); |
2851 | SetLineSkin(3, GameClient()->m_Skins.FindOrNullptr(pName: "cammostripes" )); |
2852 | SetLineSkin(4, GameClient()->m_Skins.FindOrNullptr(pName: "beast" )); |
2853 | |
2854 | // Backgrounds first |
2855 | if(!g_Config.m_ClChatOld) |
2856 | { |
2857 | Graphics()->TextureClear(); |
2858 | Graphics()->QuadsBegin(); |
2859 | Graphics()->SetColor(r: 0, g: 0, b: 0, a: 0.12f); |
2860 | |
2861 | float TempY = Y; |
2862 | const float RealBackgroundRounding = Chat.MessageRounding() * 2.0f; |
2863 | |
2864 | auto &&RenderMessageBackground = [&](int LineIndex) { |
2865 | auto Size = RenderPreview(LineIndex, 0, 0, false); |
2866 | 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); |
2867 | return Size.y; |
2868 | }; |
2869 | |
2870 | if(g_Config.m_ClShowChatSystem) |
2871 | { |
2872 | TempY += RenderMessageBackground(PREVIEW_SYS); |
2873 | } |
2874 | |
2875 | if(!g_Config.m_ClShowChatFriends) |
2876 | { |
2877 | if(!g_Config.m_ClShowChatTeamMembersOnly) |
2878 | TempY += RenderMessageBackground(PREVIEW_HIGHLIGHT); |
2879 | TempY += RenderMessageBackground(PREVIEW_TEAM); |
2880 | } |
2881 | |
2882 | if(!g_Config.m_ClShowChatTeamMembersOnly) |
2883 | TempY += RenderMessageBackground(PREVIEW_FRIEND); |
2884 | |
2885 | if(!g_Config.m_ClShowChatFriends && !g_Config.m_ClShowChatTeamMembersOnly) |
2886 | { |
2887 | TempY += RenderMessageBackground(PREVIEW_SPAMMER); |
2888 | } |
2889 | |
2890 | TempY += RenderMessageBackground(PREVIEW_CLIENT); |
2891 | |
2892 | Graphics()->QuadsEnd(); |
2893 | } |
2894 | |
2895 | // System |
2896 | if(g_Config.m_ClShowChatSystem) |
2897 | { |
2898 | Y += RenderPreview(PREVIEW_SYS, X, Y).y; |
2899 | } |
2900 | |
2901 | if(!g_Config.m_ClShowChatFriends) |
2902 | { |
2903 | // Highlighted |
2904 | if(!g_Config.m_ClChatOld && !g_Config.m_ClShowChatTeamMembersOnly) |
2905 | 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)); |
2906 | if(!g_Config.m_ClShowChatTeamMembersOnly) |
2907 | Y += RenderPreview(PREVIEW_HIGHLIGHT, X, Y).y; |
2908 | |
2909 | // Team |
2910 | if(!g_Config.m_ClChatOld) |
2911 | 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)); |
2912 | Y += RenderPreview(PREVIEW_TEAM, X, Y).y; |
2913 | } |
2914 | |
2915 | // Friend |
2916 | if(!g_Config.m_ClChatOld && !g_Config.m_ClShowChatTeamMembersOnly) |
2917 | 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)); |
2918 | if(!g_Config.m_ClShowChatTeamMembersOnly) |
2919 | Y += RenderPreview(PREVIEW_FRIEND, X, Y).y; |
2920 | |
2921 | // Normal |
2922 | if(!g_Config.m_ClShowChatFriends && !g_Config.m_ClShowChatTeamMembersOnly) |
2923 | { |
2924 | if(!g_Config.m_ClChatOld) |
2925 | 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)); |
2926 | Y += RenderPreview(PREVIEW_SPAMMER, X, Y).y; |
2927 | } |
2928 | // Client |
2929 | RenderPreview(PREVIEW_CLIENT, X, Y); |
2930 | |
2931 | TextRender()->TextColor(rgb: TextRender()->DefaultTextColor()); |
2932 | } |
2933 | else if(s_CurTab == APPEARANCE_TAB_NAME_PLATE) |
2934 | { |
2935 | MainView.VSplitMid(pLeft: &LeftView, pRight: &RightView, Spacing: MarginBetweenViews); |
2936 | |
2937 | // ***** Name Plate ***** // |
2938 | LeftView.HSplitTop(Cut: HeadlineHeight, pTop: &Label, pBottom: &LeftView); |
2939 | Ui()->DoLabel(pRect: &Label, pText: Localize(pStr: "Name Plate" ), Size: HeadlineFontSize, Align: TEXTALIGN_ML); |
2940 | LeftView.HSplitTop(Cut: MarginSmall, pTop: nullptr, pBottom: &LeftView); |
2941 | |
2942 | // General name plate settings |
2943 | DoButton_CheckBoxAutoVMarginAndSet(pId: &g_Config.m_ClNameplates, pText: Localize(pStr: "Show name plates" ), pValue: &g_Config.m_ClNameplates, pRect: &LeftView, VMargin: LineSize); |
2944 | LeftView.HSplitTop(Cut: 2 * LineSize, pTop: &Button, pBottom: &LeftView); |
2945 | Ui()->DoScrollbarOption(pId: &g_Config.m_ClNameplatesSize, pOption: &g_Config.m_ClNameplatesSize, pRect: &Button, pStr: Localize(pStr: "Name plates size" ), Min: 0, Max: 100, pScale: &CUi::ms_LinearScrollbarScale, Flags: CUi::SCROLLBAR_OPTION_MULTILINE); |
2946 | |
2947 | DoButton_CheckBoxAutoVMarginAndSet(pId: &g_Config.m_ClNameplatesClan, pText: Localize(pStr: "Show clan above name plates" ), pValue: &g_Config.m_ClNameplatesClan, pRect: &LeftView, VMargin: LineSize); |
2948 | LeftView.HSplitTop(Cut: 2 * LineSize, pTop: &Button, pBottom: &LeftView); |
2949 | if(g_Config.m_ClNameplatesClan) |
2950 | { |
2951 | Ui()->DoScrollbarOption(pId: &g_Config.m_ClNameplatesClanSize, pOption: &g_Config.m_ClNameplatesClanSize, pRect: &Button, pStr: Localize(pStr: "Clan plates size" ), Min: 0, Max: 100, pScale: &CUi::ms_LinearScrollbarScale, Flags: CUi::SCROLLBAR_OPTION_MULTILINE); |
2952 | } |
2953 | |
2954 | 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); |
2955 | DoButton_CheckBoxAutoVMarginAndSet(pId: &g_Config.m_ClNameplatesFriendMark, pText: Localize(pStr: "Show friend mark (♥) in name plates" ), pValue: &g_Config.m_ClNameplatesFriendMark, pRect: &LeftView, VMargin: LineSize); |
2956 | |
2957 | LeftView.HSplitTop(Cut: LineSize, pTop: &Button, pBottom: &LeftView); |
2958 | if(DoButton_CheckBox(pId: &g_Config.m_ClNameplatesStrong, pText: Localize(pStr: "Show hook strength icon indicator" ), Checked: g_Config.m_ClNameplatesStrong, pRect: &Button)) |
2959 | { |
2960 | g_Config.m_ClNameplatesStrong = g_Config.m_ClNameplatesStrong ? 0 : 1; |
2961 | } |
2962 | LeftView.HSplitTop(Cut: LineSize, pTop: &Button, pBottom: &LeftView); |
2963 | if(g_Config.m_ClNameplatesStrong) |
2964 | { |
2965 | static int s_NameplatesStrong = 0; |
2966 | if(DoButton_CheckBox(pId: &s_NameplatesStrong, pText: Localize(pStr: "Show hook strength number indicator" ), Checked: g_Config.m_ClNameplatesStrong == 2, pRect: &Button)) |
2967 | g_Config.m_ClNameplatesStrong = g_Config.m_ClNameplatesStrong != 2 ? 2 : 1; |
2968 | } |
2969 | |
2970 | LeftView.HSplitTop(Cut: LineSize, pTop: &Button, pBottom: &LeftView); |
2971 | if(DoButton_CheckBox(pId: &g_Config.m_ClShowDirection, pText: Localize(pStr: "Show other players' key presses" ), Checked: g_Config.m_ClShowDirection >= 1 && g_Config.m_ClShowDirection != 3, pRect: &Button)) |
2972 | { |
2973 | g_Config.m_ClShowDirection = g_Config.m_ClShowDirection ^ 1; |
2974 | } |
2975 | |
2976 | LeftView.HSplitTop(Cut: LineSize, pTop: &Button, pBottom: &LeftView); |
2977 | static int s_ShowLocalPlayer = 0; |
2978 | if(DoButton_CheckBox(pId: &s_ShowLocalPlayer, pText: Localize(pStr: "Show local player's key presses" ), Checked: g_Config.m_ClShowDirection >= 2, pRect: &Button)) |
2979 | { |
2980 | g_Config.m_ClShowDirection = g_Config.m_ClShowDirection ^ 3; |
2981 | } |
2982 | |
2983 | ColorRGBA GreenDefault(0.78f, 1.0f, 0.8f, 1.0f); |
2984 | static CButtonContainer s_AuthedColor, s_SameClanColor; |
2985 | 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); |
2986 | 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); |
2987 | } |
2988 | else if(s_CurTab == APPEARANCE_TAB_HOOK_COLLISION) |
2989 | { |
2990 | MainView.VSplitMid(pLeft: &LeftView, pRight: &RightView, Spacing: MarginBetweenViews); |
2991 | |
2992 | // ***** Hookline ***** // |
2993 | LeftView.HSplitTop(Cut: HeadlineHeight, pTop: &Label, pBottom: &LeftView); |
2994 | Ui()->DoLabel(pRect: &Label, pText: Localize(pStr: "Hook collision line" ), Size: HeadlineFontSize, Align: TEXTALIGN_ML); |
2995 | LeftView.HSplitTop(Cut: MarginSmall, pTop: nullptr, pBottom: &LeftView); |
2996 | |
2997 | // General hookline settings |
2998 | LeftView.HSplitTop(Cut: LineSize, pTop: &Button, pBottom: &LeftView); |
2999 | 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)) |
3000 | { |
3001 | g_Config.m_ClShowHookCollOwn = g_Config.m_ClShowHookCollOwn ? 0 : 1; |
3002 | } |
3003 | LeftView.HSplitTop(Cut: LineSize, pTop: &Button, pBottom: &LeftView); |
3004 | if(g_Config.m_ClShowHookCollOwn) |
3005 | { |
3006 | static int s_ShowHookCollOwn = 0; |
3007 | 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)) |
3008 | g_Config.m_ClShowHookCollOwn = g_Config.m_ClShowHookCollOwn != 2 ? 2 : 1; |
3009 | } |
3010 | |
3011 | LeftView.HSplitTop(Cut: LineSize, pTop: &Button, pBottom: &LeftView); |
3012 | if(DoButton_CheckBox(pId: &g_Config.m_ClShowHookCollOther, pText: Localize(pStr: "Show other players' hook collision lines" ), Checked: g_Config.m_ClShowHookCollOther, pRect: &Button)) |
3013 | { |
3014 | g_Config.m_ClShowHookCollOther = g_Config.m_ClShowHookCollOther >= 1 ? 0 : 1; |
3015 | } |
3016 | LeftView.HSplitTop(Cut: LineSize, pTop: &Button, pBottom: &LeftView); |
3017 | if(g_Config.m_ClShowHookCollOther) |
3018 | { |
3019 | static int s_ShowHookCollOther = 0; |
3020 | if(DoButton_CheckBox(pId: &s_ShowHookCollOther, pText: Localize(pStr: "Always show other players' hook collision lines" ), Checked: g_Config.m_ClShowHookCollOther == 2, pRect: &Button)) |
3021 | g_Config.m_ClShowHookCollOther = g_Config.m_ClShowHookCollOther != 2 ? 2 : 1; |
3022 | } |
3023 | |
3024 | LeftView.HSplitTop(Cut: 2 * LineSize, pTop: &Button, pBottom: &LeftView); |
3025 | Ui()->DoScrollbarOption(pId: &g_Config.m_ClHookCollSize, pOption: &g_Config.m_ClHookCollSize, pRect: &Button, pStr: Localize(pStr: "Hook collision line width" ), Min: 0, Max: 20, pScale: &CUi::ms_LinearScrollbarScale, Flags: CUi::SCROLLBAR_OPTION_MULTILINE); |
3026 | |
3027 | LeftView.HSplitTop(Cut: 2 * LineSize, pTop: &Button, pBottom: &LeftView); |
3028 | 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: "%" ); |
3029 | |
3030 | static CButtonContainer s_HookCollNoCollResetId, s_HookCollHookableCollResetId, s_HookCollTeeCollResetId; |
3031 | static int s_HookCollToolTip; |
3032 | |
3033 | LeftView.HSplitTop(Cut: LineSize, pTop: &Label, pBottom: &LeftView); |
3034 | LeftView.HSplitTop(Cut: MarginSmall, pTop: nullptr, pBottom: &LeftView); |
3035 | Ui()->DoLabel(pRect: &Label, pText: Localize(pStr: "Colors of the hook collision line, in case of a possible collision with:" ), Size: 13.0f, Align: TEXTALIGN_ML); |
3036 | Ui()->DoButtonLogic(pId: &s_HookCollToolTip, Checked: 0, pRect: &Label); // Just for the tooltip, result ignored |
3037 | GameClient()->m_Tooltips.DoToolTip(pId: &s_HookCollToolTip, pNearRect: &Label, pText: Localize(pStr: "Your movements are not taken into account when calculating the line colors" )); |
3038 | 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); |
3039 | 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); |
3040 | 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); |
3041 | } |
3042 | else if(s_CurTab == APPEARANCE_TAB_INFO_MESSAGES) |
3043 | { |
3044 | MainView.VSplitMid(pLeft: &LeftView, pRight: &RightView, Spacing: MarginBetweenViews); |
3045 | |
3046 | // ***** Info Messages ***** // |
3047 | LeftView.HSplitTop(Cut: HeadlineHeight, pTop: &Label, pBottom: &LeftView); |
3048 | Ui()->DoLabel(pRect: &Label, pText: Localize(pStr: "Info Messages" ), Size: HeadlineFontSize, Align: TEXTALIGN_ML); |
3049 | LeftView.HSplitTop(Cut: MarginSmall, pTop: nullptr, pBottom: &LeftView); |
3050 | |
3051 | // General info messages settings |
3052 | LeftView.HSplitTop(Cut: LineSize, pTop: &Button, pBottom: &LeftView); |
3053 | if(DoButton_CheckBox(pId: &g_Config.m_ClShowKillMessages, pText: Localize(pStr: "Show kill messages" ), Checked: g_Config.m_ClShowKillMessages, pRect: &Button)) |
3054 | { |
3055 | g_Config.m_ClShowKillMessages ^= 1; |
3056 | } |
3057 | |
3058 | LeftView.HSplitTop(Cut: LineSize, pTop: &Button, pBottom: &LeftView); |
3059 | if(DoButton_CheckBox(pId: &g_Config.m_ClShowFinishMessages, pText: Localize(pStr: "Show finish messages" ), Checked: g_Config.m_ClShowFinishMessages, pRect: &Button)) |
3060 | { |
3061 | g_Config.m_ClShowFinishMessages ^= 1; |
3062 | } |
3063 | |
3064 | static CButtonContainer s_KillMessageNormalColorId, s_KillMessageHighlightColorId; |
3065 | 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); |
3066 | 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); |
3067 | } |
3068 | else if(s_CurTab == APPEARANCE_TAB_LASER) |
3069 | { |
3070 | MainView.VSplitMid(pLeft: &LeftView, pRight: &RightView, Spacing: MarginBetweenViews); |
3071 | |
3072 | // ***** Weapons ***** // |
3073 | LeftView.HSplitTop(Cut: HeadlineHeight, pTop: &Label, pBottom: &LeftView); |
3074 | Ui()->DoLabel(pRect: &Label, pText: Localize(pStr: "Weapons" ), Size: HeadlineFontSize, Align: TEXTALIGN_ML); |
3075 | LeftView.HSplitTop(Cut: MarginSmall, pTop: nullptr, pBottom: &LeftView); |
3076 | |
3077 | // General weapon laser settings |
3078 | static CButtonContainer s_LaserRifleOutResetId, s_LaserRifleInResetId, s_LaserShotgunOutResetId, s_LaserShotgunInResetId; |
3079 | |
3080 | 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); |
3081 | 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); |
3082 | 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); |
3083 | 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); |
3084 | |
3085 | // ***** Entities ***** // |
3086 | LeftView.HSplitTop(Cut: 10.0f, pTop: nullptr, pBottom: &LeftView); |
3087 | LeftView.HSplitTop(Cut: HeadlineHeight, pTop: &Label, pBottom: &LeftView); |
3088 | Ui()->DoLabel(pRect: &Label, pText: Localize(pStr: "Entities" ), Size: HeadlineFontSize, Align: TEXTALIGN_ML); |
3089 | LeftView.HSplitTop(Cut: MarginSmall, pTop: nullptr, pBottom: &LeftView); |
3090 | |
3091 | // General entity laser settings |
3092 | static CButtonContainer s_LaserDoorOutResetId, s_LaserDoorInResetId, s_LaserFreezeOutResetId, s_LaserFreezeInResetId; |
3093 | |
3094 | 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); |
3095 | 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); |
3096 | 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); |
3097 | 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); |
3098 | |
3099 | static CButtonContainer s_AllToRifleResetId, s_AllToDefaultResetId; |
3100 | |
3101 | LeftView.HSplitTop(Cut: 4 * MarginSmall, pTop: nullptr, pBottom: &LeftView); |
3102 | LeftView.HSplitTop(Cut: LineSize, pTop: &Button, pBottom: &LeftView); |
3103 | if(DoButton_Menu(pButtonContainer: &s_AllToRifleResetId, pText: Localize(pStr: "Set all to Rifle" ), Checked: 0, pRect: &Button)) |
3104 | { |
3105 | g_Config.m_ClLaserShotgunOutlineColor = g_Config.m_ClLaserRifleOutlineColor; |
3106 | g_Config.m_ClLaserShotgunInnerColor = g_Config.m_ClLaserRifleInnerColor; |
3107 | g_Config.m_ClLaserDoorOutlineColor = g_Config.m_ClLaserRifleOutlineColor; |
3108 | g_Config.m_ClLaserDoorInnerColor = g_Config.m_ClLaserRifleInnerColor; |
3109 | g_Config.m_ClLaserFreezeOutlineColor = g_Config.m_ClLaserRifleOutlineColor; |
3110 | g_Config.m_ClLaserFreezeInnerColor = g_Config.m_ClLaserRifleInnerColor; |
3111 | } |
3112 | |
3113 | // values taken from the CL commands |
3114 | LeftView.HSplitTop(Cut: 2 * MarginSmall, pTop: nullptr, pBottom: &LeftView); |
3115 | LeftView.HSplitTop(Cut: LineSize, pTop: &Button, pBottom: &LeftView); |
3116 | if(DoButton_Menu(pButtonContainer: &s_AllToDefaultResetId, pText: Localize(pStr: "Reset to defaults" ), Checked: 0, pRect: &Button)) |
3117 | { |
3118 | g_Config.m_ClLaserRifleOutlineColor = 11176233; |
3119 | g_Config.m_ClLaserRifleInnerColor = 11206591; |
3120 | g_Config.m_ClLaserShotgunOutlineColor = 1866773; |
3121 | g_Config.m_ClLaserShotgunInnerColor = 1467241; |
3122 | g_Config.m_ClLaserDoorOutlineColor = 7667473; |
3123 | g_Config.m_ClLaserDoorInnerColor = 7701379; |
3124 | g_Config.m_ClLaserFreezeOutlineColor = 11613223; |
3125 | g_Config.m_ClLaserFreezeInnerColor = 12001153; |
3126 | } |
3127 | |
3128 | // ***** Laser Preview ***** // |
3129 | RightView.HSplitTop(Cut: HeadlineHeight, pTop: &Label, pBottom: &RightView); |
3130 | Ui()->DoLabel(pRect: &Label, pText: Localize(pStr: "Preview" ), Size: HeadlineFontSize, Align: TEXTALIGN_ML); |
3131 | RightView.HSplitTop(Cut: MarginSmall, pTop: nullptr, pBottom: &RightView); |
3132 | |
3133 | const float LaserPreviewHeight = 50.0f; |
3134 | CUIRect LaserPreview; |
3135 | RightView.HSplitTop(Cut: LaserPreviewHeight, pTop: &LaserPreview, pBottom: &RightView); |
3136 | RightView.HSplitTop(Cut: 2 * MarginSmall, pTop: nullptr, pBottom: &RightView); |
3137 | DoLaserPreview(pRect: &LaserPreview, OutlineColor: LaserRifleOutlineColor, InnerColor: LaserRifleInnerColor, LaserType: LASERTYPE_RIFLE); |
3138 | |
3139 | RightView.HSplitTop(Cut: LaserPreviewHeight, pTop: &LaserPreview, pBottom: &RightView); |
3140 | RightView.HSplitTop(Cut: 2 * MarginSmall, pTop: nullptr, pBottom: &RightView); |
3141 | DoLaserPreview(pRect: &LaserPreview, OutlineColor: LaserShotgunOutlineColor, InnerColor: LaserShotgunInnerColor, LaserType: LASERTYPE_SHOTGUN); |
3142 | |
3143 | RightView.HSplitTop(Cut: LaserPreviewHeight, pTop: &LaserPreview, pBottom: &RightView); |
3144 | RightView.HSplitTop(Cut: 2 * MarginSmall, pTop: nullptr, pBottom: &RightView); |
3145 | DoLaserPreview(pRect: &LaserPreview, OutlineColor: LaserDoorOutlineColor, InnerColor: LaserDoorInnerColor, LaserType: LASERTYPE_DOOR); |
3146 | |
3147 | RightView.HSplitTop(Cut: LaserPreviewHeight, pTop: &LaserPreview, pBottom: &RightView); |
3148 | RightView.HSplitTop(Cut: 2 * MarginSmall, pTop: nullptr, pBottom: &RightView); |
3149 | DoLaserPreview(pRect: &LaserPreview, OutlineColor: LaserFreezeOutlineColor, InnerColor: LaserFreezeInnerColor, LaserType: LASERTYPE_DOOR); |
3150 | } |
3151 | } |
3152 | |
3153 | void CMenus::(CUIRect MainView) |
3154 | { |
3155 | CUIRect Button, Left, Right, LeftLeft, Label; |
3156 | |
3157 | #if defined(CONF_AUTOUPDATE) |
3158 | CUIRect UpdaterRect; |
3159 | MainView.HSplitBottom(Cut: 20.0f, pTop: &MainView, pBottom: &UpdaterRect); |
3160 | MainView.HSplitBottom(Cut: 5.0f, pTop: &MainView, pBottom: nullptr); |
3161 | #endif |
3162 | |
3163 | // demo |
3164 | CUIRect Demo; |
3165 | MainView.HSplitTop(Cut: 110.0f, pTop: &Demo, pBottom: &MainView); |
3166 | Demo.HSplitTop(Cut: 30.0f, pTop: &Label, pBottom: &Demo); |
3167 | Ui()->DoLabel(pRect: &Label, pText: Localize(pStr: "Demo" ), Size: 20.0f, Align: TEXTALIGN_ML); |
3168 | Label.VSplitMid(pLeft: nullptr, pRight: &Label, Spacing: 20.0f); |
3169 | Ui()->DoLabel(pRect: &Label, pText: Localize(pStr: "Ghost" ), Size: 20.0f, Align: TEXTALIGN_ML); |
3170 | |
3171 | Demo.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &Demo); |
3172 | Demo.VSplitMid(pLeft: &Left, pRight: &Right, Spacing: 20.0f); |
3173 | |
3174 | Left.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Left); |
3175 | 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)) |
3176 | { |
3177 | g_Config.m_ClAutoRaceRecord ^= 1; |
3178 | } |
3179 | |
3180 | Left.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Left); |
3181 | if(DoButton_CheckBox(pId: &g_Config.m_ClReplays, pText: Localize(pStr: "Enable replays" ), Checked: g_Config.m_ClReplays, pRect: &Button)) |
3182 | { |
3183 | g_Config.m_ClReplays ^= 1; |
3184 | Client()->DemoRecorder_UpdateReplayRecorder(); |
3185 | } |
3186 | |
3187 | Left.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Left); |
3188 | if(g_Config.m_ClReplays) |
3189 | 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); |
3190 | |
3191 | Right.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Right); |
3192 | if(DoButton_CheckBox(pId: &g_Config.m_ClRaceGhost, pText: Localize(pStr: "Enable ghost" ), Checked: g_Config.m_ClRaceGhost, pRect: &Button)) |
3193 | { |
3194 | g_Config.m_ClRaceGhost ^= 1; |
3195 | } |
3196 | 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" )); |
3197 | |
3198 | if(g_Config.m_ClRaceGhost) |
3199 | { |
3200 | Right.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Right); |
3201 | Button.VSplitMid(pLeft: &LeftLeft, pRight: &Button); |
3202 | if(DoButton_CheckBox(pId: &g_Config.m_ClRaceShowGhost, pText: Localize(pStr: "Show ghost" ), Checked: g_Config.m_ClRaceShowGhost, pRect: &LeftLeft)) |
3203 | { |
3204 | g_Config.m_ClRaceShowGhost ^= 1; |
3205 | } |
3206 | 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: "%" ); |
3207 | |
3208 | Right.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Right); |
3209 | if(DoButton_CheckBox(pId: &g_Config.m_ClRaceSaveGhost, pText: Localize(pStr: "Save ghost" ), Checked: g_Config.m_ClRaceSaveGhost, pRect: &Button)) |
3210 | { |
3211 | g_Config.m_ClRaceSaveGhost ^= 1; |
3212 | } |
3213 | |
3214 | if(g_Config.m_ClRaceSaveGhost) |
3215 | { |
3216 | Right.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Right); |
3217 | if(DoButton_CheckBox(pId: &g_Config.m_ClRaceGhostSaveBest, pText: Localize(pStr: "Only save improvements" ), Checked: g_Config.m_ClRaceGhostSaveBest, pRect: &Button)) |
3218 | { |
3219 | g_Config.m_ClRaceGhostSaveBest ^= 1; |
3220 | } |
3221 | } |
3222 | } |
3223 | |
3224 | // gameplay |
3225 | CUIRect Gameplay; |
3226 | MainView.HSplitTop(Cut: 150.0f, pTop: &Gameplay, pBottom: &MainView); |
3227 | Gameplay.HSplitTop(Cut: 30.0f, pTop: &Label, pBottom: &Gameplay); |
3228 | Ui()->DoLabel(pRect: &Label, pText: Localize(pStr: "Gameplay" ), Size: 20.0f, Align: TEXTALIGN_ML); |
3229 | Gameplay.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &Gameplay); |
3230 | Gameplay.VSplitMid(pLeft: &Left, pRight: &Right, Spacing: 20.0f); |
3231 | |
3232 | Left.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Left); |
3233 | Ui()->DoScrollbarOption(pId: &g_Config.m_ClOverlayEntities, pOption: &g_Config.m_ClOverlayEntities, pRect: &Button, pStr: Localize(pStr: "Overlay entities" ), Min: 0, Max: 100); |
3234 | |
3235 | Left.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Left); |
3236 | Button.VSplitMid(pLeft: &LeftLeft, pRight: &Button); |
3237 | |
3238 | if(DoButton_CheckBox(pId: &g_Config.m_ClTextEntities, pText: Localize(pStr: "Show text entities" ), Checked: g_Config.m_ClTextEntities, pRect: &LeftLeft)) |
3239 | g_Config.m_ClTextEntities ^= 1; |
3240 | |
3241 | if(g_Config.m_ClTextEntities) |
3242 | { |
3243 | if(Ui()->DoScrollbarOption(pId: &g_Config.m_ClTextEntitiesSize, pOption: &g_Config.m_ClTextEntitiesSize, pRect: &Button, pStr: Localize(pStr: "Size" ), Min: 0, Max: 100)) |
3244 | m_pClient->m_MapImages.SetTextureScale(g_Config.m_ClTextEntitiesSize); |
3245 | } |
3246 | |
3247 | Left.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Left); |
3248 | Button.VSplitMid(pLeft: &LeftLeft, pRight: &Button); |
3249 | |
3250 | if(DoButton_CheckBox(pId: &g_Config.m_ClShowOthers, pText: Localize(pStr: "Show others" ), Checked: g_Config.m_ClShowOthers == SHOW_OTHERS_ON, pRect: &LeftLeft)) |
3251 | g_Config.m_ClShowOthers = g_Config.m_ClShowOthers != SHOW_OTHERS_ON ? SHOW_OTHERS_ON : SHOW_OTHERS_OFF; |
3252 | |
3253 | 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: "%" ); |
3254 | |
3255 | 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 nameplates" )); |
3256 | |
3257 | Left.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Left); |
3258 | static int s_ShowOwnTeamId = 0; |
3259 | 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)) |
3260 | { |
3261 | g_Config.m_ClShowOthers = g_Config.m_ClShowOthers != SHOW_OTHERS_ONLY_TEAM ? SHOW_OTHERS_ONLY_TEAM : SHOW_OTHERS_OFF; |
3262 | } |
3263 | |
3264 | Left.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Left); |
3265 | if(DoButton_CheckBox(pId: &g_Config.m_ClShowQuads, pText: Localize(pStr: "Show quads" ), Checked: g_Config.m_ClShowQuads, pRect: &Button)) |
3266 | { |
3267 | g_Config.m_ClShowQuads ^= 1; |
3268 | } |
3269 | GameClient()->m_Tooltips.DoToolTip(pId: &g_Config.m_ClShowQuads, pNearRect: &Button, pText: Localize(pStr: "Quads are used for background decoration" )); |
3270 | |
3271 | Right.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Right); |
3272 | if(Ui()->DoScrollbarOption(pId: &g_Config.m_ClDefaultZoom, pOption: &g_Config.m_ClDefaultZoom, pRect: &Button, pStr: Localize(pStr: "Default zoom" ), Min: 0, Max: 20)) |
3273 | m_pClient->m_Camera.SetZoom(Target: std::pow(x: CCamera::ZOOM_STEP, y: g_Config.m_ClDefaultZoom - 10), Smoothness: g_Config.m_ClSmoothZoomTime); |
3274 | |
3275 | Right.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Right); |
3276 | if(DoButton_CheckBox(pId: &g_Config.m_ClAntiPing, pText: Localize(pStr: "AntiPing" ), Checked: g_Config.m_ClAntiPing, pRect: &Button)) |
3277 | { |
3278 | g_Config.m_ClAntiPing ^= 1; |
3279 | } |
3280 | 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" )); |
3281 | |
3282 | if(g_Config.m_ClAntiPing) |
3283 | { |
3284 | Right.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Right); |
3285 | if(DoButton_CheckBox(pId: &g_Config.m_ClAntiPingPlayers, pText: Localize(pStr: "AntiPing: predict other players" ), Checked: g_Config.m_ClAntiPingPlayers, pRect: &Button)) |
3286 | { |
3287 | g_Config.m_ClAntiPingPlayers ^= 1; |
3288 | } |
3289 | |
3290 | Right.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Right); |
3291 | if(DoButton_CheckBox(pId: &g_Config.m_ClAntiPingWeapons, pText: Localize(pStr: "AntiPing: predict weapons" ), Checked: g_Config.m_ClAntiPingWeapons, pRect: &Button)) |
3292 | { |
3293 | g_Config.m_ClAntiPingWeapons ^= 1; |
3294 | } |
3295 | |
3296 | Right.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Right); |
3297 | if(DoButton_CheckBox(pId: &g_Config.m_ClAntiPingGrenade, pText: Localize(pStr: "AntiPing: predict grenade paths" ), Checked: g_Config.m_ClAntiPingGrenade, pRect: &Button)) |
3298 | { |
3299 | g_Config.m_ClAntiPingGrenade ^= 1; |
3300 | } |
3301 | } |
3302 | |
3303 | CUIRect Background, Miscellaneous; |
3304 | MainView.VSplitMid(pLeft: &Background, pRight: &Miscellaneous, Spacing: 20.0f); |
3305 | |
3306 | // background |
3307 | Background.HSplitTop(Cut: 30.0f, pTop: &Label, pBottom: &Background); |
3308 | Background.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &Background); |
3309 | Ui()->DoLabel(pRect: &Label, pText: Localize(pStr: "Background" ), Size: 20.0f, Align: TEXTALIGN_ML); |
3310 | |
3311 | ColorRGBA GreyDefault(0.5f, 0.5f, 0.5f, 1); |
3312 | |
3313 | static CButtonContainer s_ResetId1; |
3314 | 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); |
3315 | |
3316 | static CButtonContainer s_ResetId2; |
3317 | 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); |
3318 | |
3319 | CUIRect EditBox; |
3320 | Background.HSplitTop(Cut: 20.0f, pTop: &Label, pBottom: &Background); |
3321 | Background.HSplitTop(Cut: 2.0f, pTop: nullptr, pBottom: &Background); |
3322 | Label.VSplitLeft(Cut: 100.0f, pLeft: &Label, pRight: &EditBox); |
3323 | EditBox.VSplitRight(Cut: 100.0f, pLeft: &EditBox, pRight: &Button); |
3324 | EditBox.VSplitRight(Cut: 5.0f, pLeft: &EditBox, pRight: nullptr); |
3325 | |
3326 | Ui()->DoLabel(pRect: &Label, pText: Localize(pStr: "Map" ), Size: 14.0f, Align: TEXTALIGN_ML); |
3327 | |
3328 | static CLineInput s_BackgroundEntitiesInput(g_Config.m_ClBackgroundEntities, sizeof(g_Config.m_ClBackgroundEntities)); |
3329 | Ui()->DoEditBox(pLineInput: &s_BackgroundEntitiesInput, pRect: &EditBox, FontSize: 14.0f); |
3330 | |
3331 | static CButtonContainer s_BackgroundEntitiesReloadButton; |
3332 | if(DoButton_Menu(pButtonContainer: &s_BackgroundEntitiesReloadButton, pText: Localize(pStr: "Reload" ), Checked: 0, pRect: &Button)) |
3333 | { |
3334 | UpdateBackgroundEntities(); |
3335 | } |
3336 | |
3337 | Background.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Background); |
3338 | const bool UseCurrentMap = str_comp(a: g_Config.m_ClBackgroundEntities, CURRENT_MAP) == 0; |
3339 | static int s_UseCurrentMapId = 0; |
3340 | if(DoButton_CheckBox(pId: &s_UseCurrentMapId, pText: Localize(pStr: "Use current map as background" ), Checked: UseCurrentMap, pRect: &Button)) |
3341 | { |
3342 | if(UseCurrentMap) |
3343 | g_Config.m_ClBackgroundEntities[0] = '\0'; |
3344 | else |
3345 | str_copy(dst&: g_Config.m_ClBackgroundEntities, CURRENT_MAP); |
3346 | m_pClient->m_Background.LoadBackground(); |
3347 | } |
3348 | |
3349 | Background.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Background); |
3350 | if(DoButton_CheckBox(pId: &g_Config.m_ClBackgroundShowTilesLayers, pText: Localize(pStr: "Show tiles layers from BG map" ), Checked: g_Config.m_ClBackgroundShowTilesLayers, pRect: &Button)) |
3351 | g_Config.m_ClBackgroundShowTilesLayers ^= 1; |
3352 | |
3353 | // miscellaneous |
3354 | Miscellaneous.HSplitTop(Cut: 30.0f, pTop: &Label, pBottom: &Miscellaneous); |
3355 | Miscellaneous.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &Miscellaneous); |
3356 | |
3357 | Ui()->DoLabel(pRect: &Label, pText: Localize(pStr: "Miscellaneous" ), Size: 20.0f, Align: TEXTALIGN_ML); |
3358 | |
3359 | static CButtonContainer s_ButtonTimeout; |
3360 | Miscellaneous.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Miscellaneous); |
3361 | if(DoButton_Menu(pButtonContainer: &s_ButtonTimeout, pText: Localize(pStr: "New random timeout code" ), Checked: 0, pRect: &Button)) |
3362 | { |
3363 | Client()->GenerateTimeoutSeed(); |
3364 | } |
3365 | |
3366 | Miscellaneous.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &Miscellaneous); |
3367 | Miscellaneous.HSplitTop(Cut: 20.0f, pTop: &Label, pBottom: &Miscellaneous); |
3368 | Miscellaneous.HSplitTop(Cut: 2.0f, pTop: nullptr, pBottom: &Miscellaneous); |
3369 | Ui()->DoLabel(pRect: &Label, pText: Localize(pStr: "Run on join" ), Size: 14.0f, Align: TEXTALIGN_ML); |
3370 | Miscellaneous.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &Miscellaneous); |
3371 | static CLineInput s_RunOnJoinInput(g_Config.m_ClRunOnJoin, sizeof(g_Config.m_ClRunOnJoin)); |
3372 | s_RunOnJoinInput.SetEmptyText(Localize(pStr: "Chat command (e.g. showall 1)" )); |
3373 | Ui()->DoEditBox(pLineInput: &s_RunOnJoinInput, pRect: &Button, FontSize: 14.0f); |
3374 | |
3375 | #if defined(CONF_FAMILY_WINDOWS) |
3376 | static CButtonContainer s_ButtonUnregisterShell; |
3377 | Miscellaneous.HSplitTop(10.0f, nullptr, &Miscellaneous); |
3378 | Miscellaneous.HSplitTop(20.0f, &Button, &Miscellaneous); |
3379 | if(DoButton_Menu(&s_ButtonUnregisterShell, Localize("Unregister protocol and file extensions" ), 0, &Button)) |
3380 | { |
3381 | Client()->ShellUnregister(); |
3382 | } |
3383 | #endif |
3384 | |
3385 | // Updater |
3386 | #if defined(CONF_AUTOUPDATE) |
3387 | { |
3388 | bool NeedUpdate = str_comp(a: Client()->LatestVersion(), b: "0" ); |
3389 | IUpdater::EUpdaterState State = Updater()->GetCurrentState(); |
3390 | |
3391 | // Update Button |
3392 | char aBuf[256]; |
3393 | if(NeedUpdate && State <= IUpdater::CLEAN) |
3394 | { |
3395 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: Localize(pStr: "DDNet %s is available:" ), Client()->LatestVersion()); |
3396 | UpdaterRect.VSplitLeft(Cut: TextRender()->TextWidth(Size: 14.0f, pText: aBuf, StrLength: -1, LineWidth: -1.0f) + 10.0f, pLeft: &UpdaterRect, pRight: &Button); |
3397 | Button.VSplitLeft(Cut: 100.0f, pLeft: &Button, pRight: nullptr); |
3398 | static CButtonContainer s_ButtonUpdate; |
3399 | if(DoButton_Menu(pButtonContainer: &s_ButtonUpdate, pText: Localize(pStr: "Update now" ), Checked: 0, pRect: &Button)) |
3400 | { |
3401 | Updater()->InitiateUpdate(); |
3402 | } |
3403 | } |
3404 | else if(State >= IUpdater::GETTING_MANIFEST && State < IUpdater::NEED_RESTART) |
3405 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: Localize(pStr: "Updating…" )); |
3406 | else if(State == IUpdater::NEED_RESTART) |
3407 | { |
3408 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: Localize(pStr: "DDNet Client updated!" )); |
3409 | m_NeedRestartUpdate = true; |
3410 | } |
3411 | else |
3412 | { |
3413 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: Localize(pStr: "No updates available" )); |
3414 | UpdaterRect.VSplitLeft(Cut: TextRender()->TextWidth(Size: 14.0f, pText: aBuf, StrLength: -1, LineWidth: -1.0f) + 10.0f, pLeft: &UpdaterRect, pRight: &Button); |
3415 | Button.VSplitLeft(Cut: 100.0f, pLeft: &Button, pRight: nullptr); |
3416 | static CButtonContainer s_ButtonUpdate; |
3417 | if(DoButton_Menu(pButtonContainer: &s_ButtonUpdate, pText: Localize(pStr: "Check now" ), Checked: 0, pRect: &Button)) |
3418 | { |
3419 | Client()->RequestDDNetInfo(); |
3420 | } |
3421 | } |
3422 | Ui()->DoLabel(pRect: &UpdaterRect, pText: aBuf, Size: 14.0f, Align: TEXTALIGN_ML); |
3423 | TextRender()->TextColor(r: 1.0f, g: 1.0f, b: 1.0f, a: 1.0f); |
3424 | } |
3425 | #endif |
3426 | } |
3427 | |