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/math.h> |
4 | #include <base/system.h> |
5 | |
6 | #include <engine/demo.h> |
7 | #include <engine/favorites.h> |
8 | #include <engine/friends.h> |
9 | #include <engine/ghost.h> |
10 | #include <engine/graphics.h> |
11 | #include <engine/serverbrowser.h> |
12 | #include <engine/shared/config.h> |
13 | #include <engine/shared/localization.h> |
14 | #include <engine/textrender.h> |
15 | |
16 | #include <game/generated/client_data.h> |
17 | #include <game/generated/protocol.h> |
18 | |
19 | #include <game/client/animstate.h> |
20 | #include <game/client/components/countryflags.h> |
21 | #include <game/client/gameclient.h> |
22 | #include <game/client/render.h> |
23 | #include <game/client/ui.h> |
24 | #include <game/client/ui_listbox.h> |
25 | #include <game/client/ui_scrollregion.h> |
26 | #include <game/localization.h> |
27 | |
28 | #include "menus.h" |
29 | #include "motd.h" |
30 | #include "voting.h" |
31 | |
32 | #include "ghost.h" |
33 | #include <engine/keys.h> |
34 | #include <engine/storage.h> |
35 | |
36 | #include <chrono> |
37 | |
38 | using namespace FontIcons; |
39 | using namespace std::chrono_literals; |
40 | |
41 | void CMenus::(CUIRect MainView) |
42 | { |
43 | CUIRect Button, ButtonBar, ButtonBar2; |
44 | bool ShowDDRaceButtons = MainView.w > 855.0f; |
45 | MainView.HSplitTop(Cut: 45.0f, pTop: &ButtonBar, pBottom: &MainView); |
46 | ButtonBar.Draw(Color: ms_ColorTabbarActive, Corners: IGraphics::CORNER_B, Rounding: 10.0f); |
47 | |
48 | // button bar |
49 | ButtonBar.HSplitTop(Cut: 10.0f, pTop: 0, pBottom: &ButtonBar); |
50 | ButtonBar.HSplitTop(Cut: 25.0f, pTop: &ButtonBar, pBottom: 0); |
51 | ButtonBar.VMargin(Cut: 10.0f, pOtherRect: &ButtonBar); |
52 | |
53 | ButtonBar.HSplitTop(Cut: 30.0f, pTop: 0, pBottom: &ButtonBar2); |
54 | ButtonBar2.HSplitTop(Cut: 25.0f, pTop: &ButtonBar2, pBottom: 0); |
55 | |
56 | ButtonBar.VSplitRight(Cut: 120.0f, pLeft: &ButtonBar, pRight: &Button); |
57 | |
58 | static CButtonContainer s_DisconnectButton; |
59 | if(DoButton_Menu(pButtonContainer: &s_DisconnectButton, pText: Localize(pStr: "Disconnect" ), Checked: 0, pRect: &Button)) |
60 | { |
61 | if(Client()->GetCurrentRaceTime() / 60 >= g_Config.m_ClConfirmDisconnectTime && g_Config.m_ClConfirmDisconnectTime >= 0) |
62 | { |
63 | PopupConfirm(pTitle: Localize(pStr: "Disconnect" ), pMessage: Localize(pStr: "Are you sure that you want to disconnect?" ), pConfirmButtonLabel: Localize(pStr: "Yes" ), pCancelButtonLabel: Localize(pStr: "No" ), pfnConfirmButtonCallback: &CMenus::PopupConfirmDisconnect); |
64 | } |
65 | else |
66 | { |
67 | Client()->Disconnect(); |
68 | RefreshBrowserTab(Force: true); |
69 | } |
70 | } |
71 | |
72 | ButtonBar.VSplitRight(Cut: 5.0f, pLeft: &ButtonBar, pRight: 0); |
73 | ButtonBar.VSplitRight(Cut: 170.0f, pLeft: &ButtonBar, pRight: &Button); |
74 | |
75 | bool DummyConnecting = Client()->DummyConnecting(); |
76 | static CButtonContainer s_DummyButton; |
77 | if(!Client()->DummyAllowed()) |
78 | { |
79 | DoButton_Menu(pButtonContainer: &s_DummyButton, pText: Localize(pStr: "Connect Dummy" ), Checked: 1, pRect: &Button); |
80 | } |
81 | else if(DummyConnecting) |
82 | { |
83 | DoButton_Menu(pButtonContainer: &s_DummyButton, pText: Localize(pStr: "Connecting dummy" ), Checked: 1, pRect: &Button); |
84 | } |
85 | else if(DoButton_Menu(pButtonContainer: &s_DummyButton, pText: Client()->DummyConnected() ? Localize(pStr: "Disconnect Dummy" ) : Localize(pStr: "Connect Dummy" ), Checked: 0, pRect: &Button)) |
86 | { |
87 | if(!Client()->DummyConnected()) |
88 | { |
89 | Client()->DummyConnect(); |
90 | } |
91 | else |
92 | { |
93 | if(Client()->GetCurrentRaceTime() / 60 >= g_Config.m_ClConfirmDisconnectTime && g_Config.m_ClConfirmDisconnectTime >= 0) |
94 | { |
95 | PopupConfirm(pTitle: Localize(pStr: "Disconnect Dummy" ), pMessage: Localize(pStr: "Are you sure that you want to disconnect your dummy?" ), pConfirmButtonLabel: Localize(pStr: "Yes" ), pCancelButtonLabel: Localize(pStr: "No" ), pfnConfirmButtonCallback: &CMenus::PopupConfirmDisconnectDummy); |
96 | } |
97 | else |
98 | { |
99 | Client()->DummyDisconnect(pReason: 0); |
100 | SetActive(false); |
101 | } |
102 | } |
103 | } |
104 | |
105 | ButtonBar.VSplitRight(Cut: 5.0f, pLeft: &ButtonBar, pRight: 0); |
106 | ButtonBar.VSplitRight(Cut: 140.0f, pLeft: &ButtonBar, pRight: &Button); |
107 | |
108 | static CButtonContainer s_DemoButton; |
109 | const bool Recording = DemoRecorder(Recorder: RECORDER_MANUAL)->IsRecording(); |
110 | if(DoButton_Menu(pButtonContainer: &s_DemoButton, pText: Recording ? Localize(pStr: "Stop record" ) : Localize(pStr: "Record demo" ), Checked: 0, pRect: &Button)) |
111 | { |
112 | if(!Recording) |
113 | Client()->DemoRecorder_Start(pFilename: Client()->GetCurrentMap(), WithTimestamp: true, Recorder: RECORDER_MANUAL); |
114 | else |
115 | Client()->DemoRecorder(Recorder: RECORDER_MANUAL)->Stop(Mode: IDemoRecorder::EStopMode::KEEP_FILE); |
116 | } |
117 | |
118 | bool Paused = false; |
119 | bool Spec = false; |
120 | if(m_pClient->m_Snap.m_LocalClientId >= 0) |
121 | { |
122 | Paused = m_pClient->m_aClients[m_pClient->m_Snap.m_LocalClientId].m_Paused; |
123 | Spec = m_pClient->m_aClients[m_pClient->m_Snap.m_LocalClientId].m_Spec; |
124 | } |
125 | |
126 | if(m_pClient->m_Snap.m_pLocalInfo && m_pClient->m_Snap.m_pGameInfoObj && !Paused && !Spec) |
127 | { |
128 | static CButtonContainer s_SpectateButton; |
129 | |
130 | if(m_pClient->m_Snap.m_pLocalInfo->m_Team != TEAM_SPECTATORS) |
131 | { |
132 | ButtonBar.VSplitLeft(Cut: 5.0f, pLeft: 0, pRight: &ButtonBar); |
133 | ButtonBar.VSplitLeft(Cut: 120.0f, pLeft: &Button, pRight: &ButtonBar); |
134 | if(!DummyConnecting && DoButton_Menu(pButtonContainer: &s_SpectateButton, pText: Localize(pStr: "Spectate" ), Checked: 0, pRect: &Button)) |
135 | { |
136 | if(g_Config.m_ClDummy == 0 || Client()->DummyConnected()) |
137 | { |
138 | m_pClient->SendSwitchTeam(Team: TEAM_SPECTATORS); |
139 | SetActive(false); |
140 | } |
141 | } |
142 | } |
143 | |
144 | if(m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags & GAMEFLAG_TEAMS) |
145 | { |
146 | if(m_pClient->m_Snap.m_pLocalInfo->m_Team != TEAM_RED) |
147 | { |
148 | ButtonBar.VSplitLeft(Cut: 5.0f, pLeft: 0, pRight: &ButtonBar); |
149 | ButtonBar.VSplitLeft(Cut: 120.0f, pLeft: &Button, pRight: &ButtonBar); |
150 | static CButtonContainer s_JoinRedButton; |
151 | if(!DummyConnecting && DoButton_Menu(pButtonContainer: &s_JoinRedButton, pText: Localize(pStr: "Join red" ), Checked: 0, pRect: &Button)) |
152 | { |
153 | m_pClient->SendSwitchTeam(Team: TEAM_RED); |
154 | SetActive(false); |
155 | } |
156 | } |
157 | |
158 | if(m_pClient->m_Snap.m_pLocalInfo->m_Team != TEAM_BLUE) |
159 | { |
160 | ButtonBar.VSplitLeft(Cut: 5.0f, pLeft: 0, pRight: &ButtonBar); |
161 | ButtonBar.VSplitLeft(Cut: 120.0f, pLeft: &Button, pRight: &ButtonBar); |
162 | static CButtonContainer s_JoinBlueButton; |
163 | if(!DummyConnecting && DoButton_Menu(pButtonContainer: &s_JoinBlueButton, pText: Localize(pStr: "Join blue" ), Checked: 0, pRect: &Button)) |
164 | { |
165 | m_pClient->SendSwitchTeam(Team: TEAM_BLUE); |
166 | SetActive(false); |
167 | } |
168 | } |
169 | } |
170 | else |
171 | { |
172 | if(m_pClient->m_Snap.m_pLocalInfo->m_Team != 0) |
173 | { |
174 | ButtonBar.VSplitLeft(Cut: 5.0f, pLeft: 0, pRight: &ButtonBar); |
175 | ButtonBar.VSplitLeft(Cut: 120.0f, pLeft: &Button, pRight: &ButtonBar); |
176 | if(!DummyConnecting && DoButton_Menu(pButtonContainer: &s_SpectateButton, pText: Localize(pStr: "Join game" ), Checked: 0, pRect: &Button)) |
177 | { |
178 | m_pClient->SendSwitchTeam(Team: 0); |
179 | SetActive(false); |
180 | } |
181 | } |
182 | } |
183 | |
184 | if(m_pClient->m_Snap.m_pLocalInfo->m_Team != TEAM_SPECTATORS && ShowDDRaceButtons) |
185 | { |
186 | ButtonBar.VSplitLeft(Cut: 5.0f, pLeft: 0, pRight: &ButtonBar); |
187 | ButtonBar.VSplitLeft(Cut: 65.0f, pLeft: &Button, pRight: &ButtonBar); |
188 | |
189 | static CButtonContainer s_KillButton; |
190 | if(DoButton_Menu(pButtonContainer: &s_KillButton, pText: Localize(pStr: "Kill" ), Checked: 0, pRect: &Button)) |
191 | { |
192 | m_pClient->SendKill(ClientId: -1); |
193 | SetActive(false); |
194 | } |
195 | } |
196 | } |
197 | |
198 | if(m_pClient->m_ReceivedDDNetPlayer && m_pClient->m_Snap.m_pLocalInfo && m_pClient->m_Snap.m_pGameInfoObj && ShowDDRaceButtons) |
199 | { |
200 | if(m_pClient->m_Snap.m_pLocalInfo->m_Team != TEAM_SPECTATORS || Paused || Spec) |
201 | { |
202 | ButtonBar.VSplitLeft(Cut: 5.0f, pLeft: 0, pRight: &ButtonBar); |
203 | ButtonBar.VSplitLeft(Cut: (!Paused && !Spec) ? 65.0f : 120.0f, pLeft: &Button, pRight: &ButtonBar); |
204 | |
205 | static CButtonContainer s_PauseButton; |
206 | if(DoButton_Menu(pButtonContainer: &s_PauseButton, pText: (!Paused && !Spec) ? Localize(pStr: "Pause" ) : Localize(pStr: "Join game" ), Checked: 0, pRect: &Button)) |
207 | { |
208 | m_pClient->Console()->ExecuteLine(pStr: "say /pause" ); |
209 | SetActive(false); |
210 | } |
211 | } |
212 | } |
213 | } |
214 | |
215 | void CMenus::() |
216 | { |
217 | Client()->Disconnect(); |
218 | } |
219 | |
220 | void CMenus::() |
221 | { |
222 | Client()->DummyDisconnect(pReason: 0); |
223 | SetActive(false); |
224 | } |
225 | |
226 | void CMenus::(CUIRect MainView) |
227 | { |
228 | CUIRect Button, Button2, ButtonBar, Options, Player; |
229 | MainView.Draw(Color: ms_ColorTabbarActive, Corners: IGraphics::CORNER_B, Rounding: 10.0f); |
230 | |
231 | // player options |
232 | MainView.Margin(Cut: 10.0f, pOtherRect: &Options); |
233 | Options.Draw(Color: ColorRGBA(1.0f, 1.0f, 1.0f, 0.25f), Corners: IGraphics::CORNER_ALL, Rounding: 10.0f); |
234 | Options.Margin(Cut: 10.0f, pOtherRect: &Options); |
235 | Options.HSplitTop(Cut: 50.0f, pTop: &Button, pBottom: &Options); |
236 | Ui()->DoLabel(pRect: &Button, pText: Localize(pStr: "Player options" ), Size: 34.0f, Align: TEXTALIGN_ML); |
237 | |
238 | // headline |
239 | Options.HSplitTop(Cut: 34.0f, pTop: &ButtonBar, pBottom: &Options); |
240 | ButtonBar.VSplitRight(Cut: 231.0f, pLeft: &Player, pRight: &ButtonBar); |
241 | Ui()->DoLabel(pRect: &Player, pText: Localize(pStr: "Player" ), Size: 24.0f, Align: TEXTALIGN_ML); |
242 | |
243 | ButtonBar.HMargin(Cut: 1.0f, pOtherRect: &ButtonBar); |
244 | float Width = ButtonBar.h * 2.0f; |
245 | ButtonBar.VSplitLeft(Cut: Width, pLeft: &Button, pRight: &ButtonBar); |
246 | RenderTools()->RenderIcon(ImageId: IMAGE_GUIICONS, SpriteId: SPRITE_GUIICON_MUTE, pRect: &Button); |
247 | |
248 | ButtonBar.VSplitLeft(Cut: 20.0f, pLeft: nullptr, pRight: &ButtonBar); |
249 | ButtonBar.VSplitLeft(Cut: Width, pLeft: &Button, pRight: &ButtonBar); |
250 | RenderTools()->RenderIcon(ImageId: IMAGE_GUIICONS, SpriteId: SPRITE_GUIICON_EMOTICON_MUTE, pRect: &Button); |
251 | |
252 | ButtonBar.VSplitLeft(Cut: 20.0f, pLeft: nullptr, pRight: &ButtonBar); |
253 | ButtonBar.VSplitLeft(Cut: Width, pLeft: &Button, pRight: &ButtonBar); |
254 | RenderTools()->RenderIcon(ImageId: IMAGE_GUIICONS, SpriteId: SPRITE_GUIICON_FRIEND, pRect: &Button); |
255 | |
256 | int TotalPlayers = 0; |
257 | for(const auto &pInfoByName : m_pClient->m_Snap.m_apInfoByName) |
258 | { |
259 | if(!pInfoByName) |
260 | continue; |
261 | |
262 | int Index = pInfoByName->m_ClientId; |
263 | |
264 | if(Index == m_pClient->m_Snap.m_LocalClientId) |
265 | continue; |
266 | |
267 | TotalPlayers++; |
268 | } |
269 | |
270 | static CListBox s_ListBox; |
271 | s_ListBox.DoStart(RowHeight: 24.0f, NumItems: TotalPlayers, ItemsPerRow: 1, RowsPerScroll: 3, SelectedIndex: -1, pRect: &Options); |
272 | |
273 | // options |
274 | static char s_aPlayerIds[MAX_CLIENTS][3] = {{0}}; |
275 | |
276 | for(int i = 0, Count = 0; i < MAX_CLIENTS; ++i) |
277 | { |
278 | if(!m_pClient->m_Snap.m_apInfoByName[i]) |
279 | continue; |
280 | |
281 | int Index = m_pClient->m_Snap.m_apInfoByName[i]->m_ClientId; |
282 | if(Index == m_pClient->m_Snap.m_LocalClientId) |
283 | continue; |
284 | |
285 | CGameClient::CClientData &CurrentClient = m_pClient->m_aClients[Index]; |
286 | const CListboxItem Item = s_ListBox.DoNextItem(pId: &CurrentClient); |
287 | |
288 | Count++; |
289 | |
290 | if(!Item.m_Visible) |
291 | continue; |
292 | |
293 | CUIRect Row = Item.m_Rect; |
294 | if(Count % 2 == 1) |
295 | Row.Draw(Color: ColorRGBA(1.0f, 1.0f, 1.0f, 0.25f), Corners: IGraphics::CORNER_ALL, Rounding: 5.0f); |
296 | Row.VSplitRight(Cut: s_ListBox.ScrollbarWidthMax() - s_ListBox.ScrollbarWidth(), pLeft: &Row, pRight: nullptr); |
297 | Row.VSplitRight(Cut: 300.0f, pLeft: &Player, pRight: &Row); |
298 | |
299 | // player info |
300 | Player.VSplitLeft(Cut: 28.0f, pLeft: &Button, pRight: &Player); |
301 | |
302 | CTeeRenderInfo TeeInfo = CurrentClient.m_RenderInfo; |
303 | TeeInfo.m_Size = Button.h; |
304 | |
305 | const CAnimState *pIdleState = CAnimState::GetIdle(); |
306 | vec2 OffsetToMid; |
307 | CRenderTools::GetRenderTeeOffsetToRenderedTee(pAnim: pIdleState, pInfo: &TeeInfo, TeeOffsetToMid&: OffsetToMid); |
308 | vec2 TeeRenderPos(Button.x + Button.h / 2, Button.y + Button.h / 2 + OffsetToMid.y); |
309 | |
310 | RenderTools()->RenderTee(pAnim: pIdleState, pInfo: &TeeInfo, Emote: EMOTE_NORMAL, Dir: vec2(1.0f, 0.0f), Pos: TeeRenderPos); |
311 | |
312 | Player.HSplitTop(Cut: 1.5f, pTop: nullptr, pBottom: &Player); |
313 | Player.VSplitMid(pLeft: &Player, pRight: &Button); |
314 | Row.VSplitRight(Cut: 210.0f, pLeft: &Button2, pRight: &Row); |
315 | |
316 | Ui()->DoLabel(pRect: &Player, pText: CurrentClient.m_aName, Size: 14.0f, Align: TEXTALIGN_ML); |
317 | Ui()->DoLabel(pRect: &Button, pText: CurrentClient.m_aClan, Size: 14.0f, Align: TEXTALIGN_ML); |
318 | |
319 | m_pClient->m_CountryFlags.Render(CountryCode: CurrentClient.m_Country, Color: ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f), |
320 | x: Button2.x, y: Button2.y + Button2.h / 2.0f - 0.75f * Button2.h / 2.0f, w: 1.5f * Button2.h, h: 0.75f * Button2.h); |
321 | |
322 | // ignore chat button |
323 | Row.HMargin(Cut: 2.0f, pOtherRect: &Row); |
324 | Row.VSplitLeft(Cut: Width, pLeft: &Button, pRight: &Row); |
325 | Button.VSplitLeft(Cut: (Width - Button.h) / 4.0f, pLeft: nullptr, pRight: &Button); |
326 | Button.VSplitLeft(Cut: Button.h, pLeft: &Button, pRight: nullptr); |
327 | if(g_Config.m_ClShowChatFriends && !CurrentClient.m_Friend) |
328 | DoButton_Toggle(pId: &s_aPlayerIds[Index][0], Checked: 1, pRect: &Button, Active: false); |
329 | else if(DoButton_Toggle(pId: &s_aPlayerIds[Index][0], Checked: CurrentClient.m_ChatIgnore, pRect: &Button, Active: true)) |
330 | CurrentClient.m_ChatIgnore ^= 1; |
331 | |
332 | // ignore emoticon button |
333 | Row.VSplitLeft(Cut: 30.0f, pLeft: nullptr, pRight: &Row); |
334 | Row.VSplitLeft(Cut: Width, pLeft: &Button, pRight: &Row); |
335 | Button.VSplitLeft(Cut: (Width - Button.h) / 4.0f, pLeft: nullptr, pRight: &Button); |
336 | Button.VSplitLeft(Cut: Button.h, pLeft: &Button, pRight: nullptr); |
337 | if(g_Config.m_ClShowChatFriends && !CurrentClient.m_Friend) |
338 | DoButton_Toggle(pId: &s_aPlayerIds[Index][1], Checked: 1, pRect: &Button, Active: false); |
339 | else if(DoButton_Toggle(pId: &s_aPlayerIds[Index][1], Checked: CurrentClient.m_EmoticonIgnore, pRect: &Button, Active: true)) |
340 | CurrentClient.m_EmoticonIgnore ^= 1; |
341 | |
342 | // friend button |
343 | Row.VSplitLeft(Cut: 10.0f, pLeft: nullptr, pRight: &Row); |
344 | Row.VSplitLeft(Cut: Width, pLeft: &Button, pRight: &Row); |
345 | Button.VSplitLeft(Cut: (Width - Button.h) / 4.0f, pLeft: nullptr, pRight: &Button); |
346 | Button.VSplitLeft(Cut: Button.h, pLeft: &Button, pRight: nullptr); |
347 | if(DoButton_Toggle(pId: &s_aPlayerIds[Index][2], Checked: CurrentClient.m_Friend, pRect: &Button, Active: true)) |
348 | { |
349 | if(CurrentClient.m_Friend) |
350 | m_pClient->Friends()->RemoveFriend(pName: CurrentClient.m_aName, pClan: CurrentClient.m_aClan); |
351 | else |
352 | m_pClient->Friends()->AddFriend(pName: CurrentClient.m_aName, pClan: CurrentClient.m_aClan); |
353 | |
354 | m_pClient->Client()->ServerBrowserUpdate(); |
355 | } |
356 | } |
357 | |
358 | s_ListBox.DoEnd(); |
359 | } |
360 | |
361 | void CMenus::(CUIRect MainView) |
362 | { |
363 | if(!m_pClient->m_Snap.m_pLocalInfo) |
364 | return; |
365 | |
366 | // fetch server info |
367 | CServerInfo CurrentServerInfo; |
368 | Client()->GetServerInfo(pServerInfo: &CurrentServerInfo); |
369 | |
370 | // render background |
371 | MainView.Draw(Color: ms_ColorTabbarActive, Corners: IGraphics::CORNER_B, Rounding: 10.0f); |
372 | |
373 | CUIRect View, ServerInfo, GameInfo, Motd; |
374 | |
375 | float x = 0.0f; |
376 | float y = 0.0f; |
377 | |
378 | char aBuf[1024]; |
379 | |
380 | // set view to use for all sub-modules |
381 | MainView.Margin(Cut: 10.0f, pOtherRect: &View); |
382 | |
383 | // serverinfo |
384 | View.HSplitTop(Cut: View.h / 2 - 5.0f, pTop: &ServerInfo, pBottom: &Motd); |
385 | ServerInfo.VSplitLeft(Cut: View.w / 2 - 5.0f, pLeft: &ServerInfo, pRight: &GameInfo); |
386 | ServerInfo.Draw(Color: ColorRGBA(1, 1, 1, 0.25f), Corners: IGraphics::CORNER_ALL, Rounding: 10.0f); |
387 | |
388 | ServerInfo.Margin(Cut: 5.0f, pOtherRect: &ServerInfo); |
389 | |
390 | x = 5.0f; |
391 | y = 0.0f; |
392 | |
393 | TextRender()->Text(x: ServerInfo.x + x, y: ServerInfo.y + y, Size: 32, pText: Localize(pStr: "Server info" ), LineWidth: -1.0f); |
394 | y += 32.0f + 5.0f; |
395 | |
396 | mem_zero(block: aBuf, size: sizeof(aBuf)); |
397 | str_format( |
398 | buffer: aBuf, |
399 | buffer_size: sizeof(aBuf), |
400 | format: "%s\n\n" |
401 | "%s: %s\n" |
402 | "%s: %d\n" |
403 | "%s: %s\n" |
404 | "%s: %s\n" , |
405 | CurrentServerInfo.m_aName, |
406 | Localize(pStr: "Address" ), CurrentServerInfo.m_aAddress, |
407 | Localize(pStr: "Ping" ), m_pClient->m_Snap.m_pLocalInfo->m_Latency, |
408 | Localize(pStr: "Version" ), CurrentServerInfo.m_aVersion, |
409 | Localize(pStr: "Password" ), CurrentServerInfo.m_Flags & 1 ? Localize(pStr: "Yes" ) : Localize(pStr: "No" )); |
410 | |
411 | TextRender()->Text(x: ServerInfo.x + x, y: ServerInfo.y + y, Size: 20, pText: aBuf, LineWidth: ServerInfo.w - 10.0f); |
412 | |
413 | // copy info button |
414 | { |
415 | CUIRect Button; |
416 | ServerInfo.HSplitBottom(Cut: 20.0f, pTop: &ServerInfo, pBottom: &Button); |
417 | Button.VSplitRight(Cut: 200.0f, pLeft: &ServerInfo, pRight: &Button); |
418 | static CButtonContainer s_CopyButton; |
419 | if(DoButton_Menu(pButtonContainer: &s_CopyButton, pText: Localize(pStr: "Copy info" ), Checked: 0, pRect: &Button)) |
420 | { |
421 | char aInfo[256]; |
422 | str_format( |
423 | buffer: aInfo, |
424 | buffer_size: sizeof(aInfo), |
425 | format: "%s\n" |
426 | "Address: ddnet://%s\n" |
427 | "My IGN: %s\n" , |
428 | CurrentServerInfo.m_aName, |
429 | CurrentServerInfo.m_aAddress, |
430 | Client()->PlayerName()); |
431 | Input()->SetClipboardText(aInfo); |
432 | } |
433 | } |
434 | |
435 | // favorite checkbox |
436 | { |
437 | CUIRect Button; |
438 | NETADDR ServerAddr = Client()->ServerAddress(); |
439 | TRISTATE IsFavorite = Favorites()->IsFavorite(pAddrs: &ServerAddr, NumAddrs: 1); |
440 | ServerInfo.HSplitBottom(Cut: 20.0f, pTop: &ServerInfo, pBottom: &Button); |
441 | static int s_AddFavButton = 0; |
442 | if(DoButton_CheckBox(pId: &s_AddFavButton, pText: Localize(pStr: "Favorite" ), Checked: IsFavorite != TRISTATE::NONE, pRect: &Button)) |
443 | { |
444 | if(IsFavorite != TRISTATE::NONE) |
445 | Favorites()->Remove(pAddrs: &ServerAddr, NumAddrs: 1); |
446 | else |
447 | Favorites()->Add(pAddrs: &ServerAddr, NumAddrs: 1); |
448 | } |
449 | } |
450 | |
451 | // gameinfo |
452 | GameInfo.VSplitLeft(Cut: 10.0f, pLeft: 0x0, pRight: &GameInfo); |
453 | GameInfo.Draw(Color: ColorRGBA(1, 1, 1, 0.25f), Corners: IGraphics::CORNER_ALL, Rounding: 10.0f); |
454 | |
455 | GameInfo.Margin(Cut: 5.0f, pOtherRect: &GameInfo); |
456 | |
457 | x = 5.0f; |
458 | y = 0.0f; |
459 | |
460 | TextRender()->Text(x: GameInfo.x + x, y: GameInfo.y + y, Size: 32, pText: Localize(pStr: "Game info" ), LineWidth: -1.0f); |
461 | y += 32.0f + 5.0f; |
462 | |
463 | if(m_pClient->m_Snap.m_pGameInfoObj) |
464 | { |
465 | mem_zero(block: aBuf, size: sizeof(aBuf)); |
466 | str_format( |
467 | buffer: aBuf, |
468 | buffer_size: sizeof(aBuf), |
469 | format: "\n\n" |
470 | "%s: %s\n" |
471 | "%s: %s\n" |
472 | "%s: %d\n" |
473 | "%s: %d\n" |
474 | "\n" |
475 | "%s: %d/%d\n" , |
476 | Localize(pStr: "Game type" ), CurrentServerInfo.m_aGameType, |
477 | Localize(pStr: "Map" ), CurrentServerInfo.m_aMap, |
478 | Localize(pStr: "Score limit" ), m_pClient->m_Snap.m_pGameInfoObj->m_ScoreLimit, |
479 | Localize(pStr: "Time limit" ), m_pClient->m_Snap.m_pGameInfoObj->m_TimeLimit, |
480 | Localize(pStr: "Players" ), m_pClient->m_Snap.m_NumPlayers, CurrentServerInfo.m_MaxClients); |
481 | TextRender()->Text(x: GameInfo.x + x, y: GameInfo.y + y, Size: 20, pText: aBuf, LineWidth: GameInfo.w - 10.0f); |
482 | } |
483 | |
484 | RenderServerInfoMotd(Motd); |
485 | } |
486 | |
487 | void CMenus::(CUIRect Motd) |
488 | { |
489 | const float MotdFontSize = 16.0f; |
490 | Motd.HSplitTop(Cut: 10.0f, pTop: nullptr, pBottom: &Motd); |
491 | Motd.Draw(Color: ColorRGBA(1, 1, 1, 0.25f), Corners: IGraphics::CORNER_ALL, Rounding: 10.0f); |
492 | Motd.HMargin(Cut: 5.0f, pOtherRect: &Motd); |
493 | Motd.VMargin(Cut: 10.0f, pOtherRect: &Motd); |
494 | |
495 | CUIRect ; |
496 | Motd.HSplitTop(Cut: 2.0f * MotdFontSize, pTop: &MotdHeader, pBottom: &Motd); |
497 | Motd.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &Motd); |
498 | TextRender()->Text(x: MotdHeader.x, y: MotdHeader.y, Size: 2.0f * MotdFontSize, pText: Localize(pStr: "MOTD" ), LineWidth: -1.0f); |
499 | |
500 | if(!m_pClient->m_Motd.ServerMotd()[0]) |
501 | return; |
502 | |
503 | static CScrollRegion s_ScrollRegion; |
504 | vec2 ScrollOffset(0.0f, 0.0f); |
505 | CScrollRegionParams ScrollParams; |
506 | ScrollParams.m_ScrollUnit = 5 * MotdFontSize; |
507 | s_ScrollRegion.Begin(pClipRect: &Motd, pOutOffset: &ScrollOffset, pParams: &ScrollParams); |
508 | Motd.y += ScrollOffset.y; |
509 | |
510 | static float s_MotdHeight = 0.0f; |
511 | static int64_t s_MotdLastUpdateTime = -1; |
512 | if(!m_MotdTextContainerIndex.Valid() || s_MotdLastUpdateTime == -1 || s_MotdLastUpdateTime != m_pClient->m_Motd.ServerMotdUpdateTime()) |
513 | { |
514 | CTextCursor Cursor; |
515 | TextRender()->SetCursor(pCursor: &Cursor, x: 0.0f, y: 0.0f, FontSize: MotdFontSize, Flags: TEXTFLAG_RENDER); |
516 | Cursor.m_LineWidth = Motd.w; |
517 | TextRender()->RecreateTextContainer(TextContainerIndex&: m_MotdTextContainerIndex, pCursor: &Cursor, pText: m_pClient->m_Motd.ServerMotd()); |
518 | s_MotdHeight = Cursor.Height(); |
519 | s_MotdLastUpdateTime = m_pClient->m_Motd.ServerMotdUpdateTime(); |
520 | } |
521 | |
522 | CUIRect MotdTextArea; |
523 | Motd.HSplitTop(Cut: s_MotdHeight, pTop: &MotdTextArea, pBottom: &Motd); |
524 | s_ScrollRegion.AddRect(Rect: MotdTextArea); |
525 | |
526 | if(m_MotdTextContainerIndex.Valid()) |
527 | TextRender()->RenderTextContainer(TextContainerIndex: m_MotdTextContainerIndex, TextColor: TextRender()->DefaultTextColor(), TextOutlineColor: TextRender()->DefaultTextOutlineColor(), X: MotdTextArea.x, Y: MotdTextArea.y); |
528 | |
529 | s_ScrollRegion.End(); |
530 | } |
531 | |
532 | bool CMenus::(CUIRect MainView) |
533 | { |
534 | CUIRect List = MainView; |
535 | int Total = m_pClient->m_Voting.m_NumVoteOptions; |
536 | int NumVoteOptions = 0; |
537 | int aIndices[MAX_VOTE_OPTIONS]; |
538 | static int s_CurVoteOption = 0; |
539 | int TotalShown = 0; |
540 | |
541 | for(CVoteOptionClient *pOption = m_pClient->m_Voting.m_pFirst; pOption; pOption = pOption->m_pNext) |
542 | { |
543 | if(!m_FilterInput.IsEmpty() && !str_utf8_find_nocase(haystack: pOption->m_aDescription, needle: m_FilterInput.GetString())) |
544 | continue; |
545 | TotalShown++; |
546 | } |
547 | |
548 | static CListBox s_ListBox; |
549 | s_ListBox.DoStart(RowHeight: 19.0f, NumItems: TotalShown, ItemsPerRow: 1, RowsPerScroll: 3, SelectedIndex: s_CurVoteOption, pRect: &List); |
550 | |
551 | int i = -1; |
552 | for(CVoteOptionClient *pOption = m_pClient->m_Voting.m_pFirst; pOption; pOption = pOption->m_pNext) |
553 | { |
554 | i++; |
555 | if(!m_FilterInput.IsEmpty() && !str_utf8_find_nocase(haystack: pOption->m_aDescription, needle: m_FilterInput.GetString())) |
556 | continue; |
557 | |
558 | if(NumVoteOptions < Total) |
559 | aIndices[NumVoteOptions] = i; |
560 | NumVoteOptions++; |
561 | |
562 | const CListboxItem Item = s_ListBox.DoNextItem(pId: pOption); |
563 | if(!Item.m_Visible) |
564 | continue; |
565 | |
566 | CUIRect Label; |
567 | Item.m_Rect.VMargin(Cut: 2.0f, pOtherRect: &Label); |
568 | Ui()->DoLabel(pRect: &Label, pText: pOption->m_aDescription, Size: 13.0f, Align: TEXTALIGN_ML); |
569 | } |
570 | |
571 | s_CurVoteOption = s_ListBox.DoEnd(); |
572 | if(s_CurVoteOption < Total) |
573 | m_CallvoteSelectedOption = aIndices[s_CurVoteOption]; |
574 | return s_ListBox.WasItemActivated(); |
575 | } |
576 | |
577 | bool CMenus::(CUIRect MainView, bool FilterSpectators) |
578 | { |
579 | int NumOptions = 0; |
580 | int Selected = -1; |
581 | int aPlayerIds[MAX_CLIENTS]; |
582 | for(const auto &pInfoByName : m_pClient->m_Snap.m_apInfoByName) |
583 | { |
584 | if(!pInfoByName) |
585 | continue; |
586 | |
587 | int Index = pInfoByName->m_ClientId; |
588 | if(Index == m_pClient->m_Snap.m_LocalClientId || (FilterSpectators && pInfoByName->m_Team == TEAM_SPECTATORS)) |
589 | continue; |
590 | |
591 | if(!str_utf8_find_nocase(haystack: m_pClient->m_aClients[Index].m_aName, needle: m_FilterInput.GetString())) |
592 | continue; |
593 | |
594 | if(m_CallvoteSelectedPlayer == Index) |
595 | Selected = NumOptions; |
596 | aPlayerIds[NumOptions] = Index; |
597 | NumOptions++; |
598 | } |
599 | |
600 | static CListBox s_ListBox; |
601 | s_ListBox.DoStart(RowHeight: 24.0f, NumItems: NumOptions, ItemsPerRow: 1, RowsPerScroll: 3, SelectedIndex: Selected, pRect: &MainView); |
602 | |
603 | for(int i = 0; i < NumOptions; i++) |
604 | { |
605 | const CListboxItem Item = s_ListBox.DoNextItem(pId: &aPlayerIds[i]); |
606 | if(!Item.m_Visible) |
607 | continue; |
608 | |
609 | CUIRect TeeRect, Label; |
610 | Item.m_Rect.VSplitLeft(Cut: Item.m_Rect.h, pLeft: &TeeRect, pRight: &Label); |
611 | |
612 | CTeeRenderInfo TeeInfo = m_pClient->m_aClients[aPlayerIds[i]].m_RenderInfo; |
613 | TeeInfo.m_Size = TeeRect.h; |
614 | |
615 | const CAnimState *pIdleState = CAnimState::GetIdle(); |
616 | vec2 OffsetToMid; |
617 | CRenderTools::GetRenderTeeOffsetToRenderedTee(pAnim: pIdleState, pInfo: &TeeInfo, TeeOffsetToMid&: OffsetToMid); |
618 | vec2 TeeRenderPos(TeeRect.x + TeeInfo.m_Size / 2, TeeRect.y + TeeInfo.m_Size / 2 + OffsetToMid.y); |
619 | |
620 | RenderTools()->RenderTee(pAnim: pIdleState, pInfo: &TeeInfo, Emote: EMOTE_NORMAL, Dir: vec2(1.0f, 0.0f), Pos: TeeRenderPos); |
621 | |
622 | Ui()->DoLabel(pRect: &Label, pText: m_pClient->m_aClients[aPlayerIds[i]].m_aName, Size: 16.0f, Align: TEXTALIGN_ML); |
623 | } |
624 | |
625 | Selected = s_ListBox.DoEnd(); |
626 | m_CallvoteSelectedPlayer = Selected != -1 ? aPlayerIds[Selected] : -1; |
627 | return s_ListBox.WasItemActivated(); |
628 | } |
629 | |
630 | void CMenus::(CUIRect MainView) |
631 | { |
632 | enum class EServerControlTab |
633 | { |
634 | SETTINGS, |
635 | KICKVOTE, |
636 | SPECVOTE, |
637 | }; |
638 | static EServerControlTab s_ControlPage = EServerControlTab::SETTINGS; |
639 | |
640 | // render background |
641 | CUIRect Bottom, RconExtension, TabBar, Button; |
642 | MainView.HSplitTop(Cut: 20.0f, pTop: &Bottom, pBottom: &MainView); |
643 | Bottom.Draw(Color: ms_ColorTabbarActive, Corners: IGraphics::CORNER_NONE, Rounding: 0.0f); |
644 | MainView.HSplitTop(Cut: 20.0f, pTop: &TabBar, pBottom: &MainView); |
645 | MainView.Draw(Color: ms_ColorTabbarActive, Corners: IGraphics::CORNER_B, Rounding: 10.0f); |
646 | MainView.Margin(Cut: 10.0f, pOtherRect: &MainView); |
647 | |
648 | if(Client()->RconAuthed()) |
649 | MainView.HSplitBottom(Cut: 90.0f, pTop: &MainView, pBottom: &RconExtension); |
650 | |
651 | // tab bar |
652 | TabBar.VSplitLeft(Cut: TabBar.w / 3, pLeft: &Button, pRight: &TabBar); |
653 | static CButtonContainer s_Button0; |
654 | if(DoButton_MenuTab(pButtonContainer: &s_Button0, pText: Localize(pStr: "Change settings" ), Checked: s_ControlPage == EServerControlTab::SETTINGS, pRect: &Button, Corners: IGraphics::CORNER_NONE)) |
655 | s_ControlPage = EServerControlTab::SETTINGS; |
656 | |
657 | TabBar.VSplitMid(pLeft: &Button, pRight: &TabBar); |
658 | static CButtonContainer s_Button1; |
659 | if(DoButton_MenuTab(pButtonContainer: &s_Button1, pText: Localize(pStr: "Kick player" ), Checked: s_ControlPage == EServerControlTab::KICKVOTE, pRect: &Button, Corners: IGraphics::CORNER_NONE)) |
660 | s_ControlPage = EServerControlTab::KICKVOTE; |
661 | |
662 | static CButtonContainer s_Button2; |
663 | if(DoButton_MenuTab(pButtonContainer: &s_Button2, pText: Localize(pStr: "Move player to spectators" ), Checked: s_ControlPage == EServerControlTab::SPECVOTE, pRect: &TabBar, Corners: IGraphics::CORNER_NONE)) |
664 | s_ControlPage = EServerControlTab::SPECVOTE; |
665 | |
666 | // render page |
667 | MainView.HSplitBottom(Cut: ms_ButtonHeight + 5 * 2, pTop: &MainView, pBottom: &Bottom); |
668 | Bottom.HMargin(Cut: 5.0f, pOtherRect: &Bottom); |
669 | Bottom.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &Bottom); |
670 | |
671 | bool Call = false; |
672 | if(s_ControlPage == EServerControlTab::SETTINGS) |
673 | Call = RenderServerControlServer(MainView); |
674 | else if(s_ControlPage == EServerControlTab::KICKVOTE) |
675 | Call = RenderServerControlKick(MainView, FilterSpectators: false); |
676 | else if(s_ControlPage == EServerControlTab::SPECVOTE) |
677 | Call = RenderServerControlKick(MainView, FilterSpectators: true); |
678 | |
679 | // vote menu |
680 | |
681 | // render quick search |
682 | CUIRect QuickSearch; |
683 | Bottom.VSplitLeft(Cut: 5.0f, pLeft: 0, pRight: &Bottom); |
684 | Bottom.VSplitLeft(Cut: 250.0f, pLeft: &QuickSearch, pRight: &Bottom); |
685 | TextRender()->SetFontPreset(EFontPreset::ICON_FONT); |
686 | 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); |
687 | |
688 | Ui()->DoLabel(pRect: &QuickSearch, pText: FONT_ICON_MAGNIFYING_GLASS, Size: 14.0f, Align: TEXTALIGN_ML); |
689 | float SearchWidth = TextRender()->TextWidth(Size: 14.0f, pText: FONT_ICON_MAGNIFYING_GLASS, StrLength: -1, LineWidth: -1.0f); |
690 | TextRender()->SetRenderFlags(0); |
691 | TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); |
692 | QuickSearch.VSplitLeft(Cut: SearchWidth, pLeft: 0, pRight: &QuickSearch); |
693 | QuickSearch.VSplitLeft(Cut: 5.0f, pLeft: 0, pRight: &QuickSearch); |
694 | |
695 | if(m_ControlPageOpening || (Input()->KeyPress(Key: KEY_F) && Input()->ModifierIsPressed())) |
696 | { |
697 | Ui()->SetActiveItem(&m_FilterInput); |
698 | m_ControlPageOpening = false; |
699 | m_FilterInput.SelectAll(); |
700 | } |
701 | m_FilterInput.SetEmptyText(Localize(pStr: "Search" )); |
702 | Ui()->DoClearableEditBox(pLineInput: &m_FilterInput, pRect: &QuickSearch, FontSize: 14.0f); |
703 | |
704 | // call vote |
705 | Bottom.VSplitRight(Cut: 10.0f, pLeft: &Bottom, pRight: 0); |
706 | Bottom.VSplitRight(Cut: 120.0f, pLeft: &Bottom, pRight: &Button); |
707 | |
708 | static CButtonContainer s_CallVoteButton; |
709 | if(DoButton_Menu(pButtonContainer: &s_CallVoteButton, pText: Localize(pStr: "Call vote" ), Checked: 0, pRect: &Button) || Call) |
710 | { |
711 | if(s_ControlPage == EServerControlTab::SETTINGS) |
712 | { |
713 | m_pClient->m_Voting.CallvoteOption(OptionId: m_CallvoteSelectedOption, pReason: m_CallvoteReasonInput.GetString()); |
714 | if(g_Config.m_UiCloseWindowAfterChangingSetting) |
715 | SetActive(false); |
716 | } |
717 | else if(s_ControlPage == EServerControlTab::KICKVOTE) |
718 | { |
719 | if(m_CallvoteSelectedPlayer >= 0 && m_CallvoteSelectedPlayer < MAX_CLIENTS && |
720 | m_pClient->m_Snap.m_apPlayerInfos[m_CallvoteSelectedPlayer]) |
721 | { |
722 | m_pClient->m_Voting.CallvoteKick(ClientId: m_CallvoteSelectedPlayer, pReason: m_CallvoteReasonInput.GetString()); |
723 | SetActive(false); |
724 | } |
725 | } |
726 | else if(s_ControlPage == EServerControlTab::SPECVOTE) |
727 | { |
728 | if(m_CallvoteSelectedPlayer >= 0 && m_CallvoteSelectedPlayer < MAX_CLIENTS && |
729 | m_pClient->m_Snap.m_apPlayerInfos[m_CallvoteSelectedPlayer]) |
730 | { |
731 | m_pClient->m_Voting.CallvoteSpectate(ClientId: m_CallvoteSelectedPlayer, pReason: m_CallvoteReasonInput.GetString()); |
732 | SetActive(false); |
733 | } |
734 | } |
735 | m_CallvoteReasonInput.Clear(); |
736 | } |
737 | |
738 | // render kick reason |
739 | CUIRect Reason; |
740 | Bottom.VSplitRight(Cut: 20.0f, pLeft: &Bottom, pRight: 0); |
741 | Bottom.VSplitRight(Cut: 200.0f, pLeft: &Bottom, pRight: &Reason); |
742 | const char *pLabel = Localize(pStr: "Reason:" ); |
743 | Ui()->DoLabel(pRect: &Reason, pText: pLabel, Size: 14.0f, Align: TEXTALIGN_ML); |
744 | float w = TextRender()->TextWidth(Size: 14.0f, pText: pLabel, StrLength: -1, LineWidth: -1.0f); |
745 | Reason.VSplitLeft(Cut: w + 10.0f, pLeft: 0, pRight: &Reason); |
746 | if(Input()->KeyPress(Key: KEY_R) && Input()->ModifierIsPressed()) |
747 | { |
748 | Ui()->SetActiveItem(&m_CallvoteReasonInput); |
749 | m_CallvoteReasonInput.SelectAll(); |
750 | } |
751 | Ui()->DoEditBox(pLineInput: &m_CallvoteReasonInput, pRect: &Reason, FontSize: 14.0f); |
752 | |
753 | // vote option loading indicator |
754 | if(s_ControlPage == EServerControlTab::SETTINGS && m_pClient->m_Voting.IsReceivingOptions()) |
755 | { |
756 | CUIRect Spinner, LoadingLabel; |
757 | Bottom.VSplitLeft(Cut: 20.0f, pLeft: nullptr, pRight: &Bottom); |
758 | Bottom.VSplitLeft(Cut: 16.0f, pLeft: &Spinner, pRight: &Bottom); |
759 | Bottom.VSplitLeft(Cut: 5.0f, pLeft: nullptr, pRight: &Bottom); |
760 | Bottom.VSplitRight(Cut: 10.0f, pLeft: &LoadingLabel, pRight: nullptr); |
761 | Ui()->RenderProgressSpinner(Center: Spinner.Center(), OuterRadius: 8.0f); |
762 | Ui()->DoLabel(pRect: &LoadingLabel, pText: Localize(pStr: "Loading…" ), Size: 14.0f, Align: TEXTALIGN_ML); |
763 | } |
764 | |
765 | // extended features (only available when authed in rcon) |
766 | if(Client()->RconAuthed()) |
767 | { |
768 | // background |
769 | RconExtension.HSplitTop(Cut: 10.0f, pTop: 0, pBottom: &RconExtension); |
770 | RconExtension.HSplitTop(Cut: 20.0f, pTop: &Bottom, pBottom: &RconExtension); |
771 | RconExtension.HSplitTop(Cut: 5.0f, pTop: 0, pBottom: &RconExtension); |
772 | |
773 | // force vote |
774 | Bottom.VSplitLeft(Cut: 5.0f, pLeft: 0, pRight: &Bottom); |
775 | Bottom.VSplitLeft(Cut: 120.0f, pLeft: &Button, pRight: &Bottom); |
776 | |
777 | static CButtonContainer s_ForceVoteButton; |
778 | if(DoButton_Menu(pButtonContainer: &s_ForceVoteButton, pText: Localize(pStr: "Force vote" ), Checked: 0, pRect: &Button)) |
779 | { |
780 | if(s_ControlPage == EServerControlTab::SETTINGS) |
781 | { |
782 | m_pClient->m_Voting.CallvoteOption(OptionId: m_CallvoteSelectedOption, pReason: m_CallvoteReasonInput.GetString(), ForceVote: true); |
783 | } |
784 | else if(s_ControlPage == EServerControlTab::KICKVOTE) |
785 | { |
786 | if(m_CallvoteSelectedPlayer >= 0 && m_CallvoteSelectedPlayer < MAX_CLIENTS && |
787 | m_pClient->m_Snap.m_apPlayerInfos[m_CallvoteSelectedPlayer]) |
788 | { |
789 | m_pClient->m_Voting.CallvoteKick(ClientId: m_CallvoteSelectedPlayer, pReason: m_CallvoteReasonInput.GetString(), ForceVote: true); |
790 | SetActive(false); |
791 | } |
792 | } |
793 | else if(s_ControlPage == EServerControlTab::SPECVOTE) |
794 | { |
795 | if(m_CallvoteSelectedPlayer >= 0 && m_CallvoteSelectedPlayer < MAX_CLIENTS && |
796 | m_pClient->m_Snap.m_apPlayerInfos[m_CallvoteSelectedPlayer]) |
797 | { |
798 | m_pClient->m_Voting.CallvoteSpectate(ClientId: m_CallvoteSelectedPlayer, pReason: m_CallvoteReasonInput.GetString(), ForceVote: true); |
799 | SetActive(false); |
800 | } |
801 | } |
802 | m_CallvoteReasonInput.Clear(); |
803 | } |
804 | |
805 | if(s_ControlPage == EServerControlTab::SETTINGS) |
806 | { |
807 | // remove vote |
808 | Bottom.VSplitRight(Cut: 10.0f, pLeft: &Bottom, pRight: 0); |
809 | Bottom.VSplitRight(Cut: 120.0f, pLeft: 0, pRight: &Button); |
810 | static CButtonContainer s_RemoveVoteButton; |
811 | if(DoButton_Menu(pButtonContainer: &s_RemoveVoteButton, pText: Localize(pStr: "Remove" ), Checked: 0, pRect: &Button)) |
812 | m_pClient->m_Voting.RemovevoteOption(OptionId: m_CallvoteSelectedOption); |
813 | |
814 | // add vote |
815 | RconExtension.HSplitTop(Cut: 20.0f, pTop: &Bottom, pBottom: &RconExtension); |
816 | Bottom.VSplitLeft(Cut: 5.0f, pLeft: 0, pRight: &Bottom); |
817 | Bottom.VSplitLeft(Cut: 250.0f, pLeft: &Button, pRight: &Bottom); |
818 | Ui()->DoLabel(pRect: &Button, pText: Localize(pStr: "Vote description:" ), Size: 14.0f, Align: TEXTALIGN_ML); |
819 | |
820 | Bottom.VSplitLeft(Cut: 20.0f, pLeft: 0, pRight: &Button); |
821 | Ui()->DoLabel(pRect: &Button, pText: Localize(pStr: "Vote command:" ), Size: 14.0f, Align: TEXTALIGN_ML); |
822 | |
823 | static CLineInputBuffered<VOTE_DESC_LENGTH> s_VoteDescriptionInput; |
824 | static CLineInputBuffered<VOTE_CMD_LENGTH> s_VoteCommandInput; |
825 | RconExtension.HSplitTop(Cut: 20.0f, pTop: &Bottom, pBottom: &RconExtension); |
826 | Bottom.VSplitRight(Cut: 10.0f, pLeft: &Bottom, pRight: 0); |
827 | Bottom.VSplitRight(Cut: 120.0f, pLeft: &Bottom, pRight: &Button); |
828 | static CButtonContainer s_AddVoteButton; |
829 | if(DoButton_Menu(pButtonContainer: &s_AddVoteButton, pText: Localize(pStr: "Add" ), Checked: 0, pRect: &Button)) |
830 | if(!s_VoteDescriptionInput.IsEmpty() && !s_VoteCommandInput.IsEmpty()) |
831 | m_pClient->m_Voting.AddvoteOption(pDescription: s_VoteDescriptionInput.GetString(), pCommand: s_VoteCommandInput.GetString()); |
832 | |
833 | Bottom.VSplitLeft(Cut: 5.0f, pLeft: 0, pRight: &Bottom); |
834 | Bottom.VSplitLeft(Cut: 250.0f, pLeft: &Button, pRight: &Bottom); |
835 | Ui()->DoEditBox(pLineInput: &s_VoteDescriptionInput, pRect: &Button, FontSize: 14.0f); |
836 | |
837 | Bottom.VMargin(Cut: 20.0f, pOtherRect: &Button); |
838 | Ui()->DoEditBox(pLineInput: &s_VoteCommandInput, pRect: &Button, FontSize: 14.0f); |
839 | } |
840 | } |
841 | } |
842 | |
843 | void CMenus::(CUIRect MainView) |
844 | { |
845 | MainView.Draw(Color: ms_ColorTabbarActive, Corners: IGraphics::CORNER_B, Rounding: 10.0f); |
846 | |
847 | CUIRect TabBar, Button; |
848 | MainView.HSplitTop(Cut: 24.0f, pTop: &TabBar, pBottom: &MainView); |
849 | |
850 | int NewPage = g_Config.m_UiPage; |
851 | |
852 | TextRender()->SetFontPreset(EFontPreset::ICON_FONT); |
853 | 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); |
854 | |
855 | TabBar.VSplitLeft(Cut: 75.0f, pLeft: &Button, pRight: &TabBar); |
856 | static CButtonContainer s_InternetButton; |
857 | if(DoButton_MenuTab(pButtonContainer: &s_InternetButton, pText: FONT_ICON_EARTH_AMERICAS, Checked: g_Config.m_UiPage == PAGE_INTERNET, pRect: &Button, Corners: IGraphics::CORNER_NONE)) |
858 | { |
859 | NewPage = PAGE_INTERNET; |
860 | } |
861 | GameClient()->m_Tooltips.DoToolTip(pId: &s_InternetButton, pNearRect: &Button, pText: Localize(pStr: "Internet" )); |
862 | |
863 | TabBar.VSplitLeft(Cut: 75.0f, pLeft: &Button, pRight: &TabBar); |
864 | static CButtonContainer s_LanButton; |
865 | if(DoButton_MenuTab(pButtonContainer: &s_LanButton, pText: FONT_ICON_NETWORK_WIRED, Checked: g_Config.m_UiPage == PAGE_LAN, pRect: &Button, Corners: IGraphics::CORNER_NONE)) |
866 | { |
867 | NewPage = PAGE_LAN; |
868 | } |
869 | GameClient()->m_Tooltips.DoToolTip(pId: &s_LanButton, pNearRect: &Button, pText: Localize(pStr: "LAN" )); |
870 | |
871 | TabBar.VSplitLeft(Cut: 75.0f, pLeft: &Button, pRight: &TabBar); |
872 | static CButtonContainer s_FavoritesButton; |
873 | if(DoButton_MenuTab(pButtonContainer: &s_FavoritesButton, pText: FONT_ICON_STAR, Checked: g_Config.m_UiPage == PAGE_FAVORITES, pRect: &Button, Corners: IGraphics::CORNER_NONE)) |
874 | { |
875 | NewPage = PAGE_FAVORITES; |
876 | } |
877 | GameClient()->m_Tooltips.DoToolTip(pId: &s_FavoritesButton, pNearRect: &Button, pText: Localize(pStr: "Favorites" )); |
878 | |
879 | size_t = 0; |
880 | static CButtonContainer [5]; |
881 | static_assert(std::size(s_aFavoriteCommunityButtons) == (size_t)PAGE_FAVORITE_COMMUNITY_5 - PAGE_FAVORITE_COMMUNITY_1 + 1); |
882 | for(const CCommunity * : ServerBrowser()->FavoriteCommunities()) |
883 | { |
884 | TabBar.VSplitLeft(Cut: 75.0f, pLeft: &Button, pRight: &TabBar); |
885 | const int Page = PAGE_FAVORITE_COMMUNITY_1 + FavoriteCommunityIndex; |
886 | if(DoButton_MenuTab(pButtonContainer: &s_aFavoriteCommunityButtons[FavoriteCommunityIndex], pText: FONT_ICON_ELLIPSIS, Checked: g_Config.m_UiPage == Page, pRect: &Button, Corners: IGraphics::CORNER_NONE, pAnimator: nullptr, pDefaultColor: nullptr, pActiveColor: nullptr, pHoverColor: nullptr, EdgeRounding: 10.0f, pCommunityIcon: FindCommunityIcon(pCommunityId: pCommunity->Id()))) |
887 | { |
888 | NewPage = Page; |
889 | } |
890 | GameClient()->m_Tooltips.DoToolTip(pId: &s_aFavoriteCommunityButtons[FavoriteCommunityIndex], pNearRect: &Button, pText: pCommunity->Name()); |
891 | |
892 | ++FavoriteCommunityIndex; |
893 | if(FavoriteCommunityIndex >= std::size(s_aFavoriteCommunityButtons)) |
894 | break; |
895 | } |
896 | |
897 | TextRender()->SetRenderFlags(0); |
898 | TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); |
899 | |
900 | if(NewPage != g_Config.m_UiPage) |
901 | { |
902 | SetMenuPage(NewPage); |
903 | } |
904 | |
905 | RenderServerbrowser(MainView); |
906 | } |
907 | |
908 | // ghost stuff |
909 | int CMenus::(const CFsFileInfo *pInfo, int IsDir, int StorageType, void *pUser) |
910 | { |
911 | CMenus *pSelf = (CMenus *)pUser; |
912 | const char *pMap = pSelf->Client()->GetCurrentMap(); |
913 | if(IsDir || !str_endswith(str: pInfo->m_pName, suffix: ".gho" ) || !str_startswith(str: pInfo->m_pName, prefix: pMap)) |
914 | return 0; |
915 | |
916 | char aFilename[IO_MAX_PATH_LENGTH]; |
917 | str_format(buffer: aFilename, buffer_size: sizeof(aFilename), format: "%s/%s" , pSelf->m_pClient->m_Ghost.GetGhostDir(), pInfo->m_pName); |
918 | |
919 | CGhostInfo Info; |
920 | if(!pSelf->m_pClient->m_Ghost.GhostLoader()->GetGhostInfo(pFilename: aFilename, pInfo: &Info, pMap, MapSha256: pSelf->Client()->GetCurrentMapSha256(), MapCrc: pSelf->Client()->GetCurrentMapCrc())) |
921 | return 0; |
922 | |
923 | CGhostItem Item; |
924 | str_copy(dst&: Item.m_aFilename, src: aFilename); |
925 | str_copy(dst&: Item.m_aPlayer, src: Info.m_aOwner); |
926 | Item.m_Date = pInfo->m_TimeModified; |
927 | Item.m_Time = Info.m_Time; |
928 | if(Item.m_Time > 0) |
929 | pSelf->m_vGhosts.push_back(x: Item); |
930 | |
931 | if(time_get_nanoseconds() - pSelf->m_GhostPopulateStartTime > 500ms) |
932 | { |
933 | pSelf->RenderLoading(pCaption: Localize(pStr: "Loading ghost files" ), pContent: "" , IncreaseCounter: 0, RenderLoadingBar: false); |
934 | } |
935 | |
936 | return 0; |
937 | } |
938 | |
939 | void CMenus::() |
940 | { |
941 | m_vGhosts.clear(); |
942 | m_GhostPopulateStartTime = time_get_nanoseconds(); |
943 | Storage()->ListDirectoryInfo(Type: IStorage::TYPE_ALL, pPath: m_pClient->m_Ghost.GetGhostDir(), pfnCallback: GhostlistFetchCallback, pUser: this); |
944 | std::sort(first: m_vGhosts.begin(), last: m_vGhosts.end()); |
945 | |
946 | CGhostItem *pOwnGhost = 0; |
947 | for(auto &Ghost : m_vGhosts) |
948 | { |
949 | Ghost.m_Failed = false; |
950 | if(str_comp(a: Ghost.m_aPlayer, b: Client()->PlayerName()) == 0 && (!pOwnGhost || Ghost < *pOwnGhost)) |
951 | pOwnGhost = &Ghost; |
952 | } |
953 | |
954 | if(pOwnGhost) |
955 | { |
956 | pOwnGhost->m_Own = true; |
957 | pOwnGhost->m_Slot = m_pClient->m_Ghost.Load(pFilename: pOwnGhost->m_aFilename); |
958 | } |
959 | } |
960 | |
961 | CMenus::CGhostItem *CMenus::() |
962 | { |
963 | for(auto &Ghost : m_vGhosts) |
964 | if(Ghost.m_Own) |
965 | return &Ghost; |
966 | return nullptr; |
967 | } |
968 | |
969 | void CMenus::(CGhostItem Item) |
970 | { |
971 | int Own = -1; |
972 | for(size_t i = 0; i < m_vGhosts.size(); i++) |
973 | if(m_vGhosts[i].m_Own) |
974 | Own = i; |
975 | |
976 | if(Own == -1) |
977 | { |
978 | Item.m_Own = true; |
979 | } |
980 | else if(g_Config.m_ClRaceGhostSaveBest && (Item.HasFile() || !m_vGhosts[Own].HasFile())) |
981 | { |
982 | Item.m_Own = true; |
983 | DeleteGhostItem(Index: Own); |
984 | } |
985 | else if(m_vGhosts[Own].m_Time > Item.m_Time) |
986 | { |
987 | Item.m_Own = true; |
988 | m_vGhosts[Own].m_Own = false; |
989 | m_vGhosts[Own].m_Slot = -1; |
990 | } |
991 | else |
992 | { |
993 | Item.m_Own = false; |
994 | Item.m_Slot = -1; |
995 | } |
996 | |
997 | Item.m_Date = std::time(timer: 0); |
998 | Item.m_Failed = false; |
999 | m_vGhosts.insert(position: std::lower_bound(first: m_vGhosts.begin(), last: m_vGhosts.end(), val: Item), x: Item); |
1000 | } |
1001 | |
1002 | void CMenus::(int Index) |
1003 | { |
1004 | if(m_vGhosts[Index].HasFile()) |
1005 | Storage()->RemoveFile(pFilename: m_vGhosts[Index].m_aFilename, Type: IStorage::TYPE_SAVE); |
1006 | m_vGhosts.erase(position: m_vGhosts.begin() + Index); |
1007 | } |
1008 | |
1009 | void CMenus::(CUIRect MainView) |
1010 | { |
1011 | // render background |
1012 | MainView.Draw(Color: ms_ColorTabbarActive, Corners: IGraphics::CORNER_B, Rounding: 10.0f); |
1013 | |
1014 | MainView.HSplitTop(Cut: 10.0f, pTop: 0, pBottom: &MainView); |
1015 | MainView.HSplitBottom(Cut: 5.0f, pTop: &MainView, pBottom: 0); |
1016 | MainView.VSplitLeft(Cut: 5.0f, pLeft: 0, pRight: &MainView); |
1017 | MainView.VSplitRight(Cut: 5.0f, pLeft: &MainView, pRight: 0); |
1018 | |
1019 | CUIRect , Status; |
1020 | CUIRect View = MainView; |
1021 | |
1022 | View.HSplitTop(Cut: 17.0f, pTop: &Headers, pBottom: &View); |
1023 | View.HSplitBottom(Cut: 28.0f, pTop: &View, pBottom: &Status); |
1024 | |
1025 | // split of the scrollbar |
1026 | Headers.Draw(Color: ColorRGBA(1, 1, 1, 0.25f), Corners: IGraphics::CORNER_T, Rounding: 5.0f); |
1027 | Headers.VSplitRight(Cut: 20.0f, pLeft: &Headers, pRight: 0); |
1028 | |
1029 | struct CColumn |
1030 | { |
1031 | const char *m_pCaption; |
1032 | int m_Id; |
1033 | float m_Width; |
1034 | CUIRect m_Rect; |
1035 | }; |
1036 | |
1037 | enum |
1038 | { |
1039 | COL_ACTIVE = 0, |
1040 | COL_NAME, |
1041 | COL_TIME, |
1042 | COL_DATE, |
1043 | }; |
1044 | |
1045 | static CColumn s_aCols[] = { |
1046 | {.m_pCaption: "" , .m_Id: -1, .m_Width: 2.0f, .m_Rect: {.x: 0}}, |
1047 | {.m_pCaption: "" , .m_Id: COL_ACTIVE, .m_Width: 30.0f, .m_Rect: {.x: 0}}, |
1048 | {.m_pCaption: Localizable(pStr: "Name" ), .m_Id: COL_NAME, .m_Width: 200.0f, .m_Rect: {.x: 0}}, |
1049 | {.m_pCaption: Localizable(pStr: "Time" ), .m_Id: COL_TIME, .m_Width: 90.0f, .m_Rect: {.x: 0}}, |
1050 | {.m_pCaption: Localizable(pStr: "Date" ), .m_Id: COL_DATE, .m_Width: 150.0f, .m_Rect: {.x: 0}}, |
1051 | }; |
1052 | |
1053 | int NumCols = std::size(s_aCols); |
1054 | |
1055 | // do layout |
1056 | for(int i = 0; i < NumCols; i++) |
1057 | { |
1058 | Headers.VSplitLeft(Cut: s_aCols[i].m_Width, pLeft: &s_aCols[i].m_Rect, pRight: &Headers); |
1059 | |
1060 | if(i + 1 < NumCols) |
1061 | Headers.VSplitLeft(Cut: 2, pLeft: nullptr, pRight: &Headers); |
1062 | } |
1063 | |
1064 | // do headers |
1065 | for(int i = 0; i < NumCols; i++) |
1066 | DoButton_GridHeader(pId: &s_aCols[i].m_Id, pText: Localize(pStr: s_aCols[i].m_pCaption), Checked: 0, pRect: &s_aCols[i].m_Rect); |
1067 | |
1068 | View.Draw(Color: ColorRGBA(0, 0, 0, 0.15f), Corners: 0, Rounding: 0); |
1069 | |
1070 | const int NumGhosts = m_vGhosts.size(); |
1071 | int NumFailed = 0; |
1072 | int NumActivated = 0; |
1073 | static int s_SelectedIndex = 0; |
1074 | static CListBox s_ListBox; |
1075 | s_ListBox.DoStart(RowHeight: 17.0f, NumItems: NumGhosts, ItemsPerRow: 1, RowsPerScroll: 3, SelectedIndex: s_SelectedIndex, pRect: &View, Background: false); |
1076 | |
1077 | for(int i = 0; i < NumGhosts; i++) |
1078 | { |
1079 | const CGhostItem *pGhost = &m_vGhosts[i]; |
1080 | const CListboxItem Item = s_ListBox.DoNextItem(pId: pGhost); |
1081 | |
1082 | if(pGhost->m_Failed) |
1083 | NumFailed++; |
1084 | if(pGhost->Active()) |
1085 | NumActivated++; |
1086 | |
1087 | if(!Item.m_Visible) |
1088 | continue; |
1089 | |
1090 | ColorRGBA rgb = ColorRGBA(1.0f, 1.0f, 1.0f); |
1091 | if(pGhost->m_Own) |
1092 | rgb = color_cast<ColorRGBA>(hsl: ColorHSLA(0.33f, 1.0f, 0.75f)); |
1093 | |
1094 | if(pGhost->m_Failed) |
1095 | rgb = ColorRGBA(0.6f, 0.6f, 0.6f, 1.0f); |
1096 | |
1097 | TextRender()->TextColor(rgb: rgb.WithAlpha(alpha: pGhost->HasFile() ? 1.0f : 0.5f)); |
1098 | |
1099 | for(int c = 0; c < NumCols; c++) |
1100 | { |
1101 | CUIRect Button; |
1102 | Button.x = s_aCols[c].m_Rect.x; |
1103 | Button.y = Item.m_Rect.y; |
1104 | Button.h = Item.m_Rect.h; |
1105 | Button.w = s_aCols[c].m_Rect.w; |
1106 | |
1107 | int Id = s_aCols[c].m_Id; |
1108 | |
1109 | if(Id == COL_ACTIVE) |
1110 | { |
1111 | if(pGhost->Active()) |
1112 | { |
1113 | Graphics()->WrapClamp(); |
1114 | Graphics()->TextureSet(Texture: GameClient()->m_EmoticonsSkin.m_aSpriteEmoticons[(SPRITE_OOP + 7) - SPRITE_OOP]); |
1115 | Graphics()->QuadsBegin(); |
1116 | IGraphics::CQuadItem QuadItem(Button.x + Button.w / 2, Button.y + Button.h / 2, 20.0f, 20.0f); |
1117 | Graphics()->QuadsDraw(pArray: &QuadItem, Num: 1); |
1118 | |
1119 | Graphics()->QuadsEnd(); |
1120 | Graphics()->WrapNormal(); |
1121 | } |
1122 | } |
1123 | else if(Id == COL_NAME) |
1124 | { |
1125 | Ui()->DoLabel(pRect: &Button, pText: pGhost->m_aPlayer, Size: 12.0f, Align: TEXTALIGN_ML); |
1126 | } |
1127 | else if(Id == COL_TIME) |
1128 | { |
1129 | char aBuf[64]; |
1130 | str_time(centisecs: pGhost->m_Time / 10, format: TIME_HOURS_CENTISECS, buffer: aBuf, buffer_size: sizeof(aBuf)); |
1131 | Ui()->DoLabel(pRect: &Button, pText: aBuf, Size: 12.0f, Align: TEXTALIGN_ML); |
1132 | } |
1133 | else if(Id == COL_DATE) |
1134 | { |
1135 | char aBuf[64]; |
1136 | str_timestamp_ex(time: pGhost->m_Date, buffer: aBuf, buffer_size: sizeof(aBuf), FORMAT_SPACE); |
1137 | Ui()->DoLabel(pRect: &Button, pText: aBuf, Size: 12.0f, Align: TEXTALIGN_ML); |
1138 | } |
1139 | } |
1140 | |
1141 | TextRender()->TextColor(r: 1.0f, g: 1.0f, b: 1.0f, a: 1.0f); |
1142 | } |
1143 | |
1144 | s_SelectedIndex = s_ListBox.DoEnd(); |
1145 | |
1146 | Status.Draw(Color: ColorRGBA(1, 1, 1, 0.25f), Corners: IGraphics::CORNER_B, Rounding: 5.0f); |
1147 | Status.Margin(Cut: 5.0f, pOtherRect: &Status); |
1148 | |
1149 | CUIRect Button; |
1150 | Status.VSplitLeft(Cut: 25.0f, pLeft: &Button, pRight: &Status); |
1151 | |
1152 | static CButtonContainer s_ReloadButton; |
1153 | static CButtonContainer s_DirectoryButton; |
1154 | static CButtonContainer s_ActivateAll; |
1155 | |
1156 | if(DoButton_FontIcon(pButtonContainer: &s_ReloadButton, pText: FONT_ICON_ARROW_ROTATE_RIGHT, Checked: 0, pRect: &Button) || Input()->KeyPress(Key: KEY_F5) || (Input()->KeyPress(Key: KEY_R) && Input()->ModifierIsPressed())) |
1157 | { |
1158 | m_pClient->m_Ghost.UnloadAll(); |
1159 | GhostlistPopulate(); |
1160 | } |
1161 | |
1162 | Status.VSplitLeft(Cut: 5.0f, pLeft: &Button, pRight: &Status); |
1163 | Status.VSplitLeft(Cut: 175.0f, pLeft: &Button, pRight: &Status); |
1164 | if(DoButton_Menu(pButtonContainer: &s_DirectoryButton, pText: Localize(pStr: "Ghosts directory" ), Checked: 0, pRect: &Button)) |
1165 | { |
1166 | char aBuf[IO_MAX_PATH_LENGTH]; |
1167 | Storage()->GetCompletePath(Type: IStorage::TYPE_SAVE, pDir: "ghosts" , pBuffer: aBuf, BufferSize: sizeof(aBuf)); |
1168 | Storage()->CreateFolder(pFoldername: "ghosts" , Type: IStorage::TYPE_SAVE); |
1169 | Client()->ViewFile(pFilename: aBuf); |
1170 | } |
1171 | |
1172 | Status.VSplitLeft(Cut: 5.0f, pLeft: &Button, pRight: &Status); |
1173 | if(NumGhosts - NumFailed > 0) |
1174 | { |
1175 | Status.VSplitLeft(Cut: 175.0f, pLeft: &Button, pRight: &Status); |
1176 | bool ActivateAll = ((NumGhosts - NumFailed) != NumActivated) && m_pClient->m_Ghost.FreeSlots(); |
1177 | |
1178 | const char *pActionText = ActivateAll ? Localize(pStr: "Activate all" ) : Localize(pStr: "Deactivate all" ); |
1179 | if(DoButton_Menu(pButtonContainer: &s_ActivateAll, pText: pActionText, Checked: 0, pRect: &Button)) |
1180 | { |
1181 | for(int i = 0; i < NumGhosts; i++) |
1182 | { |
1183 | CGhostItem *pGhost = &m_vGhosts[i]; |
1184 | if(pGhost->m_Failed || (ActivateAll && pGhost->m_Slot != -1)) |
1185 | continue; |
1186 | |
1187 | if(ActivateAll) |
1188 | { |
1189 | if(!m_pClient->m_Ghost.FreeSlots()) |
1190 | break; |
1191 | |
1192 | pGhost->m_Slot = m_pClient->m_Ghost.Load(pFilename: pGhost->m_aFilename); |
1193 | if(pGhost->m_Slot == -1) |
1194 | pGhost->m_Failed = true; |
1195 | } |
1196 | else |
1197 | { |
1198 | m_pClient->m_Ghost.UnloadAll(); |
1199 | pGhost->m_Slot = -1; |
1200 | } |
1201 | } |
1202 | } |
1203 | } |
1204 | |
1205 | if(s_SelectedIndex == -1 || s_SelectedIndex >= (int)m_vGhosts.size()) |
1206 | return; |
1207 | |
1208 | CGhostItem *pGhost = &m_vGhosts[s_SelectedIndex]; |
1209 | |
1210 | CGhostItem *pOwnGhost = GetOwnGhost(); |
1211 | int ReservedSlots = !pGhost->m_Own && !(pOwnGhost && pOwnGhost->Active()); |
1212 | if(!pGhost->m_Failed && pGhost->HasFile() && (pGhost->Active() || m_pClient->m_Ghost.FreeSlots() > ReservedSlots)) |
1213 | { |
1214 | Status.VSplitRight(Cut: 120.0f, pLeft: &Status, pRight: &Button); |
1215 | |
1216 | static CButtonContainer s_GhostButton; |
1217 | const char *pText = pGhost->Active() ? Localize(pStr: "Deactivate" ) : Localize(pStr: "Activate" ); |
1218 | if(DoButton_Menu(pButtonContainer: &s_GhostButton, pText, Checked: 0, pRect: &Button) || s_ListBox.WasItemActivated()) |
1219 | { |
1220 | if(pGhost->Active()) |
1221 | { |
1222 | m_pClient->m_Ghost.Unload(Slot: pGhost->m_Slot); |
1223 | pGhost->m_Slot = -1; |
1224 | } |
1225 | else |
1226 | { |
1227 | pGhost->m_Slot = m_pClient->m_Ghost.Load(pFilename: pGhost->m_aFilename); |
1228 | if(pGhost->m_Slot == -1) |
1229 | pGhost->m_Failed = true; |
1230 | } |
1231 | } |
1232 | Status.VSplitRight(Cut: 5.0f, pLeft: &Status, pRight: 0); |
1233 | } |
1234 | |
1235 | Status.VSplitRight(Cut: 120.0f, pLeft: &Status, pRight: &Button); |
1236 | |
1237 | static CButtonContainer s_DeleteButton; |
1238 | if(DoButton_Menu(pButtonContainer: &s_DeleteButton, pText: Localize(pStr: "Delete" ), Checked: 0, pRect: &Button)) |
1239 | { |
1240 | if(pGhost->Active()) |
1241 | m_pClient->m_Ghost.Unload(Slot: pGhost->m_Slot); |
1242 | DeleteGhostItem(Index: s_SelectedIndex); |
1243 | } |
1244 | |
1245 | Status.VSplitRight(Cut: 5.0f, pLeft: &Status, pRight: 0); |
1246 | |
1247 | bool Recording = m_pClient->m_Ghost.GhostRecorder()->IsRecording(); |
1248 | if(!pGhost->HasFile() && !Recording && pGhost->Active()) |
1249 | { |
1250 | static CButtonContainer s_SaveButton; |
1251 | Status.VSplitRight(Cut: 120.0f, pLeft: &Status, pRight: &Button); |
1252 | if(DoButton_Menu(pButtonContainer: &s_SaveButton, pText: Localize(pStr: "Save" ), Checked: 0, pRect: &Button)) |
1253 | m_pClient->m_Ghost.SaveGhost(pItem: pGhost); |
1254 | } |
1255 | } |
1256 | |
1257 | void CMenus::() |
1258 | { |
1259 | float Width = 300 * Graphics()->ScreenAspect(); |
1260 | Graphics()->MapScreen(TopLeftX: 0, TopLeftY: 0, BottomRightX: Width, BottomRightY: 300); |
1261 | TextRender()->TextColor(r: 1, g: 1, b: 1, a: 1); |
1262 | TextRender()->Text(x: 5, y: 280, Size: 5, pText: Localize(pStr: "Menu opened. Press Esc key again to close menu." ), LineWidth: -1.0f); |
1263 | Ui()->MapScreen(); |
1264 | } |
1265 | |