| 1 | /* |
| 2 | * LegacyClonk |
| 3 | * |
| 4 | * Copyright (c) RedWolf Design |
| 5 | * Copyright (c) 2017-2022, The LegacyClonk Team and contributors |
| 6 | * |
| 7 | * Distributed under the terms of the ISC license; see accompanying file |
| 8 | * "COPYING" for details. |
| 9 | * |
| 10 | * "Clonk" is a registered trademark of Matthes Bender, used with permission. |
| 11 | * See accompanying file "TRADEMARK" for details. |
| 12 | * |
| 13 | * To redistribute this file separately, substitute the full license texts |
| 14 | * for the above references. |
| 15 | */ |
| 16 | |
| 17 | #include "C4Include.h" |
| 18 | #include "C4GameParameters.h" |
| 19 | |
| 20 | #include "C4Log.h" |
| 21 | #include "C4Components.h" |
| 22 | #include "C4Game.h" |
| 23 | #include "C4Gui.h" |
| 24 | #include "C4Wrappers.h" |
| 25 | |
| 26 | #include <iterator> |
| 27 | |
| 28 | // C4GameRes |
| 29 | |
| 30 | C4GameRes::C4GameRes() |
| 31 | : eType(NRT_Null), pResCore(nullptr), pNetRes(nullptr) {} |
| 32 | |
| 33 | C4GameRes::C4GameRes(const C4GameRes &Res) |
| 34 | : eType(Res.getType()), File(Res.getFile()), pResCore(Res.getResCore()), pNetRes(Res.getNetRes()) |
| 35 | { |
| 36 | if (pResCore && !pNetRes) |
| 37 | pResCore = new C4Network2ResCore(*pResCore); |
| 38 | } |
| 39 | |
| 40 | C4GameRes::~C4GameRes() |
| 41 | { |
| 42 | Clear(); |
| 43 | } |
| 44 | |
| 45 | C4GameRes &C4GameRes::operator=(const C4GameRes &Res) |
| 46 | { |
| 47 | Clear(); |
| 48 | eType = Res.getType(); |
| 49 | File = Res.getFile(); |
| 50 | pResCore = Res.getResCore(); |
| 51 | pNetRes = Res.getNetRes(); |
| 52 | if (pResCore && !pNetRes) |
| 53 | pResCore = new C4Network2ResCore(*pResCore); |
| 54 | return *this; |
| 55 | } |
| 56 | |
| 57 | void C4GameRes::Clear() |
| 58 | { |
| 59 | eType = NRT_Null; |
| 60 | File.Clear(); |
| 61 | if (!pNetRes) delete pResCore; |
| 62 | pResCore = nullptr; |
| 63 | pNetRes = nullptr; |
| 64 | } |
| 65 | |
| 66 | void C4GameRes::SetFile(C4Network2ResType enType, const char *sznFile) |
| 67 | { |
| 68 | assert(!pNetRes && !pResCore); |
| 69 | eType = enType; |
| 70 | File = sznFile; |
| 71 | } |
| 72 | |
| 73 | void C4GameRes::SetNetRes(C4Network2Res::Ref pnNetRes) |
| 74 | { |
| 75 | Clear(); |
| 76 | pNetRes = pnNetRes; |
| 77 | eType = pNetRes->getType(); |
| 78 | File = pNetRes->getFile(); |
| 79 | pResCore = &pNetRes->getCore(); |
| 80 | } |
| 81 | |
| 82 | void C4GameRes::CompileFunc(StdCompiler *pComp) |
| 83 | { |
| 84 | bool fCompiler = pComp->isCompiler(); |
| 85 | // Clear previous data for compiling |
| 86 | if (fCompiler) Clear(); |
| 87 | // Core is needed to decompile something meaningful |
| 88 | if (!fCompiler) assert(pResCore); |
| 89 | // De-/Compile core |
| 90 | pComp->Value(rStruct: mkPtrAdaptNoNull(rpObj&: const_cast<C4Network2ResCore *&>(pResCore))); |
| 91 | // Compile: Set type accordingly |
| 92 | if (fCompiler) |
| 93 | eType = pResCore->getType(); |
| 94 | } |
| 95 | |
| 96 | bool C4GameRes::Publish(C4Network2ResList *pNetResList) |
| 97 | { |
| 98 | assert(isPresent()); |
| 99 | // Already present? |
| 100 | if (pNetRes) return true; |
| 101 | // determine whether it's loadable |
| 102 | bool fAllowUnloadable = false; |
| 103 | if (eType == NRT_Definitions) fAllowUnloadable = true; |
| 104 | // Add to network resource list |
| 105 | C4Network2Res::Ref pNetRes = pNetResList->AddByFile(strFilePath: File.getData(), fTemp: false, eType, iResID: -1, szResName: nullptr, fAllowUnloadable); |
| 106 | if (!pNetRes) return false; |
| 107 | // Set resource |
| 108 | SetNetRes(pNetRes); |
| 109 | return true; |
| 110 | } |
| 111 | |
| 112 | bool C4GameRes::Load(C4Network2ResList *pNetResList) |
| 113 | { |
| 114 | assert(pResCore); |
| 115 | // Already present? |
| 116 | if (pNetRes) return true; |
| 117 | // Add to network resource list |
| 118 | C4Network2Res::Ref pNetRes = pNetResList->AddByCore(Core: *pResCore); |
| 119 | if (!pNetRes) return false; |
| 120 | // Set resource |
| 121 | SetNetRes(pNetRes); |
| 122 | return true; |
| 123 | } |
| 124 | |
| 125 | bool C4GameRes::InitNetwork(C4Network2ResList *pNetResList) |
| 126 | { |
| 127 | // Already initialized? |
| 128 | if (getNetRes()) |
| 129 | return true; |
| 130 | // Present? [Host] |
| 131 | if (isPresent()) |
| 132 | { |
| 133 | // Publish on network |
| 134 | if (!Publish(pNetResList)) |
| 135 | { |
| 136 | LogFatal(id: C4ResStrTableKey::IDS_NET_NOFILEPUBLISH, args: getFile()); |
| 137 | return false; |
| 138 | } |
| 139 | } |
| 140 | // Got a core? [Client] |
| 141 | else if (pResCore) |
| 142 | { |
| 143 | // Search/Load it |
| 144 | if (!Load(pNetResList)) |
| 145 | { |
| 146 | // Give some hints to why this might happen. |
| 147 | const char *szFilename = pResCore->getFileName(); |
| 148 | if (!pResCore->isLoadable()) |
| 149 | if (pResCore->getType() == NRT_System) |
| 150 | LogFatal(id: C4ResStrTableKey::IDS_NET_NOSAMESYSTEM, args&: szFilename); |
| 151 | else |
| 152 | LogFatal(id: C4ResStrTableKey::IDS_NET_NOSAMEANDTOOLARGE, args&: szFilename); |
| 153 | // Should not happen |
| 154 | else |
| 155 | LogFatal(id: C4ResStrTableKey::IDS_NET_NOVALIDCORE, args&: szFilename); |
| 156 | return false; |
| 157 | } |
| 158 | } |
| 159 | // Okay |
| 160 | return true; |
| 161 | } |
| 162 | |
| 163 | void C4GameRes::CalcHash() |
| 164 | { |
| 165 | if (!pNetRes) return; |
| 166 | pNetRes->CalculateSHA(); |
| 167 | } |
| 168 | |
| 169 | // C4GameResList |
| 170 | |
| 171 | C4GameResList &C4GameResList::operator=(const C4GameResList &List) |
| 172 | { |
| 173 | Clear(); |
| 174 | resList.reserve(n: List.resList.size()); |
| 175 | std::transform(first: List.resList.begin(), last: List.resList.end(), result: std::back_inserter(x&: resList), unary_op: [](const auto &res) |
| 176 | { |
| 177 | return std::make_unique<C4GameRes>(*res); |
| 178 | }); |
| 179 | return *this; |
| 180 | } |
| 181 | |
| 182 | C4GameResList::ResTypeIterator C4GameResList::iterRes(C4Network2ResType type) |
| 183 | { |
| 184 | return {type, resList}; |
| 185 | } |
| 186 | |
| 187 | void C4GameResList::Clear() |
| 188 | { |
| 189 | resList.clear(); |
| 190 | } |
| 191 | |
| 192 | bool C4GameResList::Load(const std::vector<std::string> &DefinitionFilenames) |
| 193 | { |
| 194 | // clear any prev |
| 195 | Clear(); |
| 196 | // no defs to be added? that's OK (LocalOnly) |
| 197 | if (DefinitionFilenames.size()) |
| 198 | { |
| 199 | for (const auto &def : DefinitionFilenames) |
| 200 | { |
| 201 | C4Group Def; |
| 202 | if (!Def.Open(szGroupName: def.c_str())) |
| 203 | { |
| 204 | LogFatal(id: C4ResStrTableKey::IDS_PRC_DEFNOTFOUND, args: def); |
| 205 | Def.Close(); |
| 206 | return false; |
| 207 | } |
| 208 | Def.Close(); |
| 209 | CreateByFile(eType: NRT_Definitions, szFile: def.c_str()); |
| 210 | } |
| 211 | } |
| 212 | // add System.c4g |
| 213 | CreateByFile(eType: NRT_System, C4CFN_System); |
| 214 | // add all instances of Material.c4g, except those inside the scenario file |
| 215 | C4Group *pMatParentGrp = nullptr; |
| 216 | while (pMatParentGrp = Game.GroupSet.FindGroup(C4GSCnt_Material, pAfter: pMatParentGrp)) |
| 217 | if (pMatParentGrp != &Game.ScenarioFile) |
| 218 | { |
| 219 | CreateByFile(eType: NRT_Material, szFile: (pMatParentGrp->GetFullName() + DirSep C4CFN_Material).getData()); |
| 220 | } |
| 221 | // add global Material.c4g |
| 222 | CreateByFile(eType: NRT_Material, C4CFN_Material); |
| 223 | // done; success |
| 224 | return true; |
| 225 | } |
| 226 | |
| 227 | C4GameRes *C4GameResList::CreateByFile(C4Network2ResType eType, const char *szFile) |
| 228 | { |
| 229 | // Create & set |
| 230 | C4GameRes *pRes = new C4GameRes; |
| 231 | pRes->SetFile(enType: eType, sznFile: szFile); |
| 232 | // Add to list |
| 233 | Add(pRes); |
| 234 | return pRes; |
| 235 | } |
| 236 | |
| 237 | bool C4GameResList::InitNetwork(C4Network2ResList *pNetResList) |
| 238 | { |
| 239 | // Check all resources without attached network resource object |
| 240 | for (const auto &it : resList) |
| 241 | { |
| 242 | if (!it->InitNetwork(pNetResList)) |
| 243 | return false; |
| 244 | } |
| 245 | // Success |
| 246 | return true; |
| 247 | } |
| 248 | |
| 249 | void C4GameResList::CalcHashes() |
| 250 | { |
| 251 | for (const auto &it : resList) |
| 252 | it->CalcHash(); |
| 253 | } |
| 254 | |
| 255 | bool C4GameResList::RetrieveFiles() |
| 256 | { |
| 257 | // wait for all resources |
| 258 | for (const auto &it : resList) |
| 259 | { |
| 260 | if (const C4Network2ResCore *const core{it->getResCore()}; core) |
| 261 | { |
| 262 | const std::string resName{std::format(fmt: "{}: {}" , args: LoadResStr(id: C4ResStrTableKey::IDS_DLG_DEFINITION), args: GetFilename(path: core->getFileName()))}; |
| 263 | if (!Game.Network.RetrieveRes(Core: *core, iTimeout: C4NetResRetrieveTimeout, szResName: resName.c_str())) |
| 264 | return false; |
| 265 | } |
| 266 | else |
| 267 | { |
| 268 | return false; |
| 269 | } |
| 270 | } |
| 271 | return true; |
| 272 | } |
| 273 | |
| 274 | void C4GameResList::Add(C4GameRes *pRes) |
| 275 | { |
| 276 | resList.emplace_back(args&: pRes); |
| 277 | } |
| 278 | |
| 279 | void C4GameResList::CompileFunc(StdCompiler *pComp) |
| 280 | { |
| 281 | bool fCompiler = pComp->isCompiler(); |
| 282 | // Clear previous data |
| 283 | int resCount = resList.size(); |
| 284 | // Compile resource count |
| 285 | pComp->Value(rStruct: mkNamingCountAdapt(iCount&: resCount, szName: "Resource" )); |
| 286 | // Create list |
| 287 | if (fCompiler) |
| 288 | { |
| 289 | Clear(); |
| 290 | resList.resize(new_size: resCount); |
| 291 | } |
| 292 | // Compile list |
| 293 | pComp->Value( |
| 294 | rStruct: mkNamingAdapt( |
| 295 | rValue: mkArrayAdaptS(array: resList.data(), size: resCount), |
| 296 | szName: "Resource" )); |
| 297 | } |
| 298 | |
| 299 | C4GameResList::ResTypeIterator::ResTypeIterator(C4Network2ResType type, const std::vector<std::unique_ptr<C4GameRes>> &resList) : resList{resList}, type{type}, it{resList.begin()} |
| 300 | { |
| 301 | filter(); |
| 302 | } |
| 303 | |
| 304 | void C4GameResList::ResTypeIterator::filter() |
| 305 | { |
| 306 | while (it != resList.end() && (*it)->getType() != type) |
| 307 | { |
| 308 | ++it; |
| 309 | } |
| 310 | } |
| 311 | |
| 312 | C4GameResList::ResTypeIterator &C4GameResList::ResTypeIterator::operator++() |
| 313 | { |
| 314 | ++it; |
| 315 | filter(); |
| 316 | return *this; |
| 317 | } |
| 318 | |
| 319 | C4GameRes &C4GameResList::ResTypeIterator::operator*() const |
| 320 | { |
| 321 | return **it; |
| 322 | } |
| 323 | |
| 324 | C4GameRes *C4GameResList::ResTypeIterator::operator->() const |
| 325 | { |
| 326 | return it->get(); |
| 327 | } |
| 328 | |
| 329 | bool C4GameResList::ResTypeIterator::operator==(const ResTypeIterator &other) const |
| 330 | { |
| 331 | return type == other.type && it == other.it; |
| 332 | } |
| 333 | |
| 334 | C4GameResList::ResTypeIterator C4GameResList::ResTypeIterator::end() const |
| 335 | { |
| 336 | auto ret = *this; |
| 337 | ret.it = resList.end(); |
| 338 | return ret; |
| 339 | } |
| 340 | |
| 341 | // *** C4GameParameters |
| 342 | |
| 343 | C4GameParameters::C4GameParameters() {} |
| 344 | |
| 345 | C4GameParameters::~C4GameParameters() {} |
| 346 | |
| 347 | void C4GameParameters::Clear() |
| 348 | { |
| 349 | League.Clear(); |
| 350 | LeagueAddress.Clear(); |
| 351 | Rules.Clear(); |
| 352 | Goals.Clear(); |
| 353 | ScenarioTitle.Ref(pnData: "No title" ); |
| 354 | Scenario.Clear(); |
| 355 | GameRes.Clear(); |
| 356 | Clients.Clear(); |
| 357 | PlayerInfos.Clear(); |
| 358 | RestorePlayerInfos.Clear(); |
| 359 | Teams.Clear(); |
| 360 | } |
| 361 | |
| 362 | bool C4GameParameters::Load(C4Group &hGroup, C4Scenario *pScenario, const char *szGameText, C4LangStringTable *pLang, const std::vector<std::string> &DefinitionFilenames) |
| 363 | { |
| 364 | // Clear previous data |
| 365 | Clear(); |
| 366 | |
| 367 | // Scenario |
| 368 | Scenario.SetFile(enType: NRT_Scenario, sznFile: hGroup.GetFullName().getData()); |
| 369 | |
| 370 | // Additional game resources |
| 371 | if (!GameRes.Load(DefinitionFilenames)) |
| 372 | return false; |
| 373 | |
| 374 | // Player infos (replays only) |
| 375 | if (pScenario->Head.Replay) |
| 376 | if (hGroup.FindEntry(C4CFN_PlayerInfos)) |
| 377 | PlayerInfos.Load(hGroup, C4CFN_PlayerInfos); |
| 378 | |
| 379 | // Savegame restore infos: Used for savegames to rejoin joined players |
| 380 | if (hGroup.FindEntry(C4CFN_SavePlayerInfos)) |
| 381 | { |
| 382 | // load to savegame info list |
| 383 | RestorePlayerInfos.Load(hGroup, C4CFN_SavePlayerInfos, pLang); |
| 384 | // transfer counter to allow for additional player joins in savegame resumes |
| 385 | PlayerInfos.SetIDCounter(RestorePlayerInfos.GetIDCounter()); |
| 386 | // in network mode, savegame players may be reassigned in the lobby |
| 387 | // in any mode, the final player restoration will be done in InitPlayers() |
| 388 | // dropping any players that could not be restored |
| 389 | } |
| 390 | else if (pScenario->Head.SaveGame) |
| 391 | { |
| 392 | // maybe there should be a player info file? (old-style savegame) |
| 393 | if (szGameText) |
| 394 | { |
| 395 | // then recreate the player infos to be restored from game text |
| 396 | RestorePlayerInfos.LoadFromGameText(pSource: szGameText); |
| 397 | // transfer counter |
| 398 | PlayerInfos.SetIDCounter(RestorePlayerInfos.GetIDCounter()); |
| 399 | } |
| 400 | } |
| 401 | |
| 402 | // Load teams |
| 403 | if (!Teams.Load(hGroup, pInitDefault: pScenario, pLang)) |
| 404 | { |
| 405 | LogFatal(id: C4ResStrTableKey::IDS_PRC_ERRORLOADINGTEAMS); return false; |
| 406 | } |
| 407 | |
| 408 | // Compile data |
| 409 | StdStrBuf Buf; |
| 410 | if (hGroup.LoadEntryString(C4CFN_Parameters, Buf)) |
| 411 | { |
| 412 | if (!CompileFromBuf_LogWarn<StdCompilerINIRead>( |
| 413 | TargetStruct: mkNamingAdapt(rValue: mkParAdapt(rObj&: *this, rPar: pScenario), szName: "Parameters" ), |
| 414 | SrcBuf: Buf, |
| 415 | C4CFN_Parameters)) |
| 416 | return false; |
| 417 | } |
| 418 | else |
| 419 | { |
| 420 | // Set default values |
| 421 | StdCompilerNull DefaultCompiler; |
| 422 | DefaultCompiler.Compile(rStruct: mkParAdapt(rObj&: *this, rPar: pScenario)); |
| 423 | |
| 424 | // Set random seed |
| 425 | RandomSeed = static_cast<int32_t>(time(timer: nullptr)); |
| 426 | |
| 427 | // Set control rate default |
| 428 | if (ControlRate < 0) |
| 429 | ControlRate = Config.Network.ControlRate; |
| 430 | |
| 431 | // network game? |
| 432 | IsNetworkGame = Game.NetworkActive; |
| 433 | |
| 434 | // FairCrew-flag by command line |
| 435 | if (!FairCrewForced) |
| 436 | UseFairCrew = !!Config.General.FairCrew; |
| 437 | if (!FairCrewStrength && UseFairCrew) |
| 438 | FairCrewStrength = Config.General.FairCrewStrength; |
| 439 | |
| 440 | // Auto frame skip by options |
| 441 | AutoFrameSkip = ::Config.Graphics.AutoFrameSkip; |
| 442 | } |
| 443 | |
| 444 | // enforce league settings |
| 445 | if (isLeague()) EnforceLeagueRules(pScenario); |
| 446 | |
| 447 | // Done |
| 448 | return true; |
| 449 | } |
| 450 | |
| 451 | void C4GameParameters::EnforceLeagueRules(C4Scenario *pScenario) |
| 452 | { |
| 453 | Scenario.CalcHash(); |
| 454 | GameRes.CalcHashes(); |
| 455 | Teams.EnforceLeagueRules(); |
| 456 | AllowDebug = false; |
| 457 | // Fair crew enabled in league, if not explicitely disabled by scenario |
| 458 | // Fair crew strengt to a moderately high value |
| 459 | if (!Game.Parameters.FairCrewForced) |
| 460 | { |
| 461 | Game.Parameters.UseFairCrew = true; |
| 462 | Game.Parameters.FairCrewForced = true; |
| 463 | Game.Parameters.FairCrewStrength = 20000; |
| 464 | } |
| 465 | if (pScenario) MaxPlayers = pScenario->Head.MaxPlayerLeague; |
| 466 | } |
| 467 | |
| 468 | bool C4GameParameters::CheckLeagueRulesStart(bool fFixIt) |
| 469 | { |
| 470 | // Additional checks for start parameters that are illegal in league games. |
| 471 | |
| 472 | if (!isLeague()) return true; |
| 473 | |
| 474 | bool fError = false; |
| 475 | std::string error; |
| 476 | |
| 477 | // league games: enforce one team per client |
| 478 | C4ClientPlayerInfos *pClient; C4PlayerInfo *pInfo; |
| 479 | for (int iClient = 0; pClient = Game.PlayerInfos.GetIndexedInfo(iIndex: iClient); iClient++) |
| 480 | { |
| 481 | bool fHaveTeam = false; int32_t iClientTeam; const char *szFirstPlayer; |
| 482 | for (int iInfo = 0; pInfo = pClient->GetPlayerInfo(iIndex: iInfo); iInfo++) |
| 483 | { |
| 484 | // Actual human players only |
| 485 | if (pInfo->GetType() != C4PT_User) continue; |
| 486 | |
| 487 | int32_t iTeam = pInfo->GetTeam(); |
| 488 | if (!fHaveTeam) |
| 489 | { |
| 490 | iClientTeam = iTeam; |
| 491 | szFirstPlayer = pInfo->GetName(); |
| 492 | fHaveTeam = true; |
| 493 | } |
| 494 | else if ((!Teams.IsCustom() && Game.C4S.Game.IsMelee()) || iTeam != iClientTeam) |
| 495 | { |
| 496 | error = LoadResStr(id: C4ResStrTableKey::IDS_MSG_NOSPLITSCREENINLEAGUE, args&: szFirstPlayer, args: pInfo->GetName()); |
| 497 | if (!fFixIt) |
| 498 | { |
| 499 | fError = true; |
| 500 | } |
| 501 | else |
| 502 | { |
| 503 | C4Client *pClient2 = Game.Clients.getClientByID(iID: pClient->GetClientID()); |
| 504 | if (!pClient2 || pClient2->isHost()) |
| 505 | fError = true; |
| 506 | else |
| 507 | Game.Clients.CtrlRemove(pClient: pClient2, szReason: error.c_str()); |
| 508 | } |
| 509 | } |
| 510 | } |
| 511 | } |
| 512 | |
| 513 | // Error? |
| 514 | if (fError) |
| 515 | { |
| 516 | if (Game.pGUI) |
| 517 | Game.pGUI->ShowMessageModal(szMessage: error.c_str(), szCaption: LoadResStr(id: C4ResStrTableKey::IDS_NET_ERR_LEAGUE), dwButtons: C4GUI::MessageDialog::btnOK, icoIcon: C4GUI::Ico_MeleeLeague); |
| 518 | else |
| 519 | LogNTr(message: error); |
| 520 | return false; |
| 521 | } |
| 522 | // All okay |
| 523 | return true; |
| 524 | } |
| 525 | |
| 526 | bool C4GameParameters::Save(C4Group &hGroup, C4Scenario *pScenario) |
| 527 | { |
| 528 | // Write Parameters.txt |
| 529 | const std::string parData{DecompileToBuf<StdCompilerINIWrite>( |
| 530 | SrcStruct: mkNamingAdapt(rValue: mkParAdapt(rObj&: *this, rPar: pScenario), szName: "Parameters" ))}; |
| 531 | StdStrBuf buf{parData.c_str(), parData.size()}; |
| 532 | if (!hGroup.Add(C4CFN_Parameters, pBuffer&: buf, fChild: false, fHoldBuffer: true)) |
| 533 | return false; |
| 534 | |
| 535 | // Done |
| 536 | return true; |
| 537 | } |
| 538 | |
| 539 | bool C4GameParameters::InitNetwork(C4Network2ResList *pResList) |
| 540 | { |
| 541 | // Scenario & material resource |
| 542 | if (!Scenario.InitNetwork(pNetResList: pResList)) |
| 543 | return false; |
| 544 | |
| 545 | // Other game resources |
| 546 | if (!GameRes.InitNetwork(pNetResList: pResList)) |
| 547 | return false; |
| 548 | |
| 549 | // Done |
| 550 | return true; |
| 551 | } |
| 552 | |
| 553 | void C4GameParameters::CompileFunc(StdCompiler *pComp, C4Scenario *pScenario) |
| 554 | { |
| 555 | pComp->Value(rStruct: mkNamingAdapt(rValue&: RandomSeed, szName: "RandomSeed" , rDefault: !pScenario ? 0 : pScenario->Head.RandomSeed)); |
| 556 | pComp->Value(rStruct: mkNamingAdapt(rValue&: StartupPlayerCount, szName: "StartupPlayerCount" , rDefault: 0)); |
| 557 | pComp->Value(rStruct: mkNamingAdapt(rValue&: MaxPlayers, szName: "MaxPlayers" , rDefault: !pScenario ? 0 : pScenario->Head.MaxPlayer)); |
| 558 | pComp->Value(rStruct: mkNamingAdapt(rValue&: UseFairCrew, szName: "UseFairCrew" , rDefault: !pScenario ? false : (pScenario->Head.ForcedFairCrew == C4SFairCrew_FairCrew))); |
| 559 | pComp->Value(rStruct: mkNamingAdapt(rValue&: FairCrewForced, szName: "FairCrewForced" , rDefault: !pScenario ? false : (pScenario->Head.ForcedFairCrew != C4SFairCrew_Free))); |
| 560 | pComp->Value(rStruct: mkNamingAdapt(rValue&: FairCrewStrength, szName: "FairCrewStrength" , rDefault: !pScenario ? 0 : pScenario->Head.FairCrewStrength)); |
| 561 | pComp->Value(rStruct: mkNamingAdapt(rValue&: AllowDebug, szName: "AllowDebug" , rDefault: true)); |
| 562 | pComp->Value(rStruct: mkNamingAdapt(rValue&: IsNetworkGame, szName: "IsNetworkGame" , rDefault: false)); |
| 563 | pComp->Value(rStruct: mkNamingAdapt(rValue&: ControlRate, szName: "ControlRate" , rDefault: -1)); |
| 564 | pComp->Value(rStruct: mkNamingAdapt(rValue&: AutoFrameSkip, szName: "AutoFrameSkip" , rDefault: false)); |
| 565 | pComp->Value(rStruct: mkNamingAdapt(rValue&: Rules, szName: "Rules" , rDefault: !pScenario ? C4IDList() : pScenario->Game.Rules)); |
| 566 | pComp->Value(rStruct: mkNamingAdapt(rValue&: Goals, szName: "Goals" , rDefault: !pScenario ? C4IDList() : pScenario->Game.Goals)); |
| 567 | pComp->Value(rStruct: mkNamingAdapt(rValue&: League, szName: "League" , rDefault: StdStrBuf())); |
| 568 | |
| 569 | // These values are either stored separately (see Load/Save) or |
| 570 | // don't make sense for savegames. |
| 571 | if (!pScenario) |
| 572 | { |
| 573 | pComp->Value(rStruct: mkNamingAdapt(rValue&: LeagueAddress, szName: "LeagueAddress" , rDefault: "" )); |
| 574 | |
| 575 | pComp->Value(rStruct: mkNamingAdapt(rValue&: ScenarioTitle, szName: "Title" , rDefault: "No title" )); |
| 576 | pComp->Value(rStruct: mkNamingAdapt(rValue&: Scenario, szName: "Scenario" )); |
| 577 | pComp->Value(rStruct&: GameRes); |
| 578 | |
| 579 | pComp->Value(rStruct: mkNamingAdapt(rValue&: PlayerInfos, szName: "PlayerInfos" )); |
| 580 | pComp->Value(rStruct: mkNamingAdapt(rValue&: RestorePlayerInfos, szName: "RestorePlayerInfos" )); |
| 581 | pComp->Value(rStruct: mkNamingAdapt(rValue&: Teams, szName: "Teams" )); |
| 582 | } |
| 583 | |
| 584 | pComp->Value(rStruct&: Clients); |
| 585 | } |
| 586 | |
| 587 | std::string C4GameParameters::GetGameGoalString() |
| 588 | { |
| 589 | // getting game goals from the ID list |
| 590 | // unfortunately, names cannot be deduced before object definitions are loaded |
| 591 | std::string result; |
| 592 | C4ID idGoal; |
| 593 | for (int32_t i = 0; i < Goals.GetNumberOfIDs(); ++i) |
| 594 | if (idGoal = Goals.GetID(index: i)) if (idGoal != C4ID_None) |
| 595 | { |
| 596 | if (Game.IsRunning) |
| 597 | { |
| 598 | C4Def *pDef = C4Id2Def(id: idGoal); |
| 599 | if (pDef) |
| 600 | { |
| 601 | if (!result.empty()) result += ", " ; |
| 602 | result += pDef->GetName(); |
| 603 | } |
| 604 | } |
| 605 | else |
| 606 | { |
| 607 | if (!result.empty()) result += ", " ; |
| 608 | result += C4IdText(id: idGoal); |
| 609 | } |
| 610 | } |
| 611 | // Max length safety |
| 612 | if (result.size() > C4MaxTitle) result.resize(n: C4MaxTitle); |
| 613 | // Compose desc string |
| 614 | if (!result.empty()) |
| 615 | return std::format(fmt: "{}: {}" , args: LoadResStr(id: C4ResStrTableKey::IDS_MENU_CPGOALS), args&: result); |
| 616 | else |
| 617 | return LoadResStr(id: C4ResStrTableKey::IDS_CTL_NOGOAL); |
| 618 | } |
| 619 | |