1/*
2 * LegacyClonk
3 *
4 * Copyright (c) RedWolf Design
5 * Copyright (c) 2008, Sven2
6 * Copyright (c) 2017-2022, The LegacyClonk Team and contributors
7 *
8 * Distributed under the terms of the ISC license; see accompanying file
9 * "COPYING" for details.
10 *
11 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
12 * See accompanying file "TRADEMARK" for details.
13 *
14 * To redistribute this file separately, substitute the full license texts
15 * for the above references.
16 */
17
18// Engine internal C4Menus: Main menu, Options, Player join, Hostility, etc.
19
20#include <C4Include.h>
21#include <C4MainMenu.h>
22
23#include <C4FullScreen.h>
24#include <C4Viewport.h>
25#include <C4Wrappers.h>
26#include <C4Player.h>
27#include <C4GameOverDlg.h>
28#include <C4Object.h>
29
30#include <format>
31
32// C4MainMenu
33
34C4MainMenu::C4MainMenu() : C4Menu() // will be re-adjusted later
35{
36 Clear();
37}
38
39void C4MainMenu::Default()
40{
41 C4Menu::Default();
42 Player = NO_OWNER;
43}
44
45bool C4MainMenu::Init(C4FacetExSurface &fctSymbol, const char *szEmpty, int32_t iPlayer, int32_t iExtra, int32_t iExtraData, int32_t iId, int32_t iStyle)
46{
47 if (!DoInit(fctSymbol, szEmpty, iExtra, iExtraData, iId, iStyle)) return false;
48 Player = iPlayer;
49 return true;
50}
51
52bool C4MainMenu::InitRefSym(const C4FacetEx &fctSymbol, const char *szEmpty, int32_t iPlayer, int32_t iExtra, int32_t iExtraData, int32_t iId, int32_t iStyle)
53{
54 if (!DoInitRefSym(fctSymbol, szEmpty, iExtra, iExtraData, iId, iStyle)) return false;
55 Player = iPlayer;
56 return true;
57}
58
59bool C4MainMenu::ActivateNewPlayer(int32_t iPlayer)
60{
61 const auto symbolSize = GetSymbolSize();
62
63 // league or replay game
64 if (Game.Parameters.isLeague() || Game.C4S.Head.Replay) return false;
65 // Max player limit
66 if (Game.Players.GetCount() >= Game.Parameters.MaxPlayers) return false;
67
68 // Menu symbol/init
69 if (GfxR->fctPlayerClr.Surface)
70 GfxR->fctPlayerClr.Surface->SetClr(0xff);
71 InitRefSym(GfxR->fctPlayerClr, szEmpty: LoadResStr(id: C4ResStrTableKey::IDS_MENU_NOPLRFILES), iPlayer);
72 std::string command;
73 for (DirectoryIterator iter(Config.General.PlayerPath); *iter; ++iter)
74 if (WildcardMatch(szFName1: "*.c4p", szFName2: *iter))
75 {
76 char szFilename[_MAX_PATH + 1];
77 SCopy(szSource: *iter, sTarget: szFilename, _MAX_PATH);
78 if (DirectoryExists(szFileName: szFilename)) continue;
79 if (Game.Players.FileInUse(szFilename)) continue;
80 // Open group
81 C4Group hGroup;
82 if (!hGroup.Open(szGroupName: szFilename)) continue;
83 // Load player info
84 C4PlayerInfoCore C4P;
85 if (!C4P.Load(hGroup)) { hGroup.Close(); continue; }
86 // Load custom portrait
87 C4FacetExSurface fctPortrait;
88 if (Config.Graphics.ShowPortraits)
89 if (!fctPortrait.Load(hGroup, C4CFN_BigIcon, iWdt: C4FCT_Full, iHgt: C4FCT_Full, fOwnPal: false, fNoErrIfNotFound: true))
90 if (!fctPortrait.Load(hGroup, C4CFN_Portrait, iWdt: C4FCT_Full, iHgt: C4FCT_Full, fOwnPal: false, fNoErrIfNotFound: true))
91 fctPortrait.Load(hGroup, C4CFN_Portrait_Old, iWdt: C4FCT_Full, iHgt: C4FCT_Full, fOwnPal: false, fNoErrIfNotFound: true);
92 // Close group
93 hGroup.Close();
94 // Add player item
95 command = std::format(fmt: "JoinPlayer:{}", args: +szFilename);
96 const std::string itemText{LoadResStr(id: C4ResStrTableKey::IDS_MENU_NEWPLAYER, args&: C4P.PrefName)};
97 // No custom portrait: use default player image
98 if (!fctPortrait.Surface)
99 {
100 fctPortrait.Create(iWdt: symbolSize, iHgt: symbolSize);
101 GfxR->fctPlayerClr.DrawClr(cgo&: fctPortrait, fAspect: true, dwClr: 0xff);
102 }
103 // Create color overlay for portrait
104 C4FacetExSurface fctPortraitClr;
105 fctPortraitClr.CreateClrByOwner(pBySurface: fctPortrait.Surface);
106 // Create menu symbol from colored portrait
107 C4FacetExSurface fctSymbol;
108 fctSymbol.Create(iWdt: symbolSize, iHgt: symbolSize);
109 fctPortraitClr.DrawClr(cgo&: fctSymbol, fAspect: true, dwClr: C4P.PrefColorDw);
110 // Add menu item
111 Add(szCaption: itemText.c_str(), fctSymbol, szCommand: command.c_str());
112 // Reset symbol facet (menu holds on to the surface)
113 fctSymbol.Default();
114 }
115
116 // Alignment
117 SetAlignment(C4MN_Align_Left | C4MN_Align_Bottom);
118 // Go back to options menu on close
119 SetCloseCommand("ActivateMenu:Main");
120
121 return true;
122}
123
124bool C4MainMenu::DoRefillInternal(bool &rfRefilled)
125{
126 const auto symbolSize = GetSymbolSize();
127
128 // Variables
129 C4FacetExSurface fctSymbol;
130 C4Player *pPlayer;
131 C4IDList ListItems;
132 C4Facet fctTarget;
133 bool fWasEmpty = !GetItemCount();
134
135 // Refill
136 switch (Identification)
137 {
138 case C4MN_Hostility:
139 {
140 // Clear items
141 ClearItems();
142 // Refill player
143 if (!(pPlayer = Game.Players.Get(iPlayer: Player))) return false;
144 // Refill items
145 C4Player *pPlr; int32_t iIndex;
146 for (iIndex = 0; pPlr = Game.Players.GetByIndex(iIndex); iIndex++)
147 // Ignore player self and invisible
148 if (pPlr != pPlayer) if (!pPlr->IsInvisible())
149 {
150 // Symbol
151 fctSymbol.Create(iWdt: symbolSize, iHgt: symbolSize);
152 pPlayer->DrawHostility(cgo&: fctSymbol, iIndex);
153 // Message
154 const std::string msg{LoadResStrChoice(condition: pPlayer->Hostility.GetIDCount(id: pPlr->Number + 1), ifTrue: C4ResStrTableKey::IDS_MENU_ATTACK, ifFalse: C4ResStrTableKey::IDS_MENU_NOATTACK, args: pPlr->GetName())};
155 // Command
156 const std::string command{std::format(fmt: "SetHostility:{}", args&: pPlr->Number)};
157 // Info caption
158 const std::string friendly{LoadResStrChoice(condition: pPlr->Hostility.GetIDCount(id: pPlayer->Number + 1), ifTrue: C4ResStrTableKey::IDS_MENU_ATTACKHOSTILE, ifFalse: C4ResStrTableKey::IDS_MENU_ATTACKFRIENDLY)};
159 std::string notFriendly;
160
161 if (!pPlayer->Hostility.GetIDCount(id: pPlr->Number + 1))
162 {
163 notFriendly = LoadResStr(id: C4ResStrTableKey::IDS_MENU_ATTACKNOT);
164 }
165
166 const std::string infoCaption{LoadResStr(id: C4ResStrTableKey::IDS_MENU_ATTACKINFO, args: pPlr->GetName(), args: friendly, args&: notFriendly)};
167
168 // Add item
169 Add(szCaption: msg.c_str(), fctSymbol, szCommand: command.c_str(), iCount: C4MN_Item_NoCount, pObject: nullptr, szInfoCaption: infoCaption.c_str());
170 fctSymbol.Default();
171 }
172 break;
173 }
174
175 case C4MN_TeamSelection:
176 case C4MN_TeamSwitch:
177 {
178 // Clear items
179 ClearItems();
180 // add all teams as menu items
181 // 2do: Icon
182 C4Team *pTeam; int32_t i = 0; bool fAddNewTeam = Game.Teams.IsAutoGenerateTeams();
183 for (;;)
184 {
185 pTeam = Game.Teams.GetTeamByIndex(iIndex: i);
186 if (pTeam)
187 {
188 // next regular team
189 ++i;
190 // do not add a new team if an empty team exists
191 if (!pTeam->GetPlayerCount()) fAddNewTeam = false;
192 }
193 else if (fAddNewTeam)
194 {
195 // join new team
196 fAddNewTeam = false;
197 }
198 else
199 {
200 // all teams done
201 break;
202 }
203 // create team symbol: Icon spec if specified; otherwise flag for empty and crew for nonempty team
204 fctSymbol.Create(iWdt: symbolSize, iHgt: symbolSize);
205 const char *szIconSpec = pTeam ? pTeam->GetIconSpec() : nullptr;
206 bool fHasIcon = false;
207 if (szIconSpec && *szIconSpec)
208 {
209 C4FacetExSurface fctSrc;
210 fHasIcon = Game.DrawTextSpecImage(fctTarget&: fctSrc, szSpec: szIconSpec, dwClr: pTeam->GetColor());
211 fctSrc.Draw(cgo&: fctSymbol);
212 }
213 if (!fHasIcon)
214 {
215 if (pTeam && pTeam->GetPlayerCount())
216 Game.GraphicsResource.fctCrewClr.DrawClr(cgo&: fctSymbol, fAspect: true, dwClr: pTeam->GetColor());
217 else
218 C4GUI::Icon::GetIconFacet(icoIconIndex: C4GUI::Ico_Team).Draw(cgo&: fctSymbol, fAspect: true);
219 }
220 StdStrBuf sTeamName;
221 if (pTeam)
222 {
223 sTeamName.Take(Buf2: pTeam->GetNameWithParticipants());
224 }
225 else
226 sTeamName.Ref(pnData: LoadResStr(id: C4ResStrTableKey::IDS_PRC_NEWTEAM));
227 const char *szOperation = (Identification == C4MN_TeamSwitch) ? "TeamSwitch" : "TeamSel";
228 Add(szCaption: sTeamName.getData(), fctSymbol, szCommand: std::format(fmt: "{}:{}", args&: szOperation, args: pTeam ? pTeam->GetID() : TEAMID_New).c_str(),
229 iCount: C4MN_Item_NoCount, pObject: nullptr, szInfoCaption: LoadResStr(id: C4ResStrTableKey::IDS_MSG_JOINTEAM, args: sTeamName.getData()).c_str(), idID: C4ID(pTeam ? pTeam->GetID() : 0));
230 fctSymbol.Default();
231 }
232 break;
233 }
234
235 case C4MN_Observer: // observer menu
236 {
237 // Clear items
238 ClearItems();
239 // Check validity
240 C4Viewport *pVP = Game.GraphicsSystem.GetViewport(iPlayer: NO_OWNER);
241 if (!pVP) return false;
242 int32_t iInitialSelection = 0;
243 // Add free view
244 AddRefSym(szCaption: LoadResStr(id: C4ResStrTableKey::IDS_MSG_FREEVIEW), fctSymbol: C4GUI::Icon::GetIconFacet(icoIconIndex: C4GUI::Ico_Star), szCommand: "Observe:Free", iCount: C4MN_Item_NoCount, pObject: nullptr, szInfoCaption: LoadResStr(id: C4ResStrTableKey::IDS_MSG_FREELYSCROLLAROUNDTHEMAP));
245 // Add players
246 C4Player *pPlr; int32_t iIndex;
247 for (iIndex = 0; pPlr = Game.Players.GetByIndex(iIndex); iIndex++)
248 {
249 // Ignore invisible
250 if (!pPlr->IsInvisible())
251 {
252 // Symbol
253 fctSymbol.Create(iWdt: symbolSize, iHgt: symbolSize);
254 Game.GraphicsResource.fctPlayerClr.DrawClr(cgo&: fctSymbol, fAspect: true, dwClr: pPlr->ColorDw);
255 // Message
256 StdStrBuf sMsg;
257 uint32_t dwClr = pPlr->ColorDw;
258 const std::string msg{std::format(fmt: "<c {:x}>{}</c>", args: C4GUI::MakeColorReadableOnBlack(rdwClr&: dwClr), args: pPlr->GetName())};
259 // Command
260 const std::string command{std::format(fmt: "Observe:{}", args&: pPlr->Number)};
261 // Info caption
262 const std::string info{LoadResStr(id: C4ResStrTableKey::IDS_TEXT_FOLLOWVIEWOFPLAYER, args: pPlr->GetName())};
263 // Add item
264 Add(szCaption: msg.c_str(), fctSymbol, szCommand: command.c_str(), iCount: C4MN_Item_NoCount, pObject: nullptr, szInfoCaption: info.c_str());
265 fctSymbol.Default();
266 // check if this is the currently selected player
267 if (pVP->GetPlayer() == pPlr->Number) iInitialSelection = GetItemCount() - 1;
268 }
269 // Initial selection on followed player
270 if (fWasEmpty) SetSelection(iSelection: iInitialSelection, fAdjustPosition: false, fDoCalls: true);
271 }
272 }
273 break;
274
275 default:
276 // No internal refill needed
277 return true;
278 }
279
280 // Successfull internal refill
281 rfRefilled = true;
282 return true;
283}
284
285void C4MainMenu::OnSelectionChanged(int32_t iNewSelection)
286{
287 // immediate player highlight in observer menu
288 if (Identification == C4MN_Observer)
289 {
290 C4MenuItem *pItem = GetSelectedItem();
291 if (pItem)
292 {
293 if (SEqual2(szStr1: pItem->GetCommand(), szStr2: "Observe:"))
294 MenuCommand(szCommand: pItem->GetCommand(), fIsCloseCommand: false);
295 }
296 }
297}
298
299void C4MainMenu::OnUserSelectItem(int32_t Player, int32_t iIndex)
300{
301 // direct selection for non-sync-menus
302 SetSelection(iSelection: iIndex, fAdjustPosition: true, fDoCalls: true);
303}
304
305void C4MainMenu::OnUserEnter(int32_t Player, int32_t iIndex, bool fRight)
306{
307 // direct menu control
308 // but ensure selection is Okay before
309 SetSelection(iSelection: iIndex, fAdjustPosition: true, fDoCalls: false);
310 Enter(fRight);
311}
312
313void C4MainMenu::OnUserClose()
314{
315 // just close
316 TryClose(fOK: false, fControl: true);
317}
318
319void C4MainMenu::OnClosed(bool ok)
320{
321 if (const auto player = GetControllingPlayer(); player != NO_OWNER)
322 {
323 if (const auto plr = Game.Players.Get(iPlayer: player); plr)
324 {
325 plr->ClearPressedComsSynced();
326 }
327 }
328
329 C4Menu::OnClosed(fOK: ok);
330}
331
332bool C4MainMenu::ActivateGoals(int32_t iPlayer, bool fDoActivate)
333{
334 const auto symbolSize = GetSymbolSize();
335
336 C4FacetExSurface fctSymbol;
337 C4FacetEx fctGF; // goal fulfilled facet
338
339 if (fDoActivate)
340 {
341 // Menu symbol/init
342 InitRefSym(GfxR->fctMenu.GetPhase(iPhaseX: 4), szEmpty: LoadResStr(id: C4ResStrTableKey::IDS_MENU_CPGOALS), iPlayer);
343 SetAlignment(C4MN_Align_Left | C4MN_Align_Bottom);
344 const auto scale = Application.GetScale();
345 const auto starWidth = Game.GraphicsResource.fctCaptain.Wdt * scale;
346 const auto starHeight = Game.GraphicsResource.fctCaptain.Hgt * scale;
347 SetPermanent(false);
348 fctGF.Set(nsfc: nullptr, nx: symbolSize - starWidth - 2 * scale, ny: 2 * scale, nwdt: starWidth, nhgt: starHeight);
349 }
350 // determine if the goals are fulfilled - do the calls even if the menu is not to be opened to ensure synchronization
351 C4IDList GoalList, FulfilledGoalList;
352 C4RoundResults::EvaluateGoals(GoalList, FulfilledGoalList, iPlayerNumber: iPlayer);
353 // Add Items
354 if (fDoActivate)
355 {
356 int32_t iNumGoals = GoalList.GetNumberOfIDs(), cnt;
357 C4ID idGoal; C4Def *pDef;
358 for (int32_t i = 0; i < iNumGoals; ++i)
359 if (idGoal = GoalList.GetID(index: i, ipCount: &cnt))
360 if (pDef = C4Id2Def(id: idGoal))
361 {
362 fctSymbol.Create(iWdt: symbolSize, iHgt: symbolSize);
363 // 2do: If an object instance is known, draw the object instead?
364 // this would allow us to do dynamic pictures and overlays; e.g. draw the actual, required settlement score
365 // for settlement score goals
366 // Same for pDef->GetName(), pDef->GetDesc()
367 pDef->Draw(cgo&: fctSymbol);
368 if (FulfilledGoalList.GetIDCount(id: idGoal))
369 {
370 fctGF.Surface = fctSymbol.Surface;
371 Game.GraphicsResource.fctCaptain.Draw(cgo&: fctGF);
372 }
373 Add(szCaption: pDef->GetName(), fctSymbol, szCommand: std::format(fmt: "Player:Goal:{}", args: C4IdText(id: idGoal)).c_str(), iCount: C4MN_Item_NoCount, pObject: nullptr, szInfoCaption: pDef->GetDesc());
374 }
375 // Go back to options menu on close
376 SetCloseCommand("ActivateMenu:Main");
377 }
378 // Done
379 return true;
380}
381
382bool C4MainMenu::ActivateRules(int32_t iPlayer)
383{
384 const auto symbolSize = GetSymbolSize();
385
386 // Menu symbol/init
387 std::string command;
388 C4FacetExSurface fctSymbol;
389 InitRefSym(GfxR->fctMenu.GetPhase(iPhaseX: 5), szEmpty: LoadResStr(id: C4ResStrTableKey::IDS_MENU_CPRULES), iPlayer);
390 SetAlignment(C4MN_Align_Left | C4MN_Align_Bottom);
391 SetPermanent(false);
392 // Items
393 int32_t cnt; C4ID idGoal; C4Def *pDef;
394 for (cnt = 0; idGoal = Game.Objects.ObjectsInt().GetListID(dwCategory: C4D_Rule, Index: cnt); cnt++)
395 if (pDef = C4Id2Def(id: idGoal))
396 {
397 fctSymbol.Create(iWdt: symbolSize, iHgt: symbolSize); pDef->Draw(cgo&: fctSymbol);
398 command = std::format(fmt: "Player:Rule:{}", args: C4IdText(id: idGoal));
399 Add(szCaption: pDef->GetName(), fctSymbol, szCommand: command.c_str(), iCount: C4MN_Item_NoCount, pObject: nullptr, szInfoCaption: pDef->GetDesc());
400 }
401 // Go back to options menu on close
402 SetCloseCommand("ActivateMenu:Main");
403 // Done
404 return true;
405}
406
407bool LooksLikeInteger(const char *szInt)
408{
409 // safety
410 if (!szInt) return false;
411 // check sign
412 if (*szInt == '+' || *szInt == '-') ++szInt;
413 // check int32_t length
414 if (!*szInt) return false;
415 // must contain only digits now
416 char c;
417 while (c = *(szInt++)) if (!Inside<char>(ival: c, lbound: '0', rbound: '9')) return false;
418 // it's an int32_t
419 return true;
420}
421
422bool C4MainMenu::ActivateSavegame(int32_t iPlayer)
423{
424 // Check if saving is possible
425 if (!Game.CanQuickSave()) return false;
426
427 // Menu symbol/init
428 char DirPath[_MAX_PATH + 1];
429 char ScenName[_MAX_PATH + 1]; *ScenName = 0;
430
431 InitRefSym(GfxR->fctMenu.GetPhase(iPhaseX: 0), szEmpty: LoadResStr(id: C4ResStrTableKey::IDS_MENU_CPSAVEGAME), iPlayer);
432 SetAlignment(C4MN_Align_Left | C4MN_Align_Bottom);
433 SetPermanent(true);
434
435 // target file name mask
436 // get folder & filename to store in
437 // some magic is needed to ensure savegames are stored properly into their folders
438 SCopy(szSource: GetFilename(path: Game.ScenarioFilename), sTarget: DirPath);
439 if (DirPath[strlen(s: DirPath) - 1] == '\\') DirPath[strlen(s: DirPath) - 1] = 0;
440 RemoveExtension(szFileName: DirPath);
441 if (LooksLikeInteger(szInt: DirPath))
442 {
443 // ScenTitle.c4f\%d.c4s-names (old-style savegames)
444 // get owning folder
445 if (Game.pParentGroup)
446 {
447 // owning folder determines filename
448 SCopy(szSource: GetFilenameOnly(strFilename: Game.pParentGroup->GetName()), sTarget: ScenName);
449 }
450 else
451 {
452 // no owning folder known: too bad
453 // make a vague guess based on the scenario title
454 SCopy(szSource: GetFilenameOnly(strFilename: Game.ScenarioFilename), sTarget: ScenName);
455 }
456 }
457 else
458 {
459 // DirPath is a valid filename for now...
460 SCopy(szSource: DirPath, sTarget: ScenName);
461 // but remove trailing numbers to adjust new-style savegames
462 char *pScenNameEnd = ScenName + SLen(sptr: ScenName);
463 while (Inside<char>(ival: *--pScenNameEnd, lbound: '0', rbound: '9'))
464 if (pScenNameEnd == ScenName)
465 {
466 // should not happen: digit-only-filenames should have been caught earlier
467 SCopy(szSource: "dbg_error!", sTarget: ScenName);
468 pScenNameEnd = ScenName + SLen(sptr: ScenName) - 1;
469 }
470 pScenNameEnd[1] = 0;
471 }
472
473 // New Style 2007:
474 // * scenarios are saved into ScenName.c4f/ScenName123.c4s to keep umlauts out of filenames
475 // * language titles are stored in folders as title component
476 const std::string filename{std::format(fmt: "{}.c4f" DirSep "{}{{}}.c4s", args: +ScenName, args: +ScenName)};
477
478 // Create menu items
479 std::string filenameIndexed;
480 std::string command;
481 std::string savePath;
482
483 for (int32_t i = 1; i <= 10; i++)
484 {
485 // Index filename
486 filenameIndexed = std::vformat(fmt: filename, args: std::make_format_args(fmt_args&: i));
487 // Compose commmand
488 command = std::format(fmt: "Save:Game:{}:{}", args&: filenameIndexed, args: Game.Parameters.ScenarioTitle.getData()); // Notice: the language title might contain ':' and thus confuse the segment list - but C4Menu::MenuCommand will try to handle this...
489 // Check free slot
490 savePath = std::format(fmt: "{}" DirSep "{}", args: Config.General.SaveGameFolder.getData(), args&: filenameIndexed);
491 bool fFree = !C4Group_IsGroup(szFilename: savePath.c_str());
492 // add menu item
493 AddRefSym(szCaption: LoadResStr(id: C4ResStrTableKey::IDS_MENU_CPSAVEGAME), GfxR->fctMenu.GetPhase(iPhaseX: i - 1, iPhaseY: fFree ? 2 : 1), szCommand: command.c_str(), iCount: C4MN_Item_NoCount, pObject: nullptr, szInfoCaption: LoadResStr(id: C4ResStrTableKey::IDS_MENU_CPSAVEGAMEINFO));
494 }
495
496 // Go back to options menu on close
497 SetCloseCommand("ActivateMenu:Main");
498
499 return true;
500}
501
502bool C4MainMenu::ActivateHost(int32_t iPlayer)
503{
504 // Menu symbol/init
505 InitRefSym(fctSymbol: C4GUI::Icon::GetIconFacet(icoIconIndex: C4GUI::Ico_Disconnect), szEmpty: LoadResStr(id: C4ResStrTableKey::IDS_MENU_DISCONNECTCLIENT), iPlayer, iExtra: C4MN_Extra_None, iExtraData: 0, iId: 0, iStyle: C4MN_Style_Context);
506 SetAlignment(C4MN_Align_Left | C4MN_Align_Bottom);
507 SetPermanent(true);
508 // Clients
509 for (C4Network2Client *pClient = Game.Network.Clients.GetNextClient(pClient: nullptr); pClient; pClient = Game.Network.Clients.GetNextClient(pClient))
510 {
511 bool fHost = (pClient->getID() == 0);
512 const std::string text{std::format(fmt: "{} ({})", args: pClient->getName(), args: pClient->getCore().getNick())};
513 const std::string command{std::format(fmt: "Host:Kick:{}", args: pClient->getID())};
514 C4GUI::Icons iIcon = fHost ? C4GUI::Ico_Host : (pClient->isActivated() ? C4GUI::Ico_Client : C4GUI::Ico_ObserverClient);
515 AddRefSym(szCaption: text.c_str(), fctSymbol: C4GUI::Icon::GetIconFacet(icoIconIndex: iIcon), szCommand: command.c_str());
516 }
517 // Go back to options menu on close
518 SetCloseCommand("ActivateMenu:Main");
519 return true;
520}
521
522bool C4MainMenu::ActivateClient(int32_t iPlayer)
523{
524 const auto symbolSize = GetSymbolSize();
525
526 // Menu symbol/init
527 C4FacetExSurface fctSymbol;
528 InitRefSym(fctSymbol: C4GUI::Icon::GetIconFacet(icoIconIndex: C4GUI::Ico_Disconnect), szEmpty: LoadResStr(id: C4ResStrTableKey::IDS_MENU_DISCONNECTFROMSERVER), iPlayer, iExtra: C4MN_Extra_None, iExtraData: 0, iId: 0, iStyle: C4MN_Style_Context);
529 SetAlignment(C4MN_Align_Left | C4MN_Align_Bottom);
530 fctSymbol.Create(iWdt: symbolSize, iHgt: symbolSize); GfxR->fctOKCancel.Draw(cgo&: fctSymbol, fAspect: true, iPhaseX: 3, iPhaseY: 0);
531 Add(szCaption: LoadResStr(id: C4ResStrTableKey::IDS_BTN_YES), fctSymbol, szCommand: "Part");
532 fctSymbol.Create(iWdt: symbolSize, iHgt: symbolSize); GfxR->fctOKCancel.Draw(cgo&: fctSymbol, fAspect: true, iPhaseX: 1, iPhaseY: 0);
533 Add(szCaption: LoadResStr(id: C4ResStrTableKey::IDS_BTN_NO), fctSymbol, szCommand: "");
534 SetCloseCommand("ActivateMenu:Main");
535 return true;
536}
537
538bool C4MainMenu::ActivateSurrender(int32_t iPlayer)
539{
540 const auto symbolSize = GetSymbolSize();
541
542 C4FacetExSurface fctSymbol;
543 InitRefSym(fctSymbol: C4GUI::Icon::GetIconFacet(icoIconIndex: C4GUI::Ico_Surrender), szEmpty: LoadResStr(id: C4ResStrTableKey::IDS_MENU_SURRENDER), iPlayer, iExtra: C4MN_Extra_None, iExtraData: 0, iId: 0, iStyle: C4MN_Style_Context);
544 SetAlignment(C4MN_Align_Left | C4MN_Align_Bottom);
545 fctSymbol.Create(iWdt: symbolSize, iHgt: symbolSize); GfxR->fctOKCancel.Draw(cgo&: fctSymbol, fAspect: true, iPhaseX: 3, iPhaseY: 0);
546 Add(szCaption: LoadResStr(id: C4ResStrTableKey::IDS_BTN_YES), fctSymbol, szCommand: "Surrender");
547 fctSymbol.Create(iWdt: symbolSize, iHgt: symbolSize); GfxR->fctOKCancel.Draw(cgo&: fctSymbol, fAspect: true, iPhaseX: 1, iPhaseY: 0);
548 Add(szCaption: LoadResStr(id: C4ResStrTableKey::IDS_BTN_NO), fctSymbol, szCommand: "");
549 SetCloseCommand("ActivateMenu:Main");
550 return true;
551}
552
553bool C4MainMenu::ActivateOptions(int32_t iPlayer, int32_t selection)
554{
555 // Menu symbol/init
556 InitRefSym(GfxR->fctOptions.GetPhase(iPhaseX: 0), szEmpty: LoadResStr(id: C4ResStrTableKey::IDS_MNU_OPTIONS), iPlayer, iExtra: C4MN_Extra_None, iExtraData: 0, iId: 0, iStyle: C4MN_Style_Context);
557 SetAlignment(C4MN_Align_Left | C4MN_Align_Bottom);
558 SetPermanent(true);
559 // Sound
560 AddRefSym(szCaption: LoadResStr(id: C4ResStrTableKey::IDS_DLG_SOUND), GfxR->fctOptions.GetPhase(iPhaseX: 17 + Config.Sound.RXSound), szCommand: "Options:Sound", iCount: C4MN_Item_NoCount);
561 // Music
562 AddRefSym(szCaption: LoadResStr(id: C4ResStrTableKey::IDS_MNU_MUSIC), GfxR->fctOptions.GetPhase(iPhaseX: 1 + Config.Sound.RXMusic), szCommand: "Options:Music", iCount: C4MN_Item_NoCount);
563 // Mouse control
564 C4Player *pPlr = Game.Players.Get(iPlayer);
565 if (pPlr && !Game.C4S.Head.DisableMouse)
566 {
567 if (pPlr->MouseControl)
568 AddRefSym(szCaption: LoadResStr(id: C4ResStrTableKey::IDS_MNU_MOUSECONTROL), GfxR->fctOptions.GetPhase(iPhaseX: 11 + 1), szCommand: "Options:Mouse");
569 else if (!Game.Players.MouseControlTaken())
570 AddRefSym(szCaption: LoadResStr(id: C4ResStrTableKey::IDS_MNU_MOUSECONTROL), GfxR->fctOptions.GetPhase(iPhaseX: 11), szCommand: "Options:Mouse");
571 }
572 // Music
573 AddRefSym(szCaption: LoadResStr(id: C4ResStrTableKey::IDS_MENU_DISPLAY), GfxR->fctMenu.GetPhase(iPhaseX: 8), szCommand: "ActivateMenu:Display");
574 // Restore selection
575 SetSelection(iSelection: selection, fAdjustPosition: false, fDoCalls: true);
576 // Go back to main menu on close
577 SetCloseCommand("ActivateMenu:Main");
578 // Done
579 return true;
580}
581
582bool C4MainMenu::ActivateDisplay(int32_t iPlayer, int32_t selection)
583{
584 // Menu symbol/init
585 InitRefSym(GfxR->fctMenu.GetPhase(iPhaseX: 8), szEmpty: LoadResStr(id: C4ResStrTableKey::IDS_MENU_DISPLAY), iPlayer, iExtra: C4MN_Extra_None, iExtraData: 0, iId: 0, iStyle: C4MN_Style_Context);
586 SetAlignment(C4MN_Align_Left | C4MN_Align_Bottom);
587 SetPermanent(true);
588 // Crew player names
589 AddRefSym(szCaption: LoadResStr(id: C4ResStrTableKey::IDS_MNU_PLAYERNAMES), GfxR->fctOptions.GetPhase(iPhaseX: 7 + Config.Graphics.ShowCrewNames), szCommand: "Display:PlayerNames", iCount: C4MN_Item_NoCount, pObject: nullptr, szInfoCaption: LoadResStr(id: C4ResStrTableKey::IDS_MENU_PLAYERNAMES_DESC));
590 // Crew clonk names
591 AddRefSym(szCaption: LoadResStr(id: C4ResStrTableKey::IDS_MNU_CLONKNAMES), GfxR->fctOptions.GetPhase(iPhaseX: 9 + Config.Graphics.ShowCrewCNames), szCommand: "Display:ClonkNames", iCount: C4MN_Item_NoCount, pObject: nullptr, szInfoCaption: LoadResStr(id: C4ResStrTableKey::IDS_MENU_CLONKNAMES_DESC));
592 // Portraits
593 AddRefSym(szCaption: LoadResStr(id: C4ResStrTableKey::IDS_MNU_PORTRAITS), GfxR->fctOptions.GetPhase(iPhaseX: 13 + Config.Graphics.ShowPortraits), szCommand: "Display:Portraits", iCount: C4MN_Item_NoCount);
594 // ShowCommands
595 AddRefSym(szCaption: LoadResStr(id: C4ResStrTableKey::IDS_MENU_SHOWCOMMANDS), GfxR->fctOptions.GetPhase(iPhaseX: 19 + Config.Graphics.ShowCommands), szCommand: "Display:ShowCommands", iCount: C4MN_Item_NoCount);
596 // ShowCommandKeys
597 AddRefSym(szCaption: LoadResStr(id: C4ResStrTableKey::IDS_MENU_SHOWCOMMANDKEYS), GfxR->fctOptions.GetPhase(iPhaseX: 21 + Config.Graphics.ShowCommandKeys), szCommand: "Display:ShowCommandKeys", iCount: C4MN_Item_NoCount);
598 // Upper Board
599 if (Application.isFullScreen)
600 {
601 std::string text{LoadResStr(id: C4ResStrTableKey::IDS_MNU_UPPERBOARD)};
602 text += ": ";
603
604 std::string_view modeName;
605
606 if (Config.Graphics.UpperBoard >= C4UpperBoard::First && Config.Graphics.UpperBoard <= C4UpperBoard::Last)
607 {
608 static constexpr std::array<C4ResStrTableKeyFormat<>, 4> ModeNames
609 {
610 C4ResStrTableKey::IDS_MNU_UPPERBOARD_OFF,
611 C4ResStrTableKey::IDS_MNU_UPPERBOARD_NORMAL,
612 C4ResStrTableKey::IDS_MNU_UPPERBOARD_SMALL,
613 C4ResStrTableKey::IDS_MNU_UPPERBOARD_MINI
614 };
615
616 modeName = LoadResStr(id: ModeNames[Config.Graphics.UpperBoard]);
617 }
618 else
619 {
620 modeName = "???";
621 }
622
623 text += modeName;
624 AddRefSym(szCaption: text.c_str(), GfxR->fctOptions.GetPhase(iPhaseX: 3 + (Config.Graphics.UpperBoard != C4UpperBoard::Hide)), szCommand: "Display:UpperBoard", iCount: C4MN_Item_NoCount);
625 }
626 // FPS
627 if (Application.isFullScreen)
628 AddRefSym(szCaption: LoadResStr(id: C4ResStrTableKey::IDS_MNU_FPS), GfxR->fctOptions.GetPhase(iPhaseX: 5 + Config.General.FPS), szCommand: "Display:FPS", iCount: C4MN_Item_NoCount);
629 // Clock
630 if (Application.isFullScreen)
631 AddRefSym(szCaption: LoadResStr(id: C4ResStrTableKey::IDS_MNU_CLOCK), GfxR->fctOptions.GetPhase(iPhaseX: 15 + Config.Graphics.ShowClock), szCommand: "Display:Clock", iCount: C4MN_Item_NoCount);
632 // White chat
633 if (Application.isFullScreen)
634 AddRefSym(szCaption: LoadResStr(id: C4ResStrTableKey::IDS_MNU_WHITECHAT), GfxR->fctOptions.GetPhase(iPhaseX: 3 + Config.General.UseWhiteIngameChat), szCommand: "Display:WhiteChat", iCount: C4MN_Item_NoCount, pObject: nullptr, szInfoCaption: LoadResStr(id: C4ResStrTableKey::IDS_DESC_WHITECHAT_INGAME));
635 // Restore selection
636 SetSelection(iSelection: selection, fAdjustPosition: false, fDoCalls: true);
637 // Go back to options menu on close
638 SetCloseCommand("ActivateMenu:Options");
639 // Done
640 return true;
641}
642
643bool C4MainMenu::ActivateMain(int32_t iPlayer)
644{
645 const auto symbolSize = GetSymbolSize();
646
647 // Determine player
648 C4Player *pPlr = Game.Players.Get(iPlayer);
649 // Menu symbol/init
650 C4FacetExSurface fctSymbol;
651 fctSymbol.Create(iWdt: symbolSize, iHgt: symbolSize);
652 GfxR->fctOKCancel.Draw(cgo&: fctSymbol, fAspect: true, iPhaseX: 1, iPhaseY: 1);
653 Init(fctSymbol, szEmpty: LoadResStrChoice(condition: pPlr, ifTrue: C4ResStrTableKey::IDS_MENU_CPMAIN, ifFalse: C4ResStrTableKey::IDS_MENU_OBSERVER), iPlayer, iExtra: C4MN_Extra_None, iExtraData: 0, iId: 0, iStyle: C4MN_Style_Context);
654 SetAlignment(C4MN_Align_Left | C4MN_Align_Bottom);
655 // Goals+Rules (player menu only)
656 // Goal menu can't be shown because of script callbacks
657 // Rule menu could be shown, but rule activation would issue script callbacks and trigger client activation
658 // Showing rules but not showing goals would be strange anyway
659 if (pPlr)
660 {
661 // Goals
662 AddRefSym(szCaption: LoadResStr(id: C4ResStrTableKey::IDS_MENU_CPGOALS), GfxR->fctMenu.GetPhase(iPhaseX: 4), szCommand: "ActivateMenu:Goals", iCount: C4MN_Item_NoCount, pObject: nullptr, szInfoCaption: LoadResStr(id: C4ResStrTableKey::IDS_MENU_CPGOALSINFO));
663 // Rules
664 AddRefSym(szCaption: LoadResStr(id: C4ResStrTableKey::IDS_MENU_CPRULES), GfxR->fctMenu.GetPhase(iPhaseX: 5), szCommand: "ActivateMenu:Rules", iCount: C4MN_Item_NoCount, pObject: nullptr, szInfoCaption: LoadResStr(id: C4ResStrTableKey::IDS_MENU_CPRULESINFO));
665 }
666 // Observer menu in free viewport
667 if (!pPlr)
668 {
669 AddRefSym(szCaption: LoadResStr(id: C4ResStrTableKey::IDS_TEXT_VIEW), fctSymbol: C4GUI::Icon::GetIconFacet(icoIconIndex: C4GUI::Ico_View), szCommand: "ActivateMenu:Observer", iCount: C4MN_Item_NoCount, pObject: nullptr, szInfoCaption: LoadResStr(id: C4ResStrTableKey::IDS_TEXT_DETERMINEPLAYERVIEWTOFOLL));
670 }
671 // Hostility (player menu only)
672 if (pPlr && (Game.Players.GetCount() > 1))
673 {
674 GfxR->fctFlagClr.Surface->SetClr(0xff0000);
675 AddRefSym(szCaption: LoadResStr(id: C4ResStrTableKey::IDS_MENU_CPATTACK), GfxR->fctMenu.GetPhase(iPhaseX: 7), szCommand: "ActivateMenu:Hostility", iCount: C4MN_Item_NoCount, pObject: nullptr, szInfoCaption: LoadResStr(id: C4ResStrTableKey::IDS_MENU_CPATTACKINFO));
676 }
677 // Team change
678 if (pPlr && Game.Teams.IsTeamSwitchAllowed())
679 {
680 C4FacetEx fctTeams; fctTeams = C4GUI::Icon::GetIconFacet(icoIconIndex: C4GUI::Ico_Team);
681 AddRefSym(szCaption: LoadResStr(id: C4ResStrTableKey::IDS_MSG_SELTEAM), fctSymbol: fctTeams, szCommand: "ActivateMenu:TeamSel", iCount: C4MN_Item_NoCount, pObject: nullptr, szInfoCaption: LoadResStr(id: C4ResStrTableKey::IDS_MSG_ALLOWSYOUTOJOINADIFFERENT));
682 }
683 // Player join
684 if ((Game.Players.GetCount() < Game.Parameters.MaxPlayers) && !Game.Parameters.isLeague())
685 {
686 AddRefSym(szCaption: LoadResStr(id: C4ResStrTableKey::IDS_MENU_CPNEWPLAYER), GfxR->fctPlayerClr.GetPhase(), szCommand: "ActivateMenu:NewPlayer", iCount: C4MN_Item_NoCount, pObject: nullptr, szInfoCaption: LoadResStr(id: C4ResStrTableKey::IDS_MENU_CPNEWPLAYERINFO));
687 }
688 // Save game (player menu only - should we allow saving games with no players in it?)
689 if (pPlr && (!Game.Network.isEnabled() || Game.Network.isHost()))
690 {
691 AddRefSym(szCaption: LoadResStr(id: C4ResStrTableKey::IDS_MENU_CPSAVEGAME), GfxR->fctMenu.GetPhase(iPhaseX: 0), szCommand: "ActivateMenu:Save:Game", iCount: C4MN_Item_NoCount, pObject: nullptr, szInfoCaption: LoadResStr(id: C4ResStrTableKey::IDS_MENU_CPSAVEGAMEINFO));
692 }
693 // Options
694 AddRefSym(szCaption: LoadResStr(id: C4ResStrTableKey::IDS_MNU_OPTIONS), GfxR->fctOptions.GetPhase(iPhaseX: 0), szCommand: "ActivateMenu:Options", iCount: C4MN_Item_NoCount, pObject: nullptr, szInfoCaption: LoadResStr(id: C4ResStrTableKey::IDS_MNU_OPTIONSINFO));
695 // Disconnect
696 if (Game.Network.isEnabled())
697 {
698 // Host
699 if (Game.Network.isHost() && Game.Clients.getClient(pAfter: nullptr))
700 AddRefSym(szCaption: LoadResStr(id: C4ResStrTableKey::IDS_MENU_DISCONNECT), fctSymbol: C4GUI::Icon::GetIconFacet(icoIconIndex: C4GUI::Ico_Disconnect), szCommand: "ActivateMenu:Host", iCount: C4MN_Item_NoCount, pObject: nullptr, szInfoCaption: LoadResStr(id: C4ResStrTableKey::IDS_TEXT_KICKCERTAINCLIENTSFROMTHE));
701 // Client
702 if (!Game.Network.isHost())
703 AddRefSym(szCaption: LoadResStr(id: C4ResStrTableKey::IDS_MENU_DISCONNECT), fctSymbol: C4GUI::Icon::GetIconFacet(icoIconIndex: C4GUI::Ico_Disconnect), szCommand: "ActivateMenu:Client", iCount: C4MN_Item_NoCount, pObject: nullptr, szInfoCaption: LoadResStr(id: C4ResStrTableKey::IDS_TEXT_DISCONNECTTHEGAMEFROMTHES));
704 }
705 // Surrender (player menu only)
706 if (pPlr)
707 AddRefSym(szCaption: LoadResStr(id: C4ResStrTableKey::IDS_MENU_CPSURRENDER), fctSymbol: C4GUI::Icon::GetIconFacet(icoIconIndex: C4GUI::Ico_Surrender), szCommand: "ActivateMenu:Surrender", iCount: C4MN_Item_NoCount, pObject: nullptr, szInfoCaption: LoadResStr(id: C4ResStrTableKey::IDS_MENU_CPSURRENDERINFO));
708 // Abort
709 if (Application.isFullScreen)
710 AddRefSym(szCaption: LoadResStr(id: C4ResStrTableKey::IDS_MENU_ABORT), fctSymbol: C4GUI::Icon::GetIconFacet(icoIconIndex: C4GUI::Ico_Exit), szCommand: "Abort", iCount: C4MN_Item_NoCount, pObject: nullptr, szInfoCaption: LoadResStr(id: C4ResStrTableKey::IDS_MENU_ABORT_DESC));
711 // No empty menus
712 if (GetItemCount() == 0) Close(fOK: false);
713 // Done
714 return true;
715}
716
717bool C4MainMenu::ActivateHostility(int32_t iPlayer)
718{
719 const auto symbolSize = GetSymbolSize();
720
721 // Init menu
722 C4FacetExSurface fctSymbol;
723 fctSymbol.Create(iWdt: symbolSize, iHgt: symbolSize);
724 GfxR->fctMenu.GetPhase(iPhaseX: 7).Draw(cgo&: fctSymbol);
725 Init(fctSymbol, szEmpty: LoadResStr(id: C4ResStrTableKey::IDS_MENU_CPATTACK), iPlayer, iExtra: C4MN_Extra_None, iExtraData: 0, iId: C4MN_Hostility);
726 SetAlignment(C4MN_Align_Left | C4MN_Align_Bottom);
727 SetPermanent(true);
728 Refill();
729 // Go back to options menu on close
730 SetCloseCommand("ActivateMenu:Main");
731 return true;
732}
733
734bool C4MainMenu::MenuCommand(const char *szCommand, bool fIsCloseCommand)
735{
736 // Determine player
737 C4Player *pPlr = Game.Players.Get(iPlayer: Player);
738 // Activate
739 if (SEqual2(szStr1: szCommand, szStr2: "ActivateMenu:"))
740 {
741 if (C4GameOverDlg::IsShown()) return false; // no new menus during game over dlg
742 if (SEqual(szStr1: szCommand + 13, szStr2: "Main")) return ActivateMain(iPlayer: Player);
743 if (SEqual(szStr1: szCommand + 13, szStr2: "Hostility")) return ActivateHostility(iPlayer: Player);
744 if (SEqual(szStr1: szCommand + 13, szStr2: "NewPlayer")) return ActivateNewPlayer(iPlayer: Player);
745 if (SEqual(szStr1: szCommand + 13, szStr2: "Goals"))
746 {
747 Game.Control.DoInput(eCtrlType: CID_ActivateGameGoalMenu, pPkt: new C4ControlActivateGameGoalMenu(Player), eDelivery: CDT_Queue);
748 return true;
749 }
750 if (SEqual(szStr1: szCommand + 13, szStr2: "Rules")) return ActivateRules(iPlayer: Player);
751 if (SEqual(szStr1: szCommand + 13, szStr2: "Host")) return ActivateHost(iPlayer: Player);
752 if (SEqual(szStr1: szCommand + 13, szStr2: "Client")) return ActivateClient(iPlayer: Player);
753 if (SEqual(szStr1: szCommand + 13, szStr2: "Options")) return ActivateOptions(iPlayer: Player);
754 if (SEqual(szStr1: szCommand + 13, szStr2: "Display")) return ActivateDisplay(iPlayer: Player);
755 if (SEqual(szStr1: szCommand + 13, szStr2: "Save:Game")) return ActivateSavegame(iPlayer: Player);
756 if (SEqual(szStr1: szCommand + 13, szStr2: "TeamSel")) return pPlr ? pPlr->ActivateMenuTeamSelection(fFromMain: true) : false;
757 if (SEqual(szStr1: szCommand + 13, szStr2: "Surrender")) return ActivateSurrender(iPlayer: Player);
758 if (SEqual(szStr1: szCommand + 13, szStr2: "Observer")) return ActivateObserver();
759 }
760 // JoinPlayer
761 if (SEqual2(szStr1: szCommand, szStr2: "JoinPlayer:"))
762 {
763 // not in league or replay mode
764 if (Game.Parameters.isLeague() || Game.C4S.Head.Replay) return false;
765 // join player
766 if (Game.Network.isEnabled())
767 // 2do: not for observers and such?
768 Game.Network.Players.JoinLocalPlayer(szLocalPlayerFilename: szCommand + 11, fAdd: true);
769 else
770 Game.Players.CtrlJoinLocalNoNetwork(szFilename: szCommand + 11, iAtClient: Game.Clients.getLocalID(), szAtClientName: Game.Clients.getLocalName());
771 return true;
772 }
773 // SetHostility
774 if (SEqual2(szStr1: szCommand, szStr2: "SetHostility:"))
775 {
776 // only if allowed
777 if (!Game.Teams.IsHostilityChangeAllowed()) return false;
778 int32_t iOpponent; sscanf(s: szCommand + 13, format: "%i", &iOpponent);
779 C4Player *pOpponent = Game.Players.Get(iPlayer: iOpponent);
780 if (!pOpponent || pOpponent->GetType() != C4PT_User) return false;
781 Game.Input.Add(eType: CID_ToggleHostility, pCtrl: new C4ControlToggleHostility(Player, iOpponent));
782 return true;
783 }
784 // Abort
785 if (SEqual2(szStr1: szCommand, szStr2: "Abort"))
786 {
787 FullScreen.ShowAbortDlg();
788 return true;
789 }
790 // Surrender
791 if (SEqual2(szStr1: szCommand, szStr2: "Surrender"))
792 {
793 Game.Control.DoInput(eCtrlType: CID_SurrenderPlayer, pPkt: new C4ControlSurrenderPlayer(Player), eDelivery: CDT_Queue);
794 return true;
795 }
796 // Save game
797 if (SEqual2(szStr1: szCommand, szStr2: "Save:Game:"))
798 {
799 char strFilename[_MAX_PATH + 1]; SCopySegment(fstr: szCommand, segn: 2, tstr: strFilename, sepa: ':', _MAX_PATH);
800 char strTitle[_MAX_PATH + 1]; SCopy(szSource: szCommand + SCharPos(cTarget: ':', szInStr: szCommand, iIndex: 2) + 1, sTarget: strTitle, _MAX_PATH);
801 Game.QuickSave(strFilename, strTitle);
802 ActivateSavegame(iPlayer: Player);
803 return true;
804 }
805 // Kick
806 if (SEqual2(szStr1: szCommand, szStr2: "Host:Kick:"))
807 {
808 int iClientID = atoi(nptr: szCommand + 10);
809 if (iClientID && Game.Network.isEnabled())
810 if (Game.Parameters.isLeague() && Game.Players.GetAtClient(iClient: iClientID))
811 Game.Network.Vote(eType: VT_Kick, fApprove: true, iData: iClientID);
812 else
813 {
814 C4Client *pClient = Game.Clients.getClientByID(iID: iClientID);
815 if (pClient) Game.Clients.CtrlRemove(pClient, szReason: LoadResStr(id: C4ResStrTableKey::IDS_MSG_KICKBYMENU));
816 Close(fOK: true);
817 }
818 return true;
819 }
820 // Part
821 if (SEqual2(szStr1: szCommand, szStr2: "Part"))
822 {
823 if (Game.Network.isEnabled())
824 if (Game.Parameters.isLeague() && Game.Players.GetLocalByIndex(iIndex: 0))
825 Game.Network.Vote(eType: VT_Kick, fApprove: true, iData: Game.Control.ClientID());
826 else
827 {
828 Game.RoundResults.EvaluateNetwork(eResult: C4RoundResults::NR_NetError, szResultsString: LoadResStr(id: C4ResStrTableKey::IDS_ERR_GAMELEFTVIAPLAYERMENU));
829 Game.Network.Clear();
830 }
831 return true;
832 }
833 // Options
834 if (SEqual2(szStr1: szCommand, szStr2: "Options:"))
835 {
836 // Music
837 if (SEqual(szStr1: szCommand + 8, szStr2: "Music"))
838 {
839 Application.MusicSystem->ToggleOnOff();
840 }
841 // Sound
842 if (SEqual(szStr1: szCommand + 8, szStr2: "Sound"))
843 {
844 Application.SoundSystem->ToggleOnOff();
845 }
846 // Mouse control
847 if (SEqual(szStr1: szCommand + 8, szStr2: "Mouse"))
848 if (pPlr)
849 pPlr->ToggleMouseControl();
850 // Reopen with updated options
851 ActivateOptions(iPlayer: Player, selection: GetSelection());
852 return true;
853 }
854 // Display
855 if (SEqual2(szStr1: szCommand, szStr2: "Display:"))
856 {
857 // Upper board
858 if (SEqual(szStr1: szCommand + 8, szStr2: "UpperBoard"))
859 {
860 ++Config.Graphics.UpperBoard;
861 if (Config.Graphics.UpperBoard > C4UpperBoard::Last)
862 Config.Graphics.UpperBoard = C4UpperBoard::First;
863 Game.InitFullscreenComponents(fRunning: true);
864 }
865 // FPS
866 if (SEqual(szStr1: szCommand + 8, szStr2: "FPS")) Config.General.FPS = !Config.General.FPS;
867 // Player names
868 if (SEqual(szStr1: szCommand + 8, szStr2: "PlayerNames")) Config.Graphics.ShowCrewNames = !Config.Graphics.ShowCrewNames;
869 // Clonk names
870 if (SEqual(szStr1: szCommand + 8, szStr2: "ClonkNames")) Config.Graphics.ShowCrewCNames = !Config.Graphics.ShowCrewCNames;
871 // Portraits
872 if (SEqual(szStr1: szCommand + 8, szStr2: "Portraits")) Config.Graphics.ShowPortraits = !Config.Graphics.ShowPortraits;
873 // ShowCommands
874 if (SEqual(szStr1: szCommand + 8, szStr2: "ShowCommands")) Config.Graphics.ShowCommands = !Config.Graphics.ShowCommands;
875 // ShowCommandKeys
876 if (SEqual(szStr1: szCommand + 8, szStr2: "ShowCommandKeys")) Config.Graphics.ShowCommandKeys = !Config.Graphics.ShowCommandKeys;
877 // Clock
878 if (SEqual(szStr1: szCommand + 8, szStr2: "Clock")) Config.Graphics.ShowClock = !Config.Graphics.ShowClock;
879 // White chat
880 if (SEqual(szStr1: szCommand + 8, szStr2: "WhiteChat")) Config.General.UseWhiteIngameChat = !Config.General.UseWhiteIngameChat;
881 // Reopen with updated options
882 ActivateDisplay(iPlayer: Player, selection: GetSelection());
883 return true;
884 }
885 // Goal info
886 if (SEqual2(szStr1: szCommand, szStr2: "Player:Goal:") || SEqual2(szStr1: szCommand, szStr2: "Player:Rule:"))
887 {
888 if (!ValidPlr(plr: Player)) return false; // observers may not look at goal/rule info, because it requires queue activation
889 Close(fOK: true);
890 // TODO!
891 C4Object *pObj; C4ID idItem = C4Id(str: szCommand + 12);
892 if (pObj = Game.Objects.FindInternal(id: idItem))
893 Game.Control.DoInput(eCtrlType: CID_ActivateGameGoalRule, pPkt: new C4ControlActivateGameGoalRule(Player, pObj->Number), eDelivery: CDT_Queue);
894 else
895 return false;
896 return true;
897 }
898 // Team selection
899 if (SEqual2(szStr1: szCommand, szStr2: "TeamSel:"))
900 {
901 Close(fOK: true);
902 int32_t idTeam = atoi(nptr: szCommand + 8);
903
904 // OK, join this team
905 if (pPlr) pPlr->DoTeamSelection(idTeam);
906 return true;
907 }
908 // Team switch
909 if (SEqual2(szStr1: szCommand, szStr2: "TeamSwitch:"))
910 {
911 Close(fOK: true);
912 int32_t idTeam = atoi(nptr: szCommand + 11);
913
914 // check if it's still allowed
915 if (!Game.Teams.IsTeamSwitchAllowed()) return false;
916 // OK, join this team
917 Game.Control.DoInput(eCtrlType: CID_SetPlayerTeam, pPkt: new C4ControlSetPlayerTeam(Player, idTeam), eDelivery: CDT_Queue);
918 return true;
919 }
920 // Observe
921 if (SEqual2(szStr1: szCommand, szStr2: "Observe:"))
922 {
923 const char *szObserverTarget = szCommand + 8;
924 C4Viewport *pVP = Game.GraphicsSystem.GetViewport(iPlayer: NO_OWNER);
925 if (pVP) // viewport may have closed meanwhile
926 {
927 if (SEqual(szStr1: szObserverTarget, szStr2: "Free"))
928 {
929 // free view
930 pVP->Init(iPlayer: NO_OWNER, fSetTempOnly: true);
931 return true;
932 }
933 else
934 {
935 // view following player
936 int32_t iPlr = atoi(nptr: szObserverTarget);
937 if (ValidPlr(plr: iPlr))
938 {
939 pVP->Init(iPlayer: iPlr, fSetTempOnly: true);
940 return true;
941 }
942 }
943 }
944 return false;
945 }
946 // No valid command
947 return false;
948}
949
950bool C4MainMenu::ActivateObserver()
951{
952 // Safety: Viewport lost?
953 if (!Game.GraphicsSystem.GetViewport(iPlayer: NO_OWNER)) return false;
954 // Menu symbol/init
955 InitRefSym(fctSymbol: C4GUI::Icon::GetIconFacet(icoIconIndex: C4GUI::Ico_View), szEmpty: LoadResStr(id: C4ResStrTableKey::IDS_TEXT_VIEW), iPlayer: NO_OWNER, iExtra: C4MN_Extra_None, iExtraData: 0, iId: C4MN_Observer, iStyle: C4MN_Style_Context);
956 SetAlignment(C4MN_Align_Left | C4MN_Align_Bottom);
957 // Players added in Refill
958 Refill();
959 // Go back to main menu on close
960 SetCloseCommand("ActivateMenu:Main");
961 return true;
962}
963