| 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 | |
| 34 | C4MainMenu::C4MainMenu() : C4Menu() // will be re-adjusted later |
| 35 | { |
| 36 | Clear(); |
| 37 | } |
| 38 | |
| 39 | void C4MainMenu::Default() |
| 40 | { |
| 41 | C4Menu::Default(); |
| 42 | Player = NO_OWNER; |
| 43 | } |
| 44 | |
| 45 | bool C4MainMenu::Init(C4FacetExSurface &fctSymbol, const char *szEmpty, int32_t iPlayer, int32_t , int32_t , 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 | |
| 52 | bool C4MainMenu::InitRefSym(const C4FacetEx &fctSymbol, const char *szEmpty, int32_t iPlayer, int32_t , int32_t , 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 | |
| 59 | bool 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 | |
| 124 | bool 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 | |
| 285 | void 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 | |
| 299 | void 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 | |
| 305 | void 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 | |
| 313 | void C4MainMenu::OnUserClose() |
| 314 | { |
| 315 | // just close |
| 316 | TryClose(fOK: false, fControl: true); |
| 317 | } |
| 318 | |
| 319 | void 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 | |
| 332 | bool 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 | |
| 382 | bool 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 | |
| 407 | bool 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 | |
| 422 | bool 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 | |
| 502 | bool 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 | |
| 522 | bool 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 | |
| 538 | bool 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 | |
| 553 | bool 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 | |
| 582 | bool 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 | |
| 643 | bool 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 | |
| 717 | bool 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 | |
| 734 | bool 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 | |
| 950 | bool 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 | |