1 /*
2 * OpenClonk, http://www.openclonk.org
3 *
4 * Copyright (c) 1998-2000, Matthes Bender
5 * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
6 * Copyright (c) 2009-2016, The OpenClonk 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 /* Main class to run the game */
19
20 #include "C4Include.h"
21 #include "C4ForbidLibraryCompilation.h"
22 #include "game/C4Game.h"
23
24 #include "C4Version.h"
25 #include "control/C4GameControl.h"
26 #include "control/C4GameParameters.h"
27 #include "control/C4GameSave.h"
28 #include "control/C4PlayerControl.h"
29 #include "control/C4PlayerInfo.h"
30 #include "control/C4Record.h"
31 #include "control/C4RoundResults.h"
32 #include "editor/C4Console.h"
33 #include "game/C4Application.h"
34 #include "game/C4FullScreen.h"
35 #include "game/C4GraphicsSystem.h"
36 #include "game/C4Viewport.h"
37 #include "graphics/C4GraphicsResource.h"
38 #include "gui/C4ChatDlg.h"
39 #include "gui/C4GameLobby.h"
40 #include "gui/C4GameMessage.h"
41 #include "gui/C4GameOverDlg.h"
42 #include "gui/C4LoaderScreen.h"
43 #include "gui/C4MessageInput.h"
44 #include "gui/C4MouseControl.h"
45 #include "gui/C4ScriptGuiWindow.h"
46 #include "gui/C4Startup.h"
47 #include "landscape/C4Landscape.h"
48 #include "landscape/C4MapScript.h"
49 #include "landscape/C4MassMover.h"
50 #include "landscape/C4Material.h"
51 #include "landscape/C4PXS.h"
52 #include "landscape/C4Particles.h"
53 #include "landscape/C4Sky.h"
54 #include "landscape/C4SolidMask.h"
55 #include "landscape/C4Texture.h"
56 #include "landscape/C4Weather.h"
57 #include "landscape/fow/C4FoW.h"
58 #include "lib/C4Random.h"
59 #include "lib/C4Stat.h"
60 #include "lib/StdMesh.h"
61 #include "network/C4League.h"
62 #include "network/C4Network2Dialogs.h"
63 #include "network/C4Network2Reference.h"
64 #include "network/C4Network2Stats.h"
65 #include "object/C4Command.h"
66 #include "object/C4Def.h"
67 #include "object/C4DefList.h"
68 #include "object/C4GameObjects.h"
69 #include "object/C4Object.h"
70 #include "object/C4ObjectCom.h"
71 #include "object/C4ObjectInfo.h"
72 #include "object/C4ObjectMenu.h"
73 #include "platform/C4FileMonitor.h"
74 #include "player/C4Player.h"
75 #include "player/C4PlayerList.h"
76 #include "player/C4RankSystem.h"
77 #include "script/C4AulDebug.h"
78 #include "script/C4AulExec.h"
79 #include "script/C4Effect.h"
80
81 #include <unordered_map>
82
83 class C4GameSec1Timer : public C4ApplicationSec1Timer
84 {
85 public:
C4GameSec1Timer()86 C4GameSec1Timer() { Application.Add(this); }
~C4GameSec1Timer()87 ~C4GameSec1Timer() override { Application.Remove(this); }
88 void OnSec1Timer() override;
89 };
90
91 static C4GameParameters GameParameters;
92 static C4ScenarioParameterDefs GameScenarioParameterDefs;
93 static C4ScenarioParameters GameStartupScenarioParameters;
94 static C4RoundResults GameRoundResults;
95 static C4Value GameGlobalSoundModifier;
96
C4Game()97 C4Game::C4Game():
98 ScenarioParameterDefs(GameScenarioParameterDefs),
99 Parameters(GameParameters),
100 StartupScenarioParameters(GameStartupScenarioParameters),
101 Clients(Parameters.Clients),
102 Teams(Parameters.Teams),
103 PlayerInfos(Parameters.PlayerInfos),
104 RestorePlayerInfos(Parameters.RestorePlayerInfos),
105 RoundResults(GameRoundResults),
106 Input(Control.Input),
107 KeyboardInput(C4KeyboardInput_Init()),
108 pSec1Timer(new C4GameSec1Timer()),
109 GlobalSoundModifier(GameGlobalSoundModifier)
110 {
111 Default();
112 }
113
~C4Game()114 C4Game::~C4Game()
115 {
116 // make sure no startup gfx remain loaded
117 C4Startup::Unload();
118 }
119
InitDefs()120 bool C4Game::InitDefs()
121 {
122 int32_t iDefs=0;
123 Log(LoadResStr("IDS_PRC_INITDEFS"));
124 int iDefResCount = 0;
125 C4GameRes *pDef;
126 for (pDef = Parameters.GameRes.iterRes(nullptr, NRT_Definitions); pDef; pDef = Parameters.GameRes.iterRes(pDef, NRT_Definitions))
127 ++iDefResCount;
128 int i = 0;
129 // Load specified defs
130 for (pDef = Parameters.GameRes.iterRes(nullptr, NRT_Definitions); pDef; pDef = Parameters.GameRes.iterRes(pDef, NRT_Definitions))
131 {
132 int iMinProgress = 25 + (25 * i) / iDefResCount;
133 int iMaxProgress = 25 + (25 * (i + 1)) / iDefResCount;
134 ++i;
135 iDefs+=::Definitions.Load(pDef->getFile(),C4D_Load_RX,Config.General.LanguageEx,&Application.SoundSystem,true,iMinProgress,iMaxProgress);
136
137 // Def load failure
138 if (::Definitions.LoadFailure) return false;
139 }
140
141 // Load for scenario file - ignore sys group here, because it has been loaded already
142 iDefs+=::Definitions.Load(ScenarioFile,C4D_Load_RX,Config.General.LanguageEx,&Application.SoundSystem,true,true,35,40, false);
143
144 // Absolutely no defs: we don't like that
145 if (!iDefs) { LogFatal(LoadResStr("IDS_PRC_NODEFS")); return false; }
146
147 // Check def engine version (should be done immediately on def load)
148 iDefs=::Definitions.CheckEngineVersion(C4XVER1,C4XVER2);
149 if (iDefs>0) { LogF(LoadResStr("IDS_PRC_DEFSINVC4X"),iDefs); }
150
151 // Check for unmet requirements
152 ::Definitions.CheckRequireDef();
153
154 // build quick access table
155 ::Definitions.BuildTable();
156
157 // handle skeleton appends and includes
158 ::Definitions.AppendAndIncludeSkeletons();
159
160 // Done
161 return true;
162 }
163
164
OpenScenario()165 bool C4Game::OpenScenario()
166 {
167
168 // Scenario from record stream
169 if (RecordStream.getSize())
170 {
171 StdStrBuf RecordFile;
172 if (!C4Playback::StreamToRecord(RecordStream.getData(), &RecordFile))
173 { LogFatal("[!] Could not process record stream data!"); return false; }
174 SCopy(RecordFile.getData(), ScenarioFilename, _MAX_PATH);
175 }
176
177 // Scenario filename check & log
178 if (!ScenarioFilename[0]) { LogFatal(LoadResStr("IDS_PRC_NOC4S")); return false; }
179 LogF(LoadResStr("IDS_PRC_LOADC4S"),ScenarioFilename);
180
181 // get parent folder, if it's ocf
182 pParentGroup = GroupSet.RegisterParentFolders(ScenarioFilename);
183
184 // open scenario
185 if (pParentGroup)
186 {
187 // open from parent group
188 if (!ScenarioFile.OpenAsChild(pParentGroup, GetFilename(ScenarioFilename)))
189 { LogF("%s: %s", LoadResStr("IDS_PRC_FILENOTFOUND"), (const char *)ScenarioFilename); return false; }
190 }
191 else
192 {
193 // open directly
194 if (!Reloc.Open(ScenarioFile, ScenarioFilename))
195 { LogF("%s: %s", LoadResStr("IDS_PRC_FILENOTFOUND"), (const char *)ScenarioFilename); return false; }
196 }
197
198 // Remember full (absolute) path
199 SCopy(ScenarioFile.GetFullName().getData(), ScenarioFilename, _MAX_PATH);
200
201 // add scenario to group
202 GroupSet.RegisterGroup(ScenarioFile, false, C4GSPrio_Scenario, C4GSCnt_Scenario);
203
204 // Read scenario core
205 if (!C4S.Load(ScenarioFile))
206 { LogFatal(LoadResStr("IDS_PRC_FILEINVALID")); return false; }
207
208 // Check minimum engine version
209 if (CompareVersion(C4S.Head.C4XVer[0],C4S.Head.C4XVer[1]) > 0)
210 {
211 LogFatal(FormatString(LoadResStr("IDS_PRC_NOREQC4X"), C4S.Head.C4XVer[0],C4S.Head.C4XVer[1]).getData());
212 return false;
213 }
214
215 // Add scenario origin to group set
216 if (C4S.Head.Origin.getLength() && !ItemIdentical(C4S.Head.Origin.getData(), ScenarioFilename))
217 GroupSet.RegisterParentFolders(C4S.Head.Origin.getData());
218
219 // Scenario definition preset
220 StdStrBuf sDefinitionFilenames;
221 if (!C4S.Definitions.AllowUserChange && C4S.Definitions.GetModules(&sDefinitionFilenames))
222 {
223 SCopy(sDefinitionFilenames.getData(), DefinitionFilenames, (sizeof DefinitionFilenames)-1);
224 if (DefinitionFilenames[0]) Log(LoadResStr("IDS_PRC_SCEOWNDEFS"));
225 else Log(LoadResStr("IDS_PRC_LOCALONLY"));
226 }
227
228 // Check mission access
229 #ifndef USE_CONSOLE
230 #ifndef _DEBUG
231 if (C4S.Head.MissionAccess[0])
232 if (!Application.isEditor)
233 if (!SIsModule(Config.General.MissionAccess, C4S.Head.MissionAccess.c_str()))
234 { LogFatal(LoadResStr("IDS_PRC_NOMISSIONACCESS")); return false; }
235 #endif
236 #endif
237
238 // Title
239 C4Language::LoadComponentHost(&Title, ScenarioFile, C4CFN_Title, Config.General.LanguageEx);
240 if (!Title.GetLanguageString(Config.General.LanguageEx, ScenarioTitle))
241 ScenarioTitle = C4S.Head.Title;
242
243 // String tables
244 C4Language::LoadComponentHost(&ScenarioLangStringTable, ScenarioFile, C4CFN_ScriptStringTbl, Config.General.LanguageEx);
245
246 // Custom scenario parameter definitions. Load even as network client to get localized option names
247 ScenarioParameterDefs.Load(ScenarioFile, &ScenarioLangStringTable);
248
249 // Load parameters (not as network client, because then team info has already been sent by host)
250 if (!Network.isEnabled() || Network.isHost())
251 if (!Parameters.Load(ScenarioFile, &C4S, GameText.GetData(), &ScenarioLangStringTable, DefinitionFilenames, &StartupScenarioParameters))
252 return false;
253
254 SetInitProgress(4);
255
256 // If scenario is a directory: Watch for changes
257 if (!ScenarioFile.IsPacked() && pFileMonitor)
258 Game.pFileMonitor->AddDirectory(ScenarioFile.GetFullName().getData());
259
260 return true;
261 }
262
CloseScenario()263 void C4Game::CloseScenario()
264 {
265 // close scenario
266 ScenarioFile.Close();
267 GroupSet.CloseFolders();
268 pParentGroup = nullptr;
269 // remove if temporary
270 if (TempScenarioFile)
271 {
272 EraseItem(TempScenarioFile.getData());
273 TempScenarioFile.Clear();
274 }
275 // clear scenario section
276 // this removes any temp files, which may yet need to be used by any future features
277 // so better don't do this too early (like, in C4Game::Clear)
278 if (pScenarioSections) { delete pScenarioSections; pScenarioSections=pCurrentScenarioSection=nullptr;}
279 }
280
281
PreInit()282 bool C4Game::PreInit()
283 {
284 // init extra root group
285 // this loads font definitions in this group as well
286 // the function may return false, if no extra group is present - that is OK
287 Extra.InitGroup();
288
289 RandomSeed = time(nullptr);
290 // Randomize
291 FixRandom(RandomSeed);
292 // Timer flags
293 GameGo=false;
294 // init message input (default commands)
295 MessageInput.Init();
296 Game.SetInitProgress(31.0f);
297 // init keyboard input (default keys, plus overloads)
298 if (!InitKeyboard())
299 { LogFatal(LoadResStr("IDS_ERR_NOKEYBOARD")); return false; }
300 // Load string table
301 UpdateLanguage();
302 // Player keyboard input: Key definitions and default sets
303 if (!InitPlayerControlSettings()) return false;
304 Game.SetInitProgress(32.0f);
305 // Rank system
306 ::DefaultRanks.Init(Config.GetSubkeyPath("ClonkRanks"), LoadResStr("IDS_GAME_DEFRANKS"), 1000);
307 Game.SetInitProgress(33.0f);
308
309 // Graphics system (required for GUI)
310 if (!GraphicsSystem.Init())
311 { LogFatal(LoadResStr("IDS_ERR_NOGFXSYS")); return false; }
312
313 // load GUI
314 #ifndef USE_CONSOLE
315 C4Rect r;
316 if (Application.isEditor)
317 Console.GetSize(&r);
318 else
319 FullScreen.GetSize(&r);
320 pGUI->Init(0, 0, r.Wdt, r.Hgt);
321 #endif
322
323 fPreinited = true;
324
325 return true;
326 }
327
Init()328 bool C4Game::Init()
329 {
330 C4ValueNumbers numbers;
331 IsRunning = false;
332
333 InitProgress=0; LastInitProgress=0;
334 SetInitProgress(0);
335
336 // reinit keyboard to reflect any config changes that might have been done
337 // this is a good time to do it, because no GUI dialogs are opened
338 if (!InitKeyboard()) LogFatal(LoadResStr("IDS_ERR_NOKEYBOARD"));
339
340 // start log pos (used by startup)
341 StartupLogPos=GetLogPos();
342 fQuitWithError = false;
343 C4GameLobby::UserAbort = false;
344
345 // Store a start time that identifies this game on this host
346 StartTime = time(nullptr);
347
348 // Get PlayerFilenames from Config, if ParseCommandLine did not fill some in
349 // Must be done here, because InitGame calls PlayerInfos.InitLocal
350 if (!*PlayerFilenames)
351 {
352 SCopy(Config.General.Participants, PlayerFilenames, std::min(sizeof(PlayerFilenames), sizeof(Config.General.Participants)) - 1);
353 }
354
355 // Join a game?
356 if (pJoinReference || *DirectJoinAddress)
357 {
358
359 if (!GraphicsSystem.pLoaderScreen)
360 {
361 // init extra; needed for loader screen
362 Log(LoadResStr("IDS_PRC_INITEXTRA"));
363 if (!Extra.Init())
364 { LogFatal(LoadResStr("IDS_PRC_ERREXTRA")); return false; }
365
366 // init loader
367 if (!Application.isEditor && !GraphicsSystem.InitLoaderScreen(C4S.Head.Loader.c_str()))
368 { LogFatal(LoadResStr("IDS_PRC_ERRLOADER")); return false; }
369 }
370
371 SetInitProgress(5);
372
373 // Initialize network
374 if (pJoinReference)
375 {
376 // By reference
377 bool fSuccess = InitNetworkFromReference(*pJoinReference);
378 pJoinReference.reset();
379 if (!fSuccess)
380 return false;
381 }
382 else if (SEqual2(DirectJoinAddress, DirectJoinFilePrefix))
383 {
384 // By reference serialized to temp file
385 if (!InitNetworkFromReferenceFile(DirectJoinAddress + strlen(DirectJoinFilePrefix)))
386 return false;
387 }
388 else
389 {
390 // By address
391 if (!InitNetworkFromAddress(DirectJoinAddress))
392 return false;
393 }
394
395 // check wether console mode is allowed
396 if (Application.isEditor && !Parameters.AllowDebug)
397 { LogFatal(LoadResStr("IDS_TEXT_JOININCONSOLEMODENOTALLOW")); return false; }
398
399 // do lobby (if desired)
400 if (Network.isLobbyActive())
401 if (!Network.DoLobby())
402 return false;
403
404 // get scenario
405 char szScenario[_MAX_PATH+1];
406 SetInitProgress(6);
407 if (!Network.RetrieveScenario(szScenario)) return false;
408
409 // open new scenario
410 SCopy(szScenario, ScenarioFilename, _MAX_PATH);
411 if (!OpenScenario()) return false;
412 TempScenarioFile.Copy(ScenarioFilename);
413
414 // get everything else
415 if (!Parameters.GameRes.RetrieveFiles()) return false;
416
417 // Check network game data scenario type (safety)
418 if (!C4S.Head.NetworkGame)
419 { LogFatal(LoadResStr("IDS_NET_NONETGAME")); return false; }
420
421 SetInitProgress(7);
422
423 }
424
425 // Local game or host?
426 else
427 {
428
429 // Open scenario
430 if (!OpenScenario())
431 { LogFatal(LoadResStr("IDS_PRC_FAIL")); return false; }
432
433 // init extra; needed for loader screen
434 Log(LoadResStr("IDS_PRC_INITEXTRA"));
435 if (!Extra.Init())
436 { LogFatal(LoadResStr("IDS_PRC_ERREXTRA")); return false; }
437
438 // init loader
439 if (!Application.isEditor && !GraphicsSystem.InitLoaderScreen(C4S.Head.Loader.c_str()))
440 { LogFatal(LoadResStr("IDS_PRC_ERRLOADER")); return false; }
441
442 // Init network
443 if (!InitNetworkHost()) return false;
444 SetInitProgress(7);
445
446 }
447
448 // now free all startup gfx to make room for game gfx
449 C4Startup::Unload();
450
451 // Init debugmode
452 DebugMode = !!Application.isEditor;
453 if (Config.General.AlwaysDebug)
454 DebugMode = true;
455 if (!Parameters.AllowDebug)
456 DebugMode = false;
457
458 // Init game
459 if (!InitGame(ScenarioFile, IM_Normal, true, &numbers)) return false;
460
461 // Network final init
462 if (Network.isEnabled())
463 {
464 if (!Network.FinalInit()) return false;
465 }
466 // non-net may have to synchronize now to keep in sync with replays
467 // also needs to synchronize to update transfer zones
468 else
469 {
470 // - would kill DebugRec-sync for runtime debugrec starts
471 C4DebugRecOff DBGRECOFF(!!C4S.Head.SaveGame);
472 SyncClearance();
473 Synchronize(false);
474 }
475
476 // Init players
477 if (!InitPlayers(&numbers)) return false;
478 SetInitProgress(98);
479
480 // Final init
481 if (!InitGameFinal(IM_Normal)) return false;
482 SetInitProgress(99);
483
484 // Sound modifier from savegames
485 if (GlobalSoundModifier) SetGlobalSoundModifier(GlobalSoundModifier._getPropList());
486
487 // Message board and upper board
488 if (!Application.isEditor)
489 {
490 InitFullscreenComponents(true);
491 }
492
493 // Default fullscreen menu, in case any old surfaces are left (extra safety)
494 FullScreen.CloseMenu();
495
496 // start statistics (always for now. Make this a config?)
497 pNetworkStatistics = std::make_unique<C4Network2Stats>();
498
499 // clear loader screen
500 if (GraphicsSystem.pLoaderScreen)
501 {
502 delete GraphicsSystem.pLoaderScreen;
503 GraphicsSystem.pLoaderScreen=nullptr;
504 }
505
506 // game running now!
507 IsRunning = true;
508
509 // Start message
510 Log(LoadResStr(C4S.Head.NetworkGame ? "IDS_PRC_JOIN" : C4S.Head.SaveGame ? "IDS_PRC_RESUME" : "IDS_PRC_START"));
511
512 // set non-exclusive GUI
513 pGUI->SetExclusive(false);
514
515 // after GUI is made non-exclusive, recheck the scoreboard
516 Scoreboard.DoDlgShow(0, false);
517 SetInitProgress(100);
518
519 // and redraw background
520 GraphicsSystem.InvalidateBg();
521
522 // Notify editor
523 Console.InitGame();
524
525 return true;
526 }
527
SetScenarioFilename(const char * c4sfile)528 void C4Game::SetScenarioFilename(const char * c4sfile)
529 {
530 SCopy(c4sfile,ScenarioFilename,_MAX_PATH);
531 if (SEqualNoCase(GetFilename(c4sfile),"scenario.txt"))
532 {
533 if (GetFilename(ScenarioFilename) != ScenarioFilename) *(GetFilename(ScenarioFilename) - 1) = 0;
534 }
535 }
536
Clear()537 void C4Game::Clear()
538 {
539 pFileMonitor.reset();
540 // fade out music
541 Application.MusicSystem.FadeOut(2000);
542 // game no longer running
543 IsRunning = false;
544 PointersDenumerated = false;
545
546 C4ST_SHOWSTAT
547 // C4ST_RESET
548
549 // Evaluation
550 if (GameOver)
551 {
552 if (!Evaluated) Evaluate();
553 }
554
555 // stop statistics
556 pNetworkStatistics.reset();
557 C4AulProfiler::Abort();
558
559 // next mission (shoud have been transferred to C4Application now if next mission was desired)
560 NextMission.Clear(); NextMissionText.Clear(); NextMissionDesc.Clear();
561
562 // Clear control
563 Network.Clear();
564 Control.Clear();
565 MouseControl.Clear();
566 KeyboardInput.Clear();
567 PlayerControlUserAssignmentSets.Clear();
568 PlayerControlDefaultAssignmentSets.Clear();
569 PlayerControlDefs.Clear();
570
571 // Clear game info
572 Scoreboard.Clear();
573 Players.Clear();
574 Parameters.Clear();
575 RoundResults.Clear();
576 C4S.Clear();
577 ScenarioParameterDefs.Clear();
578 StartupScenarioParameters.Clear();
579 Info.Clear();
580 Title.Clear();
581 Names.Clear();
582 GameText.Clear();
583 RecordDumpFile.Clear();
584 RecordStream.Clear();
585
586 #ifdef WITH_QT_EDITOR
587 // clear console pointers held into script engine
588 ::Console.EditCursor.Clear();
589 ::Console.ClearGamePointers();
590 #endif
591 // notify editor
592 Console.CloseGame();
593
594 // Clear the particles before cleaning up the objects.
595 Particles.Clear();
596 DeleteObjects(true);
597
598 // exit gui
599 pGUI->Clear();
600 ScriptGuiRoot.reset();
601
602 // Clear landscape
603 Weather.Clear();
604 Landscape.Clear();
605 PXS.Clear();
606 ::MaterialMap.Clear();
607 TextureMap.Clear(); // texture map *MUST* be cleared after the materials, because of the patterns!
608 PathFinder.Clear();
609 TransferZones.Clear();
610
611 ::Messages.Clear();
612 MessageInput.Clear();
613 SetGlobalSoundModifier(nullptr); // must be called before script engine clear
614 Application.SoundSystem.Modifiers.Clear(); // free some prop list pointers
615
616 // Clear script engine
617 ::MapScript.Clear();
618 ::Definitions.Clear();
619 ::GameScript.Clear();
620 C4PropListNumbered::ClearShelve(); // may be nonempty if there was a fatal error during section load
621 ScriptEngine.Clear();
622 pScenarioObjectsScript = nullptr;
623 // delete any remaining prop lists from circular chains
624 C4PropListNumbered::ClearNumberedPropLists();
625 C4PropListScript::ClearScriptPropLists();
626
627 // Clear translation tables
628 MainSysLangStringTable.Clear();
629 ScenarioLangStringTable.Clear();
630
631 // Cleanup remaining open scenario files
632 CloseScenario();
633 GroupSet.Clear();
634 ::Application.MusicSystem.ClearGame();
635 Application.SoundSystem.Clear(); // will be re-inited by application pre-init if running from startup system
636
637 // Clear remaining graphics
638 #ifndef USE_CONSOLE
639 ::FontLoader.Clear();
640 #endif
641 ::MeshMaterialManager.Clear();
642 GraphicsSystem.Clear();
643
644 // global fullscreen class is not cleared, because it holds the carrier window
645 // but the menu must be cleared (maybe move Fullscreen.Menu somewhere else?)
646 FullScreen.CloseMenu();
647
648 // Message
649 // avoid double message by not printing it if no restbl is loaded
650 // this would log an "[Undefined]" only, anyway
651 // (could abort the whole clear-procedure here, btw?)
652 if (::Languages.HasStringTable()) Log(LoadResStr("IDS_CNS_GAMECLOSED"));
653
654 // clear game starting parameters
655 *DefinitionFilenames = *DirectJoinAddress = *ScenarioFilename = *PlayerFilenames = 0;
656
657 // join reference
658 pJoinReference.reset();
659
660 // okay, game cleared now. Remember log section
661 QuitLogPos = GetLogPos();
662
663 fPreinited = false;
664 C4PropListNumbered::ResetEnumerationIndex();
665
666 // FIXME: remove this
667 Default();
668 }
669
GameOverCheck()670 bool C4Game::GameOverCheck()
671 {
672 bool fDoGameOver = false;
673
674 // Only every 35 ticks
675 if (::Game.iTick35) return false;
676
677 // do not GameOver in replay
678 if (Control.isReplay()) return false;
679
680 // All players eliminated: game over
681 if (!Players.GetCountNotEliminated())
682 fDoGameOver=true;
683
684 // Message
685 if (fDoGameOver) DoGameOver();
686
687 return GameOver;
688 }
689
690 C4ST_NEW(ControlRcvStat, "C4Game::Execute ReceiveControl")
691 C4ST_NEW(ControlStat, "C4Game::Execute ExecuteControl")
692 C4ST_NEW(ExecObjectsStat, "C4Game::Execute ExecObjects")
693 C4ST_NEW(GEStats, "C4Game::Execute pGlobalEffects->Execute")
694 C4ST_NEW(PXSStat, "C4Game::Execute PXS.Execute")
695 C4ST_NEW(DynPartStat, "C4Game::Execute Particles.Execute")
696 C4ST_NEW(MassMoverStat, "C4Game::Execute MassMover.Execute")
697 C4ST_NEW(WeatherStat, "C4Game::Execute Weather.Execute")
698 C4ST_NEW(PlayersStat, "C4Game::Execute Players.Execute")
699 C4ST_NEW(LandscapeStat, "C4Game::Execute Landscape.Execute")
700 C4ST_NEW(MusicSystemStat, "C4Game::Execute MusicSystem.Execute")
701 C4ST_NEW(MessagesStat, "C4Game::Execute Messages.Execute")
702
703 #define EXEC_S(Expressions, Stat) \
704 { C4ST_START(Stat) Expressions C4ST_STOP(Stat) }
705
706 #define EXEC_S_DR(Expressions, Stat, DebugRecName) { if (Config.General.DebugRec) AddDbgRec(RCT_Block, DebugRecName, 6); EXEC_S(Expressions, Stat) }
707 #define EXEC_DR(Expressions, DebugRecName) { if (Config.General.DebugRec) AddDbgRec(RCT_Block, DebugRecName, 6); Expressions }
708
Execute()709 bool C4Game::Execute() // Returns true if the game is over
710 {
711
712 // Let's go
713 GameGo = true;
714
715 // Network
716 Network.Execute();
717
718 // Prepare control
719 bool fControl;
720 EXEC_S( fControl = Control.Prepare(); , ControlStat )
721 if (!fControl) return false; // not ready yet: wait
722
723 // Halt
724 if (HaltCount) return false;
725
726 if (Config.General.DebugRec)
727 Landscape.DoRelights();
728
729 // Execute the control
730 Control.Execute();
731 if (!IsRunning) return false;
732
733 // Ticks
734 EXEC_DR( Ticks(); , "Ticks")
735
736 if (Config.General.DebugRec)
737 // debugrec
738 AddDbgRec(RCT_DbgFrame, &FrameCounter, sizeof(int32_t));
739
740 // allow the particle system to execute the next frame BEFORE the other game stuff is calculated since it will run in parallel to the main thread
741 Particles.CalculateNextStep();
742
743 // Game
744
745 EXEC_S( ExecObjects(); , ExecObjectsStat )
746 EXEC_S_DR( C4Effect::Execute(&ScriptEngine.pGlobalEffects);
747 C4Effect::Execute(&GameScript.pScenarioEffects);
748 , GEStats , "GEEx\0");
749 EXEC_S_DR( PXS.Execute(); , PXSStat , "PXSEx")
750 EXEC_S_DR( MassMover.Execute(); , MassMoverStat , "MMvEx")
751 EXEC_S_DR( Weather.Execute(); , WeatherStat , "WtrEx")
752 EXEC_S_DR( Landscape.Execute(); , LandscapeStat , "LdsEx")
753 EXEC_S_DR( Players.Execute(); , PlayersStat , "PlrEx")
754 EXEC_S_DR( ::Messages.Execute(); , MessagesStat , "MsgEx")
755
756 EXEC_DR( MouseControl.Execute(); , "Input")
757
758 EXEC_DR( GameOverCheck(); , "Misc\0")
759
760 Control.DoSyncCheck();
761
762 // Evaluation; Game over dlg
763 if (GameOver)
764 {
765 if (!Evaluated) Evaluate();
766 if (!GameOverDlgShown) ShowGameOverDlg();
767 }
768
769 // show stat each 1000 ticks
770 if (!(FrameCounter % 1000))
771 {
772 C4ST_SHOWPARTSTAT(FrameCounter)
773 C4ST_RESETPART
774 }
775
776 if (Config.General.DebugRec)
777 {
778 AddDbgRec(RCT_Block, "eGame", 6);
779 Landscape.DoRelights();
780 }
781
782 return true;
783 }
784
InitFullscreenComponents(bool fRunning)785 void C4Game::InitFullscreenComponents(bool fRunning)
786 {
787 // It can happen that this is called before graphics are loaded due to
788 // an early OnResolutionChanged() call. Ignore it, the message board,
789 // upper board and viewports will be initialized within the regular
790 // startup sequence then.
791 if(!GraphicsResource.IsInitialized()) return;
792
793 // fullscreen message board
794 C4Facet cgo;
795 cgo.Set(FullScreen.pSurface, 0, 0, C4GUI::GetScreenWdt(), C4GUI::GetScreenHgt());
796 GraphicsSystem.MessageBoard->Init(cgo, !fRunning);
797 if (fRunning)
798 {
799 // running game: Message board upper board and viewports
800 C4Facet cgo2;
801 cgo2.Set(FullScreen.pSurface, 0, 0, C4GUI::GetScreenWdt(), C4UpperBoardHeight);
802 GraphicsSystem.UpperBoard.Init(cgo2);
803 ::Viewports.RecalculateViewports();
804 }
805 }
806
InitMaterialTexture()807 bool C4Game::InitMaterialTexture()
808 {
809
810 // Clear old data
811 TextureMap.Clear();
812 ::MaterialMap.Clear();
813
814 // Check for scenario local materials
815 bool fHaveScenMaterials = Game.ScenarioFile.FindEntry(C4CFN_Material);
816
817 // Load all materials
818 C4GameRes *pMatRes = nullptr;
819 bool fFirst = true, fOverloadMaterials = true, fOverloadTextures = true;
820 long tex_count = 0, mat_count = 0;
821 while (fOverloadMaterials || fOverloadTextures)
822 {
823
824 // Are there any scenario local materials that need to be looked at firs?
825 C4Group Mats;
826 if (fHaveScenMaterials)
827 {
828 if (!Mats.OpenAsChild(&Game.ScenarioFile, C4CFN_Material))
829 return false;
830 // Once only
831 fHaveScenMaterials = false;
832 }
833 else
834 {
835 // Find next external material source
836 pMatRes = Game.Parameters.GameRes.iterRes(pMatRes, NRT_Material);
837 if (!pMatRes) break;
838 if (!Reloc.Open(Mats, pMatRes->getFile()))
839 return false;
840 }
841
842 // Texture loader will access out of order. Pre-cache the small text-files to prevent rewind.
843 Mats.PreCacheEntries(C4CFN_TexMap);
844 Mats.PreCacheEntries(C4CFN_MaterialFiles, true);
845
846 // First material file? Load texture map.
847 bool fNewOverloadMaterials = false, fNewOverloadTextures = false;
848 if (fFirst)
849 {
850 long tme_count = TextureMap.LoadMap(Mats, C4CFN_TexMap, &fNewOverloadMaterials, &fNewOverloadTextures);
851 LogF(LoadResStr("IDS_PRC_TEXMAPENTRIES"),tme_count);
852 // Only once
853 fFirst = false;
854 }
855 else
856 {
857 // Check overload-flags only
858 if (!C4TextureMap::LoadFlags(Mats, C4CFN_TexMap, &fNewOverloadMaterials, &fNewOverloadTextures))
859 fOverloadMaterials = fOverloadTextures = false;
860 }
861
862 // Load textures
863 if (fOverloadTextures)
864 {
865 int iTexs = TextureMap.LoadTextures(Mats);
866 // Automatically continue search if no texture was found
867 if (!iTexs) fNewOverloadTextures = true;
868 tex_count += iTexs;
869 }
870
871 // Load materials
872 if (fOverloadMaterials)
873 {
874 int iMats = ::MaterialMap.Load(Mats);
875 // Automatically continue search if no material was found
876 if (!iMats) fNewOverloadMaterials = true;
877 mat_count += iMats;
878 }
879
880 // Set flags
881 fOverloadTextures = fNewOverloadTextures;
882 fOverloadMaterials = fNewOverloadMaterials;
883 }
884
885 // Logs
886 LogF(LoadResStr("IDS_PRC_TEXTURES"),tex_count);
887 LogF(LoadResStr("IDS_PRC_MATERIALS"),mat_count);
888
889 // Load material enumeration
890 if (!::MaterialMap.LoadEnumeration(ScenarioFile))
891 { LogFatal(LoadResStr("IDS_PRC_NOMATENUM")); return false; }
892
893 // Initialize texture map
894 TextureMap.Init();
895
896 // Cross map mats (after texture init, because Material-Texture-combinations are used)
897 if (!::MaterialMap.CrossMapMaterials(C4S.Landscape.Material.c_str())) return false;
898
899 // get material script funcs
900 ::MaterialMap.UpdateScriptPointers();
901
902 return true;
903 }
904
ClearObjectPtrs(C4Object * pObj)905 void C4Game::ClearObjectPtrs(C4Object *pObj)
906 {
907 // May not call Objects.ClearPointers() because that would
908 // remove pObj from primary list and pObj is to be kept
909 // until CheckObjectRemoval().
910 for (C4Object *cObj : Objects)
911 {
912 cObj->ClearPointers(pObj);
913 }
914 // check in inactive objects as well
915 for (C4Object *cObj : Objects.InactiveObjects)
916 {
917 cObj->ClearPointers(pObj);
918 }
919 }
920
ClearPointers(C4Object * pObj)921 void C4Game::ClearPointers(C4Object * pObj)
922 {
923 ::AulExec.ClearPointers(pObj);
924 ::Objects.ForeObjects.ClearPointers(pObj);
925 ::Messages.ClearPointers(pObj);
926 ClearObjectPtrs(pObj);
927 Application.SoundSystem.ClearPointers(pObj);
928 ::Players.ClearPointers(pObj);
929 ::Viewports.ClearPointers(pObj);
930 ::MessageInput.ClearPointers(pObj);
931 ::Console.ClearPointers(pObj);
932 ::MouseControl.ClearPointers(pObj);
933 ScriptGuiRoot->ClearPointers(pObj);
934 TransferZones.ClearPointers(pObj);
935 if (::ScriptEngine.pGlobalEffects)
936 ::ScriptEngine.pGlobalEffects->ClearPointers(pObj);
937 if (::GameScript.pScenarioEffects)
938 ::GameScript.pScenarioEffects->ClearPointers(pObj);
939 ::Landscape.ClearPointers(pObj);
940 }
941
TogglePause()942 bool C4Game::TogglePause()
943 {
944 // pause toggling disabled during round evaluation
945 if (C4GameOverDlg::IsShown()) return false;
946 // otherwise, toggle
947 if (IsPaused()) return Unpause(); else return Pause();
948 }
949
Pause()950 bool C4Game::Pause()
951 {
952 // already paused?
953 if (IsPaused()) return false;
954 // pause by net?
955 if (::Network.isEnabled())
956 {
957 // league? Vote...
958 if (Parameters.isLeague() && !Game.Evaluated)
959 {
960 ::Network.Vote(VT_Pause, true, true);
961 return false;
962 }
963 // host only
964 if (!::Network.isHost()) return true;
965 ::Network.Pause();
966 }
967 else
968 {
969 // pause game directly
970 Game.HaltCount = true;
971 }
972 Console.UpdateHaltCtrls(IsPaused());
973 return true;
974 }
975
Unpause()976 bool C4Game::Unpause()
977 {
978 // already paused?
979 if (!IsPaused()) return false;
980 // pause by net?
981 if (::Network.isEnabled())
982 {
983 // league? Vote...
984 if (Parameters.isLeague() && !Game.Evaluated)
985 {
986 ::Network.Vote(VT_Pause, true, false);
987 return false;
988 }
989 // host only
990 if (!::Network.isHost()) return true;
991 ::Network.Start();
992 }
993 else
994 {
995 // unpause game directly
996 Game.HaltCount = false;
997 }
998 Console.UpdateHaltCtrls(IsPaused());
999 return true;
1000 }
1001
IsPaused()1002 bool C4Game::IsPaused()
1003 {
1004 // pause state defined either by network or by game halt count
1005 if (::Network.isEnabled())
1006 return !::Network.isRunning();
1007 return !!HaltCount;
1008 }
1009
1010
NewObject(C4PropList * pDef,C4Object * pCreator,int32_t iOwner,C4ObjectInfo * pInfo,int32_t iX,int32_t iY,int32_t iR,C4Real xdir,C4Real ydir,C4Real rdir,int32_t iCon,int32_t iController,bool grow_from_center)1011 C4Object* C4Game::NewObject( C4PropList *pDef, C4Object *pCreator,
1012 int32_t iOwner, C4ObjectInfo *pInfo,
1013 int32_t iX, int32_t iY, int32_t iR,
1014 C4Real xdir, C4Real ydir, C4Real rdir,
1015 int32_t iCon, int32_t iController, bool grow_from_center)
1016 {
1017 // Safety
1018 if (!pDef) return nullptr;
1019 if (Config.General.DebugRec)
1020 {
1021 C4RCCreateObj rc;
1022 memset(&rc, '\0', sizeof(rc));
1023 strncpy(rc.id, pDef->GetName(), 32+1);
1024 rc.oei=C4PropListNumbered::GetEnumerationIndex()+1;
1025 rc.x=iX; rc.y=iY; rc.ownr=iOwner;
1026 AddDbgRec(RCT_CrObj, &rc, sizeof(rc));
1027 }
1028 // Create object
1029 C4Object *pObj;
1030 if (!(pObj=new C4Object)) return nullptr;
1031 // Initialize object
1032 pObj->Init( pDef,pCreator,iOwner,pInfo,iX,iY,iR,xdir,ydir,rdir, iController );
1033 // Add to object list
1034 if (!Objects.Add(pObj)) { delete pObj; return nullptr; }
1035 // ---- From now on, object is ready to be used in scripts!
1036 // Construction callback
1037 C4AulParSet pars(pCreator);
1038 pObj->Call(PSF_Construction, &pars);
1039 // AssignRemoval called? (Con 0)
1040 if (!pObj->Status) { return nullptr; }
1041 // Do initial con (grow)
1042 pObj->DoCon(iCon, grow_from_center);
1043 // AssignRemoval called? (Con 0)
1044 if (!pObj->Status) { return nullptr; }
1045 // Success
1046 return pObj;
1047 }
1048
DeleteObjects(bool fDeleteInactive)1049 void C4Game::DeleteObjects(bool fDeleteInactive)
1050 {
1051 // del any objects
1052 ::Objects.DeleteObjects(fDeleteInactive);
1053 // reset resort flag
1054 fResortAnyObject = false;
1055 }
1056
CreateObject(C4ID id,C4Object * pCreator,int32_t iOwner,int32_t x,int32_t y,int32_t r,bool grow_from_center,C4Real xdir,C4Real ydir,C4Real rdir,int32_t iController)1057 C4Object* C4Game::CreateObject(C4ID id, C4Object *pCreator, int32_t iOwner,
1058 int32_t x, int32_t y, int32_t r, bool grow_from_center,
1059 C4Real xdir, C4Real ydir, C4Real rdir, int32_t iController)
1060 {
1061 C4Def *pDef;
1062 // Get pDef
1063 if (!(pDef=C4Id2Def(id))) return nullptr;
1064 // Create object
1065 return NewObject(pDef,pCreator,
1066 iOwner,nullptr,
1067 x,y,r,
1068 xdir,ydir,rdir,
1069 FullCon, iController, grow_from_center);
1070 }
1071
CreateObject(C4PropList * PropList,C4Object * pCreator,int32_t iOwner,int32_t x,int32_t y,int32_t r,bool grow_from_center,C4Real xdir,C4Real ydir,C4Real rdir,int32_t iController)1072 C4Object* C4Game::CreateObject(C4PropList * PropList, C4Object *pCreator, int32_t iOwner,
1073 int32_t x, int32_t y, int32_t r, bool grow_from_center,
1074 C4Real xdir, C4Real ydir, C4Real rdir, int32_t iController)
1075 {
1076 // check Definition
1077 if (!PropList || !PropList->GetDef()) return nullptr;
1078 // Create object
1079 return NewObject(PropList,pCreator,
1080 iOwner,nullptr,
1081 x,y,r,
1082 xdir,ydir,rdir,
1083 FullCon, iController, grow_from_center);
1084 }
1085
CreateInfoObject(C4ObjectInfo * cinf,int32_t iOwner,int32_t tx,int32_t ty)1086 C4Object* C4Game::CreateInfoObject(C4ObjectInfo *cinf, int32_t iOwner,
1087 int32_t tx, int32_t ty)
1088 {
1089 C4Def *def;
1090 // Valid check
1091 if (!cinf) return nullptr;
1092 // Get def
1093 if (!(def=C4Id2Def(cinf->id))) return nullptr;
1094 // Create object
1095 return NewObject( def,nullptr,
1096 iOwner,cinf,
1097 tx,ty,0,
1098 Fix0,Fix0,Fix0,
1099 FullCon, NO_OWNER, false);
1100 }
1101
CreateObjectConstruction(C4PropList * PropList,C4Object * pCreator,int32_t iOwner,int32_t iX,int32_t iBY,int32_t iCon,bool fTerrain)1102 C4Object* C4Game::CreateObjectConstruction(C4PropList * PropList,
1103 C4Object *pCreator,
1104 int32_t iOwner,
1105 int32_t iX, int32_t iBY,
1106 int32_t iCon,
1107 bool fTerrain)
1108 {
1109 C4Def *pDef;
1110 C4Object *pObj;
1111
1112 // Get def
1113 if (!PropList) return nullptr;
1114 if (!(pDef=PropList->GetDef())) return nullptr;
1115
1116 int32_t dx,dy,dwdt,dhgt;
1117 dwdt=pDef->Shape.Wdt; dhgt=pDef->Shape.Hgt;
1118 dx=iX-dwdt/2; dy=iBY-dhgt;
1119
1120 // Terrain
1121 if (fTerrain)
1122 {
1123 // Clear site background (ignored for ultra-large structures)
1124 if (dwdt*dhgt<12000)
1125 Landscape.DigFreeRect(dx,dy,dwdt,dhgt);
1126 // Raise Terrain
1127 Landscape.RaiseTerrain(dx,dy+dhgt,dwdt);
1128 }
1129
1130 // Create object
1131 if (!(pObj=NewObject(PropList,
1132 pCreator,
1133 iOwner,nullptr,
1134 iX,iBY,0,
1135 Fix0,Fix0,Fix0,
1136 iCon, pCreator ? pCreator->Controller : NO_OWNER, false))) return nullptr;
1137
1138 return pObj;
1139 }
1140
1141 // Finds an object (OCF_Exclusive) that blocks a potential construction site in the given rectangle
FindConstuctionSiteBlock(int32_t tx,int32_t ty,int32_t wdt,int32_t hgt)1142 C4Object* C4Game::FindConstuctionSiteBlock(int32_t tx, int32_t ty, int32_t wdt, int32_t hgt)
1143 {
1144 C4Rect rect1,rect2;
1145 rect1.x=tx; rect1.y=ty; rect1.Wdt=wdt; rect1.Hgt=hgt;
1146 C4LArea Area(&::Objects.Sectors, tx, ty, wdt, hgt); C4LSector *pSector;
1147 for (C4ObjectList *pObjs = Area.FirstObjectShapes(&pSector); pSector; pObjs = Area.NextObjectShapes(pObjs, &pSector))
1148 for (C4Object *cObj : *pObjs)
1149 if (cObj->Status && !cObj->Contained)
1150 if (cObj->OCF & OCF_Exclusive)
1151 {
1152 rect2=cObj->Shape; rect2.x+=cObj->GetX(); rect2.y+=cObj->GetY();
1153 if (rect1.Overlap(rect2)) return cObj;
1154 }
1155 return nullptr;
1156 }
1157
FindObject(C4Def * pDef,int32_t iX,int32_t iY,int32_t iWdt,int32_t iHgt,DWORD ocf,C4Object * pFindNext)1158 C4Object* C4Game::FindObject(C4Def * pDef,
1159 int32_t iX, int32_t iY, int32_t iWdt, int32_t iHgt,
1160 DWORD ocf,
1161 C4Object *pFindNext)
1162 {
1163
1164 C4Object *pClosest=nullptr;
1165 int32_t iClosest = 0,iDistance,iFartherThan=-1;
1166 C4Object *pFindNextCpy=pFindNext;
1167
1168 // check the easy case first: no instances at all?
1169 if (pDef && !pDef->Count) return nullptr;
1170
1171 // Finding next closest: find closest but further away than last closest
1172 if (pFindNext && (iWdt==-1) && (iHgt==-1))
1173 {
1174 iFartherThan = (pFindNext->GetX()-iX)*(pFindNext->GetX()-iX)+(pFindNext->GetY()-iY)*(pFindNext->GetY()-iY);
1175 pFindNext = nullptr;
1176 }
1177
1178 // Scan all objects
1179 for (C4Object *cObj : Objects)
1180 {
1181 // Not skipping to find next
1182 if (!pFindNext)
1183 // Status
1184 if (cObj->Status)
1185 // ID
1186 if (!pDef || (cObj->Def == pDef))
1187 // OCF (match any specified)
1188 if (cObj->OCF & ocf)
1189 // Area
1190 {
1191 // Point
1192 if ((iWdt==0) && (iHgt==0))
1193 {
1194 if (Inside<int32_t>(iX-(cObj->GetX()+cObj->Shape.x),0,cObj->Shape.Wdt-1))
1195 if (Inside<int32_t>(iY-(cObj->GetY()+cObj->Shape.y),0,cObj->Shape.Hgt-1))
1196 return cObj;
1197 continue;
1198 }
1199 // Closest
1200 if ((iWdt==-1) && (iHgt==-1))
1201 {
1202 iDistance = (cObj->GetX()-iX)*(cObj->GetX()-iX)+(cObj->GetY()-iY)*(cObj->GetY()-iY);
1203 // same distance?
1204 if ((iDistance == iFartherThan) && !pFindNextCpy)
1205 return cObj;
1206 // nearer than/first closest?
1207 if (!pClosest || (iDistance < iClosest))
1208 if (iDistance > iFartherThan)
1209 { pClosest=cObj; iClosest=iDistance; }
1210 }
1211 // Range
1212 else if (Inside<int32_t>(cObj->GetX()-iX,0,iWdt-1) && Inside<int32_t>(cObj->GetY()-iY,0,iHgt-1))
1213 return cObj;
1214 }
1215
1216 // Find next mark reached
1217 if (cObj == pFindNextCpy) pFindNext = pFindNextCpy = nullptr;
1218
1219 }
1220
1221 return pClosest;
1222
1223 }
1224
FindVisObject(float tx,float ty,int32_t iPlr,const C4Facet & fctViewportGame,const C4Facet & fctViewportGUI,float game_x,float game_y,DWORD category,float gui_x,float gui_y)1225 C4Object *C4Game::FindVisObject(float tx, float ty, int32_t iPlr, const C4Facet &fctViewportGame, const C4Facet &fctViewportGUI,
1226 float game_x, float game_y, DWORD category, float gui_x, float gui_y)
1227 {
1228 // FIXME: Use C4FindObject here for optimization
1229 // -- can't really do that, since sectors ignore parallaxity, etc.
1230 // determine layer to search in
1231 C4Object *layer_object = nullptr;
1232 C4Player *plr = ::Players.Get(iPlr);
1233 if (plr && plr->Cursor) layer_object = plr->Cursor->Layer;
1234 // scan all object lists separately
1235 C4ObjectList *pLst = &::Objects.ForeObjects;
1236 while (pLst)
1237 {
1238 // Scan all objects in list
1239 for (C4Object *cObj : *pLst)
1240 {
1241 // Status
1242 if (cObj->Status == C4OS_NORMAL)
1243 // exclude fore-objects from main list
1244 if ((pLst != &Objects) || (~cObj->Category & C4D_Foreground))
1245 // exclude MouseIgnore-objects
1246 if (~cObj->Category & C4D_MouseIgnore)
1247 // Category (match any specified)
1248 if (cObj->Category & category)
1249 // Container
1250 if (!cObj->Contained)
1251 // Visibility
1252 if (cObj->IsVisible(iPlr, false))
1253 // Layer check: Layered objects are invisible to players whose cursor is in another layer
1254 // except for GUI: GUI always visible
1255 {
1256 if (cObj->Layer != layer_object)
1257 if (pLst != &::Objects.ForeObjects)
1258 continue;
1259 // Area
1260 // get object position
1261 float iObjX, iObjY, check_x, check_y;
1262 if (pLst == &::Objects.ForeObjects)
1263 {
1264 // object position for HUD object
1265 check_x = gui_x; check_y = gui_y;
1266 cObj->GetViewPos(iObjX, iObjY, -fctViewportGUI.X, -fctViewportGUI.Y, fctViewportGUI);
1267 }
1268 else
1269 {
1270 // object position for game object
1271 check_x = game_x; check_y = game_y;
1272 cObj->GetViewPos(iObjX, iObjY, tx, ty, fctViewportGame);
1273 }
1274 // Point search
1275 if (Inside<float>(check_x-(iObjX+cObj->Shape.x),0,float(cObj->Shape.Wdt)-1))
1276 if (Inside<float>(check_y-(iObjY+cObj->Shape.y-cObj->addtop()),0,float(cObj->Shape.Hgt+cObj->addtop()-1)))
1277 return cObj;
1278 }
1279 }
1280 // next list
1281 if (pLst == &::Objects.ForeObjects) pLst = &Objects;
1282 else pLst = nullptr;
1283 }
1284
1285 // none found
1286 return nullptr;
1287 }
1288
ObjectCount(C4ID id)1289 int32_t C4Game::ObjectCount(C4ID id)
1290 {
1291 C4Def *pDef;
1292 // check the easy cases first
1293 if (id != C4ID::None)
1294 {
1295 if (!(pDef=C4Id2Def(id))) return 0; // no valid def
1296 return pDef->Count;
1297 }
1298 int32_t iResult = 0;
1299 for (C4Object *cObj : Objects)
1300 // Status
1301 if (cObj->Status)
1302 ++iResult;
1303 return iResult;
1304 }
1305
1306 // Deletes removal-assigned data from list.
1307 // Pointer clearance is done by AssignRemoval.
1308
ObjectRemovalCheck()1309 void C4Game::ObjectRemovalCheck() // Every ::Game.iTick255 by ExecObjects
1310 {
1311 for (C4Object *cObj : Objects)
1312 {
1313 if (!cObj->Status && (cObj->RemovalDelay==0))
1314 {
1315 Objects.Remove(cObj);
1316 delete cObj;
1317 }
1318 }
1319 }
1320
ExecObjects()1321 void C4Game::ExecObjects() // Every Tick1 by Execute
1322 {
1323 if (Config.General.DebugRec)
1324 AddDbgRec(RCT_Block, "ObjEx", 6);
1325
1326 // Execute objects - reverse order to ensure
1327 for (C4Object *cObj : Objects.reverse())
1328 {
1329 if (cObj)
1330 {
1331 if (cObj->Status)
1332 // Execute object
1333 cObj->Execute();
1334 else
1335 // Status reset: process removal delay
1336 if (cObj->RemovalDelay>0) cObj->RemovalDelay--;
1337 }
1338 }
1339
1340 if (Config.General.DebugRec)
1341 AddDbgRec(RCT_Block, "ObjCC", 6);
1342
1343 // Cross check objects
1344 Objects.CrossCheck();
1345
1346 if (Config.General.DebugRec)
1347 AddDbgRec(RCT_Block, "ObjRs", 6);
1348
1349 // Resort
1350 if (fResortAnyObject)
1351 {
1352 fResortAnyObject = false;
1353 Objects.ResortUnsorted();
1354 }
1355
1356 if (Config.General.DebugRec)
1357 AddDbgRec(RCT_Block, "ObjRm", 6);
1358
1359 // Removal
1360 if (!::Game.iTick255) ObjectRemovalCheck();
1361 }
1362
DefFileGetID(const char * szFilename)1363 C4ID DefFileGetID(const char *szFilename)
1364 {
1365 C4Group hDef;
1366 C4Def DefCore;
1367 if (!hDef.Open(szFilename)) return C4ID::None;
1368 if (!DefCore.LoadDefCore(hDef)) { hDef.Close(); return C4ID::None; }
1369 hDef.Close();
1370 return DefCore.id;
1371 }
1372
DropFile(const char * szFilename,float iX,float iY)1373 bool C4Game::DropFile(const char *szFilename, float iX, float iY)
1374 {
1375 C4ID c_id; C4Def *cdef;
1376 // Drop def to create object
1377 if (SEqualNoCase(GetExtension(szFilename),"ocd"))
1378 {
1379 // Get id from file
1380 if ((c_id=DefFileGetID(szFilename)))
1381 // Get loaded def or try to load def from file
1382 if ( (cdef=C4Id2Def(c_id))
1383 || (::Definitions.Load(szFilename,C4D_Load_RX,Config.General.LanguageEx,&Application.SoundSystem) && (cdef=C4Id2Def(c_id))) )
1384 {
1385 return DropDef(c_id, iX, iY);
1386 }
1387 // Failure
1388 Console.Out(FormatString(LoadResStr("IDS_CNS_DROPNODEF"),GetFilename(szFilename)).getData());
1389 return false;
1390 }
1391 return false;
1392 }
1393
DropDef(C4ID id,float X,float Y)1394 bool C4Game::DropDef(C4ID id, float X, float Y)
1395 {
1396 // Get def
1397 C4Def *pDef;
1398 if ((pDef=C4Id2Def(id)))
1399 {
1400 ::Control.DoInput(CID_EMMoveObj, C4ControlEMMoveObject::CreateObject(id, ftofix(X), ftofix(Y), nullptr), CDT_Decide);
1401 return true;
1402 }
1403 else
1404 {
1405 // Failure
1406 Console.Out(FormatString(LoadResStr("IDS_CNS_DROPNODEF"),id.ToString()).getData());
1407 }
1408 return false;
1409 }
1410
CastObjects(C4ID id,C4Object * pCreator,int32_t num,int32_t level,int32_t tx,int32_t ty,int32_t iOwner,int32_t iController,C4ValueArray * out_objects)1411 void C4Game::CastObjects(C4ID id, C4Object *pCreator, int32_t num, int32_t level, int32_t tx, int32_t ty, int32_t iOwner, int32_t iController, C4ValueArray *out_objects)
1412 {
1413 int32_t cnt, out_obj_size=0;
1414 if (out_objects)
1415 {
1416 out_obj_size = out_objects->GetSize();
1417 out_objects->SetSize(out_obj_size + num);
1418 }
1419 for (cnt=0; cnt<num; cnt++)
1420 {
1421 // Must do these calculation steps separately, because the order of
1422 // invokations of Random() is not defined if they're used as parameters
1423 int32_t angle = Random(360);
1424 C4Real xdir = C4REAL10(Random(2*level+1)-level);
1425 C4Real ydir = C4REAL10(Random(2*level+1)-level);
1426 C4Real rdir = itofix(Random(3)+1);
1427 C4Object *obj = CreateObject(id,pCreator,iOwner,
1428 tx,ty,angle,
1429 false,
1430 xdir,
1431 ydir,
1432 rdir, iController);
1433 if (obj && obj->Status && out_objects) (*out_objects)[out_obj_size+cnt] = C4VObj(obj);
1434 }
1435 }
1436
OnSec1Timer()1437 void C4GameSec1Timer::OnSec1Timer()
1438 {
1439 // updates the game clock
1440 if (Game.TimeGo) { Game.Time++; Game.TimeGo = false; }
1441 Game.FPS=Game.cFPS; Game.cFPS=0;
1442 }
1443
Default()1444 void C4Game::Default()
1445 {
1446 PointersDenumerated = false;
1447 IsRunning = false;
1448 FrameCounter=0;
1449 GameOver=GameOverDlgShown=InitialPlayersJoined=false;
1450 ScenarioFilename[0]=0;
1451 PlayerFilenames[0]=0;
1452 DefinitionFilenames[0]=0;
1453 DirectJoinAddress[0]=0;
1454 pJoinReference=nullptr;
1455 StartupPlayerCount=0;
1456 StartupTeamCount = 0;
1457 ScenarioTitle.Ref("");
1458 HaltCount=0;
1459 fReferenceDefinitionOverride=false;
1460 Evaluated=false;
1461 EvaluateOnAbort=false;
1462 TimeGo=false;
1463 Time=0;
1464 StartTime=0;
1465 InitProgress=0; LastInitProgress=0;
1466 FPS=cFPS=0;
1467 fScriptCreatedObjects=false;
1468 fLobby=fObserve=false;
1469 iLobbyTimeout=0;
1470 iTick2=iTick3=iTick5=iTick10=iTick35=iTick255=iTick1000=0;
1471 FullSpeed=false;
1472 FrameSkip=1; DoSkipFrame=false;
1473 ::Definitions.Default();
1474 ::MaterialMap.Default();
1475 Objects.Default();
1476 Players.Default();
1477 Weather.Default();
1478 Landscape.Default();
1479 ::DefaultRanks.Default();
1480 MassMover.Default();
1481 PXS.Default();
1482 GraphicsSystem.Default();
1483 C4S.Default();
1484 ::Messages.Default();
1485 MessageInput.Default();
1486 MouseControl.Default();
1487 PathFinder.Default();
1488 TransferZones.Default();
1489 GroupSet.Default();
1490 pParentGroup=nullptr;
1491 pScenarioSections=pCurrentScenarioSection=nullptr;
1492 *CurrentScenarioSection=0;
1493 fResortAnyObject=false;
1494 pNetworkStatistics.reset();
1495 ::Application.MusicSystem.ClearGame();
1496 DebugPort = 0;
1497 DebugPassword.Clear();
1498 DebugHost.Clear();
1499 DebugWait = false;
1500 assert(!ScriptGuiRoot);
1501 ScriptGuiRoot.reset();
1502 }
1503
Evaluate()1504 void C4Game::Evaluate()
1505 {
1506
1507 // League game?
1508 bool fLeague = Network.isEnabled() && Network.isHost() && Parameters.isLeague();
1509
1510 // Stop record
1511 StdStrBuf RecordName; BYTE RecordSHA[SHA_DIGEST_LENGTH];
1512 if (Control.isRecord())
1513 Control.StopRecord(&RecordName, fLeague ? RecordSHA : nullptr);
1514
1515 // Send league result
1516 if (fLeague)
1517 Network.LeagueGameEvaluate(RecordName.getData(), RecordSHA);
1518
1519 // Players
1520 // saving local players only, because remote players will probably not rejoin after evaluation anyway)
1521 Players.Evaluate();
1522 Players.Save(true);
1523
1524 // Round results
1525 RoundResults.EvaluateGame();
1526
1527 // Set game flag
1528 Log(LoadResStr("IDS_PRC_EVALUATED"));
1529 Evaluated=true;
1530
1531 }
1532
DrawCrewOverheadText(C4TargetFacet & cgo,int32_t iPlayer)1533 void C4Game::DrawCrewOverheadText(C4TargetFacet &cgo, int32_t iPlayer)
1534 {
1535
1536 // All drawing in this function must not be affected by zoom; but remember zoom and reset later.
1537 ZoomData r;
1538 pDraw->GetZoom(&r);
1539 const float zoom = r.Zoom;
1540 r.Zoom = 1.0;
1541 pDraw->SetZoom(r);
1542 // Offset for all text/objects
1543 const float fixedOffsetX = -cgo.X * cgo.Zoom + cgo.X;
1544 const float fixedOffsetY = (-cgo.Y - 10.0f) * cgo.Zoom + cgo.Y;
1545 // Draw cursor mark arrow & cursor object name
1546 C4Facet &fctCursor = GraphicsResource.fctMouseCursor;
1547 for (C4Player *pPlr = Players.First; pPlr; pPlr = pPlr->Next)
1548 {
1549 // Draw a small selector & name above the cursor? F.e. after switching crew.
1550 const bool drawCursorInfo = (pPlr->Number == iPlayer || iPlayer == NO_OWNER) // only for the viewport's player..
1551 && (pPlr->CursorFlash && pPlr->Cursor); // ..and if the player wants to show their cursor.
1552 // Otherwise, for allied players we might want to draw player & crew names.
1553 // Note that these two conditions are generally mutually-exclusive.
1554 const bool drawPlayerAndCursorNames = (pPlr->Number != iPlayer) // Never for own player..
1555 && (Config.Graphics.ShowCrewNames || Config.Graphics.ShowCrewCNames) // ..and if the settings allow it..
1556 && !Hostile(iPlayer, pPlr->Number) && !pPlr->IsInvisible(); // ..and of course only if applicable.
1557
1558 if (!drawPlayerAndCursorNames && !drawCursorInfo) continue;
1559
1560 // Lambda to calculate correct drawing position of object, (re-)adjusted by zoom.
1561 float drawX, drawY, drawZoom;
1562 auto calculateObjectTextPosition = [&](C4Object *obj)
1563 {
1564 obj->GetDrawPosition(cgo, fixtof(obj->fix_x), fixtof(obj->fix_y), 1.0, drawX, drawY, drawZoom);
1565 drawX = drawX * cgo.Zoom + fixedOffsetX;
1566 drawY = drawY * cgo.Zoom - static_cast<float>(obj->Def->Shape.Hgt) / 2.0 + fixedOffsetY;
1567 };
1568
1569 // Actual text output!
1570 if (drawPlayerAndCursorNames)
1571 {
1572 // We need to show crew names for that player, we do so for every crew-member.
1573 for (C4Object * const & crew : pPlr->Crew)
1574 {
1575 if (!crew->Status || !crew->Def) continue;
1576 if (crew->Contained) continue;
1577 if ((crew->OCF & OCF_CrewMember) == 0) continue;
1578 if (!crew->IsVisible(iPlayer, false)) continue;
1579
1580 calculateObjectTextPosition(crew);
1581 drawY -= 5.0f; // aesthetical offset
1582
1583 // compose string
1584 char szText[C4GM_MaxText + 1];
1585 if (Config.Graphics.ShowCrewNames)
1586 if (Config.Graphics.ShowCrewCNames)
1587 sprintf(szText, "%s (%s)", crew->GetName(), pPlr->GetName());
1588 else
1589 SCopy(pPlr->GetName(), szText);
1590 else
1591 SCopy(crew->GetName(), szText);
1592 // Word wrap to cgo width
1593 int32_t iCharWdt, dummy;
1594 ::GraphicsResource.FontRegular.GetTextExtent("m", iCharWdt, dummy, false);
1595 int32_t iMaxLine = std::max<int32_t>(cgo.Wdt / std::max<int32_t>(1, iCharWdt), 20);
1596 SWordWrap(szText, ' ', '|', iMaxLine);
1597 // Center text vertically, too
1598 int textWidth, textHeight;
1599 ::GraphicsResource.FontRegular.GetTextExtent(szText, textWidth, textHeight, true);
1600 // Draw
1601 pDraw->TextOut(szText, ::GraphicsResource.FontRegular, 1.0, cgo.Surface, drawX, drawY - static_cast<float>(textHeight) / 2.0,
1602 pPlr->ColorDw | 0x7f000000, ACenter);
1603 }
1604 }
1605 else if (drawCursorInfo)
1606 {
1607 C4Object * const cursor = pPlr->Cursor;
1608 calculateObjectTextPosition(cursor);
1609 // Draw a down-arrow above the Clonk's head
1610 drawY += -fctCursor.Hgt;
1611 fctCursor.Draw(cgo.Surface, drawX - static_cast<float>(fctCursor.Wdt) / 2.0, drawY, 4);
1612 // And possibly draw some info text, too
1613 if (cursor->Info)
1614 {
1615 int32_t texthgt = ::GraphicsResource.FontRegular.GetLineHeight();
1616 StdStrBuf str;
1617 if (cursor->Info->Rank > 0)
1618 {
1619 str.Format("%s|%s", cursor->Info->sRankName.getData(), cursor->GetName());
1620 texthgt += texthgt;
1621 }
1622 else str = cursor->GetName();
1623
1624 pDraw->TextOut(str.getData(), ::GraphicsResource.FontRegular, 1.0, cgo.Surface,
1625 drawX,
1626 drawY - static_cast<float>(texthgt) / 2.0,
1627 0xffff0000, ACenter);
1628
1629 }
1630 }
1631 }
1632 // Reset zoom
1633 r.Zoom = zoom;
1634 pDraw->SetZoom(r);
1635 }
1636
Ticks()1637 void C4Game::Ticks()
1638 {
1639 // Frames
1640 FrameCounter++; GameGo = FullSpeed;
1641 // Ticks
1642 if (++iTick2==2) iTick2=0;
1643 if (++iTick3==3) iTick3=0;
1644 if (++iTick5==5) iTick5=0;
1645 if (++iTick10==10) iTick10=0;
1646 if (++iTick35==35) iTick35=0;
1647 if (++iTick255==255) iTick255=0;
1648 if (++iTick1000==1000) iTick1000=0;
1649 // FPS / time
1650 cFPS++; TimeGo = true;
1651 // Frame skip
1652 if (FrameCounter % FrameSkip) DoSkipFrame = true;
1653 // Control
1654 Control.Ticks();
1655 // Full speed
1656 if (GameGo) Application.NextTick(); // short-circuit the timer
1657 // statistics
1658 if (pNetworkStatistics) pNetworkStatistics->ExecuteFrame();
1659 }
1660
CompileFunc(StdCompiler * pComp,CompileSettings comp,C4ValueNumbers * numbers)1661 void C4Game::CompileFunc(StdCompiler *pComp, CompileSettings comp, C4ValueNumbers * numbers)
1662 {
1663 if (comp.init_mode == IM_Normal && comp.fExact)
1664 {
1665 pComp->Name("Game");
1666 pComp->Value(mkNamingAdapt(Time, "Time", 0));
1667 pComp->Value(mkNamingAdapt(FrameCounter, "Frame", 0));
1668 if (comp.fSync)
1669 {
1670 pComp->Value(mkNamingAdapt(Control.ControlTick, "ControlTick", 0));
1671 pComp->Value(mkNamingAdapt(Control.SyncRate, "SyncRate", C4SyncCheckRate));
1672 }
1673 pComp->Value(mkNamingAdapt(iTick2, "Tick2", 0));
1674 pComp->Value(mkNamingAdapt(iTick3, "Tick3", 0));
1675 pComp->Value(mkNamingAdapt(iTick5, "Tick5", 0));
1676 pComp->Value(mkNamingAdapt(iTick10, "Tick10", 0));
1677 pComp->Value(mkNamingAdapt(iTick35, "Tick35", 0));
1678 pComp->Value(mkNamingAdapt(iTick255, "Tick255", 0));
1679 pComp->Value(mkNamingAdapt(iTick1000, "Tick1000", 0));
1680 pComp->Value(mkNamingAdapt(InitialPlayersJoined, "InitialPlayersJoined", false));
1681 pComp->Value(mkNamingAdapt(StartupPlayerCount, "StartupPlayerCount", 0));
1682 pComp->Value(mkNamingAdapt(StartupTeamCount, "StartupTeamCount", 0));
1683 pComp->Value(mkNamingAdapt(C4PropListNumbered::EnumerationIndex,"ObjectEnumerationIndex",0));
1684 pComp->Value(mkNamingAdapt(mkStringAdaptMA(CurrentScenarioSection), "CurrentScenarioSection", ""));
1685 pComp->Value(mkNamingAdapt(fResortAnyObject, "ResortAnyObj", false));
1686 pComp->Value(mkNamingAdapt(mkParAdapt(GlobalSoundModifier, numbers), "GlobalSoundModifier", C4Value()));
1687 pComp->Value(mkNamingAdapt(NextMission, "NextMission", StdCopyStrBuf()));
1688 pComp->Value(mkNamingAdapt(NextMissionText, "NextMissionText", StdCopyStrBuf()));
1689 pComp->Value(mkNamingAdapt(NextMissionDesc, "NextMissionDesc", StdCopyStrBuf()));
1690 pComp->NameEnd();
1691
1692 // Music settings
1693 pComp->Value(mkNamingAdapt(::Application.MusicSystem, "Music"));
1694
1695 // scoreboard compiles into main level [Scoreboard]
1696 pComp->Value(mkNamingAdapt(Scoreboard, "Scoreboard"));
1697 // Keyboard status of global keys synchronized for exact (runtime join) only; not for savegames,
1698 // as keys might be released between a savegame save and its resume
1699 }
1700
1701 if (comp.fExact)
1702 {
1703 pComp->Value(mkNamingAdapt(Weather, "Weather"));
1704 pComp->Value(mkNamingAdapt(Landscape, "Landscape"));
1705 pComp->Value(mkNamingAdapt(Landscape.GetSky(), "Sky"));
1706
1707 // save custom GUIs only if a real savegame and not for editor-scenario-saves or section changes
1708 if (comp.init_mode == IM_Normal)
1709 {
1710 pComp->Name("GUI");
1711 if (pComp->isDeserializer())
1712 {
1713 C4Value val;
1714 pComp->Value(mkNamingAdapt(mkParAdapt(val, numbers), "ScriptGUIs", C4VNull));
1715 // if loading, assume
1716 assert(ScriptGuiRoot->GetID() == 0); // ID of 0 means "had no subwindows ever" aka "is fresh" for root
1717 // we will need to denumerate and create the actual GUI post-loading
1718 // for now, just remember our enumerated ID
1719 if (val.GetType() == C4V_Enum)
1720 {
1721 int enumID = val._getInt();
1722 ScriptGuiRoot->SetEnumeratedID(enumID);
1723 }
1724 }
1725 else
1726 {
1727 C4Value *val = new C4Value(ScriptGuiRoot->ToC4Value());
1728 pComp->Value(mkNamingAdapt(mkParAdapt(*val, numbers), "ScriptGUIs", C4VNull));
1729 }
1730 pComp->NameEnd();
1731 }
1732 }
1733
1734 if (comp.fPlayers)
1735 {
1736 assert(pComp->isSerializer());
1737 // player parsing: Parse all players
1738 // This doesn't create any players, but just parses existing by their ID
1739 // Primary player ininitialization (also setting ID) is done by player info list
1740 // Won't work this way for binary mode!
1741 for (C4Player *pPlr=Players.First; pPlr; pPlr=pPlr->Next)
1742 pComp->Value(mkNamingAdapt(mkParAdapt(*pPlr, numbers), FormatString("Player%d", pPlr->ID).getData()));
1743 }
1744
1745 // Section load: Clear existing prop list numbering to make room for the new objects
1746 // Numbers will be re-acquired in C4GameObjects::PostLoad
1747 if (comp.init_mode == IM_Section) C4PropListNumbered::ShelveNumberedPropLists();
1748
1749 pComp->Value(mkParAdapt(Objects, !comp.fExact, numbers));
1750
1751 pComp->Value(mkNamingAdapt(mkParAdapt(ScriptEngine, comp.init_mode == IM_Section, numbers), "Script"));
1752 }
1753
CompileRuntimeData(C4Group & hGroup,InitMode init_mode,bool exact,bool sync,C4ValueNumbers * numbers)1754 bool C4Game::CompileRuntimeData(C4Group &hGroup, InitMode init_mode, bool exact, bool sync, C4ValueNumbers * numbers)
1755 {
1756 ::Objects.Clear(init_mode != IM_Section);
1757 GameText.Load(hGroup,C4CFN_Game);
1758 CompileSettings Settings(init_mode, false, exact, sync);
1759 // C4Game is not defaulted on compilation.
1760 // Loading of runtime data overrides only certain values.
1761 // Doesn't compile players; those will be done later
1762 if (GameText.GetData())
1763 {
1764 if (!CompileFromBuf_LogWarn<StdCompilerINIRead>(
1765 mkParAdapt(*this, Settings, numbers),
1766 GameText.GetDataBuf(), C4CFN_Game))
1767 return false;
1768 // Objects
1769 int32_t iObjects = Objects.ObjectCount();
1770 if (iObjects) { LogF(LoadResStr("IDS_PRC_OBJECTSLOADED"),iObjects); }
1771 }
1772 // Success
1773 return true;
1774 }
1775
SaveData(C4Group & hGroup,bool fSaveSection,bool fSaveExact,bool fSaveSync,C4ValueNumbers * numbers)1776 bool C4Game::SaveData(C4Group &hGroup, bool fSaveSection, bool fSaveExact, bool fSaveSync, C4ValueNumbers * numbers)
1777 {
1778 if (fSaveExact)
1779 {
1780 StdStrBuf Buf;
1781 // Decompile (without players for scenario sections)
1782 DecompileToBuf_Log<StdCompilerINIWrite>(mkParAdapt(*this, CompileSettings(fSaveSection ? IM_Section : IM_Normal, !fSaveSection && fSaveExact, fSaveExact, fSaveSync), numbers), &Buf, "Game");
1783
1784 // Empty? All default save a Game.txt anyway because it is used to signal the engine to not load Objects.c
1785 if (!Buf.getLength()) Buf.Copy(" ");
1786
1787 // Save
1788 return hGroup.Add(C4CFN_Game,Buf,false,true);
1789 }
1790 else
1791 {
1792 // Clear any exact game data in case scenario is saved from savegame resume
1793 hGroup.Delete(C4CFN_Game);
1794
1795 // Save objects to file using system scripts
1796 int32_t objects_file_handle = ::ScriptEngine.CreateUserFile();
1797 C4AulParSet pars(objects_file_handle);
1798 C4Value result_c4v(::ScriptEngine.GetPropList()->Call(PSF_SaveScenarioObjects, &pars));
1799 bool result = !!result_c4v;
1800 if (result_c4v.GetType() == C4V_Nil)
1801 {
1802 // Function returned nil: This usually means there was a script error during object writing.
1803 // It could also mean the scripter overloaded global func SaveScenarioObjects and returned nil.
1804 // In either case, objects will not match landscape any more, so better fail and don't save at all.
1805 LogF("ERROR: No valid result from global func " PSF_SaveScenarioObjects ". Saving objects failed.");
1806 }
1807 else
1808 {
1809 // Function completed successfully (returning true or false)
1810 C4AulUserFile *file = ::ScriptEngine.GetUserFile(objects_file_handle);
1811 if (!result || !file || !file->GetFileLength())
1812 {
1813 // Nothing written? Then we don't have objects.
1814 hGroup.Delete(C4CFN_ScenarioObjectsScript);
1815 // That's OK; not an error.
1816 result = true;
1817 }
1818 else
1819 {
1820 // Write objects script to file!
1821 StdStrBuf data = file->GrabFileContents();
1822 result = hGroup.Add(C4CFN_ScenarioObjectsScript, data, false, true);
1823 }
1824 }
1825 ::ScriptEngine.CloseUserFile(objects_file_handle);
1826 return result;
1827 }
1828 }
1829
SaveGameTitle(C4Group & hGroup)1830 bool C4Game::SaveGameTitle(C4Group &hGroup)
1831 {
1832
1833 // Game not running
1834 if (!FrameCounter)
1835 {
1836 char* bpBytes;
1837 size_t iSize;
1838 StdStrBuf realFilename;
1839
1840 if(ScenarioFile.FindEntry(FormatString("%s.*",C4CFN_ScenarioTitle).getData(),&realFilename,&iSize))
1841 if (ScenarioFile.LoadEntry(realFilename.getData(),&bpBytes,&iSize))
1842 hGroup.Add(realFilename.getData(),bpBytes,iSize,false,true);
1843 }
1844
1845 // Fullscreen screenshot
1846 else if (!Application.isEditor && Application.Active)
1847 {
1848 C4Surface * sfcPic; int32_t iSfcWdt=200,iSfcHgt=150;
1849 if (!(sfcPic = new C4Surface(iSfcWdt,iSfcHgt,0))) return false;
1850
1851 // Fullscreen
1852 pDraw->Blit(FullScreen.pSurface,
1853 0.0f,0.0f,float(C4GUI::GetScreenWdt()),float(C4GUI::GetScreenHgt()-::GraphicsResource.FontRegular.GetLineHeight()),
1854 sfcPic,0,0,iSfcWdt,iSfcHgt);
1855
1856 bool fOkay=true;
1857 fOkay = sfcPic->SavePNG(Config.AtTempPath(C4CFN_TempTitle), false, false, false);
1858 StdStrBuf destFilename = FormatString("%s.png",C4CFN_ScenarioTitle);
1859 delete sfcPic; if (!fOkay) return false;
1860 if (!hGroup.Move(Config.AtTempPath(C4CFN_TempTitle),destFilename.getData())) return false;
1861 }
1862
1863 return true;
1864 }
1865
DoKeyboardInput(C4KeyCode vk_code,C4KeyEventType eEventType,bool fAlt,bool fCtrl,bool fShift,bool fRepeated,class C4GUI::Dialog * pForDialog,bool fPlrCtrlOnly,int32_t iStrength)1866 bool C4Game::DoKeyboardInput(C4KeyCode vk_code, C4KeyEventType eEventType, bool fAlt, bool fCtrl, bool fShift, bool fRepeated, class C4GUI::Dialog *pForDialog, bool fPlrCtrlOnly, int32_t iStrength)
1867 {
1868 // compose key
1869 C4KeyCodeEx Key(vk_code, C4KeyShiftState(fAlt*KEYS_Alt + fCtrl*KEYS_Control + fShift*KEYS_Shift), fRepeated);
1870 return DoKeyboardInput(Key, eEventType, pForDialog, fPlrCtrlOnly, iStrength);
1871 }
1872
1873
DoKeyboardInput(C4KeyCodeEx Key,C4KeyEventType eEventType,class C4GUI::Dialog * pForDialog,bool fPlrCtrlOnly,int32_t iStrength)1874 bool C4Game::DoKeyboardInput(C4KeyCodeEx Key, C4KeyEventType eEventType, class C4GUI::Dialog *pForDialog, bool fPlrCtrlOnly, int32_t iStrength)
1875 {
1876 Key.FixShiftKeys();
1877 // compose keyboard scope
1878 DWORD InScope = 0;
1879 if (fPlrCtrlOnly)
1880 InScope = KEYSCOPE_Control;
1881 else
1882 {
1883 if (IsRunning) InScope = KEYSCOPE_Generic;
1884 // if GUI has keyfocus, this overrides regular controls
1885 if (pGUI->HasKeyboardFocus() || pForDialog)
1886 {
1887 InScope |= KEYSCOPE_Gui;
1888 // control to console mode dialog: Make current keyboard target the active dlg,
1889 // so it can process input
1890 if (pForDialog) pGUI->ActivateDialog(pForDialog);
1891 // any keystroke in GUI resets tooltip times
1892 pGUI->KeyAny();
1893 }
1894 else
1895 {
1896 if (!Application.isEditor)
1897 {
1898 if (FullScreen.pMenu && FullScreen.pMenu->IsActive()) // fullscreen menu
1899 InScope |= KEYSCOPE_FullSMenu;
1900 else if (Game.C4S.Head.Replay && C4S.Head.Film) // film view only
1901 InScope |= KEYSCOPE_FilmView;
1902 else if (::Viewports.GetViewport(NO_OWNER)) // NO_OWNER-viewport-controls
1903 InScope |= KEYSCOPE_FreeView;
1904 else
1905 {
1906 // regular player viewport controls
1907 InScope |= KEYSCOPE_FullSView;
1908 // player controls disabled during round over dlg
1909 if (!C4GameOverDlg::IsShown()) InScope |= KEYSCOPE_Control;
1910 }
1911 }
1912 else
1913 // regular player viewport controls
1914 InScope |= KEYSCOPE_Control;
1915 }
1916 // fullscreen/console (in running game)
1917 if (IsRunning)
1918 {
1919 if (FullScreen.Active) InScope |= KEYSCOPE_Fullscreen;
1920 if (Console.Active) InScope |= KEYSCOPE_Console;
1921 }
1922 }
1923 // okay; do input
1924 if (KeyboardInput.DoInput(Key, eEventType, InScope, iStrength))
1925 return true;
1926
1927 // unprocessed key
1928 return false;
1929 }
1930
CanQuickSave()1931 bool C4Game::CanQuickSave()
1932 {
1933 // Network hosts only
1934 if (Network.isEnabled() && !Network.isHost())
1935 { Log(LoadResStr("IDS_GAME_NOCLIENTSAVE")); return false; }
1936
1937 return true;
1938 }
1939
QuickSave(const char * strFilename,const char * strTitle,bool fForceSave)1940 bool C4Game::QuickSave(const char *strFilename, const char *strTitle, bool fForceSave)
1941 {
1942 // Check
1943 if (!fForceSave) if (!CanQuickSave()) return false;
1944
1945 // Create savegame folder
1946 if (!Config.General.CreateSaveFolder(Config.AtUserDataPath(C4CFN_Savegames), LoadResStr("IDS_GAME_SAVEGAMESTITLE")))
1947 { Log(LoadResStr("IDS_GAME_FAILSAVEGAME")); return false; }
1948
1949 // Create savegame subfolder(s)
1950 char strSaveFolder[_MAX_PATH + 1];
1951 for (uint32_t i = 0; i < SCharCount(DirectorySeparator, strFilename); i++)
1952 {
1953 SCopy(Config.AtUserDataPath(C4CFN_Savegames), strSaveFolder); AppendBackslash(strSaveFolder);
1954 SCopyUntil(strFilename, strSaveFolder + SLen(strSaveFolder), DirectorySeparator, _MAX_PATH, i);
1955 if (!Config.General.CreateSaveFolder(strSaveFolder, strTitle))
1956 { Log(LoadResStr("IDS_GAME_FAILSAVEGAME")); return false; }
1957 }
1958
1959 // Compose savegame filename
1960 StdStrBuf strSavePath;
1961 strSavePath.Format("%s%c%s", Config.AtUserDataPath(C4CFN_Savegames), DirectorySeparator, strFilename);
1962
1963 // Must not be the scenario file that is currently open
1964 if (ItemIdentical(ScenarioFilename, strSavePath.getData()))
1965 {
1966 StartSoundEffect("UI::Error");
1967 ::GraphicsSystem.FlashMessage(LoadResStr("IDS_GAME_NOSAVEONCURR"));
1968 Log(LoadResStr("IDS_GAME_FAILSAVEGAME"));
1969 return false;
1970 }
1971
1972 // Wait message
1973 Log(LoadResStr("IDS_HOLD_SAVINGGAME"));
1974 GraphicsSystem.MessageBoard->EnsureLastMessage();
1975
1976 // Save to target scenario file
1977 C4GameSave *pGameSave;
1978 pGameSave = new C4GameSaveSavegame();
1979 if (!pGameSave->Save(strSavePath.getData()))
1980 { Log(LoadResStr("IDS_GAME_FAILSAVEGAME")); delete pGameSave; return false; }
1981 delete pGameSave;
1982
1983 // Success
1984 Log(LoadResStr("IDS_CNS_GAMESAVED"));
1985 return true;
1986 }
1987
LandscapeFree(int32_t x,int32_t y)1988 bool LandscapeFree(int32_t x, int32_t y)
1989 {
1990 if (!Inside<int32_t>(x,0,::Landscape.GetWidth()-1) || !Inside<int32_t>(y,0,::Landscape.GetHeight()-1)) return false;
1991 return !DensitySolid(GBackDensity(x,y));
1992 }
1993
FileMonitorCallback(const char * file,const char * extrafile)1994 static void FileMonitorCallback(const char * file, const char * extrafile)
1995 {
1996 Game.ReloadFile(file);
1997 }
1998
ReloadFile(const char * szFile)1999 bool C4Game::ReloadFile(const char *szFile)
2000 {
2001 // not in network
2002 if (::Network.isEnabled()) return false;
2003 const char *szRelativePath = Config.AtRelativePath(szFile);
2004 // a definition? or part of a definition?
2005 C4Def *pDef;
2006 if ((pDef = ::Definitions.GetByPath(szRelativePath)))
2007 return ReloadDef(pDef->id);
2008 // script?
2009 if (ScriptEngine.ReloadScript(szRelativePath, Config.General.LanguageEx))
2010 {
2011 ReLinkScriptEngine();
2012 }
2013 return true;
2014 }
2015
ReloadDef(C4ID id)2016 bool C4Game::ReloadDef(C4ID id)
2017 {
2018 bool fSucc;
2019 // not in network
2020 if (::Network.isEnabled()) return false;
2021 // syncronize (close menus with dead surfaces, etc.)
2022 // no need to sync back player files, though
2023 Synchronize(false);
2024 // SolidMasks might be updated
2025 C4SolidMask::RemoveSolidMasks();
2026 // reload def
2027 C4Def *pDef = ::Definitions.ID2Def(id);
2028 if (!pDef) return false;
2029 // Open Graphics.ocg -- we might need to fetch some shader (slices)
2030 // from there when reloading mesh materials.
2031 if (!::GraphicsResource.RegisterGlobalGraphics()) return false;
2032 if (!::GraphicsResource.RegisterMainGroups()) return false;
2033 // Message
2034 LogF("Reloading %s from %s",pDef->id.ToString(),GetFilename(pDef->Filename));
2035 // Reload def
2036 if (::Definitions.Reload(pDef,C4D_Load_RX,Config.General.LanguageEx,&Application.SoundSystem))
2037 {
2038 // update script engine - this will also do include callbacks and Freeze() pDef
2039 ReLinkScriptEngine();
2040 // Success, update all concerned object faces
2041 // may have been done by graphics-update already - but not for objects using graphics of another def
2042 // better update everything :)
2043 for (C4Object *obj : Objects)
2044 {
2045 if (obj->id == id)
2046 obj->UpdateFace(true);
2047 }
2048 fSucc = true;
2049 }
2050 else
2051 {
2052 // Failure, remove all objects of this type
2053 for (C4Object *obj : Objects)
2054 if (obj->id == id)
2055 obj->AssignRemoval();
2056 // safety: If a removed def is being profiled, profiling must stop
2057 C4AulProfiler::Abort();
2058 // Kill def
2059 ::Definitions.Remove(pDef);
2060 // Log
2061 Log("Reloading failure. All objects of this type removed.");
2062 fSucc = false;
2063 }
2064 // close Graphics.ocg again
2065 ::GraphicsResource.CloseFiles();
2066 // update game messages
2067 ::Messages.UpdateDef(id);
2068 // re-put removed SolidMasks
2069 C4SolidMask::PutSolidMasks();
2070 // done
2071 return fSucc;
2072 }
2073
ReloadParticle(const char * szName)2074 bool C4Game::ReloadParticle(const char *szName)
2075 {
2076 // not in network
2077 if (::Network.isEnabled()) return false;
2078 // safety
2079 if (!szName) return false;
2080 // get particle def
2081 C4ParticleDef *pDef = Particles.definitions.GetDef(szName);
2082 if (!pDef) return false;
2083 // verbose
2084 LogF("Reloading particle %s from %s",pDef->Name.getData(),GetFilename(pDef->Filename.getData()));
2085 // reload it
2086 if (!pDef->Reload())
2087 {
2088 // safer: remove all particles
2089 ::Particles.ClearAllParticles();
2090 // clear def
2091 delete pDef;
2092 // log
2093 LogF("Reloading failure. All particles removed.");
2094 // failure
2095 return false;
2096 }
2097 // success
2098 return true;
2099 }
2100
InitGame(C4Group & hGroup,InitMode init_mode,bool fLoadSky,C4ValueNumbers * numbers)2101 bool C4Game::InitGame(C4Group &hGroup, InitMode init_mode, bool fLoadSky, C4ValueNumbers * numbers)
2102 {
2103 // Activate debugger if requested
2104 // needs to happen before any scripts are compiled to bytecode so AB_DEBUG chunks will be inserted
2105 if (DebugPort)
2106 {
2107 if (Parameters.isLeague())
2108 Log("Debugger disabled. Not allowed in league.");
2109 else
2110 if (!::C4AulDebug::InitDebug(DebugPassword.getData(), DebugHost.getData()))
2111 return false;
2112 }
2113
2114 if (init_mode == IM_Normal)
2115 {
2116
2117 // file monitor
2118 if (Config.Developer.AutoFileReload && Application.isEditor && !pFileMonitor)
2119 pFileMonitor = std::make_unique<C4FileMonitor>(FileMonitorCallback);
2120
2121 // system scripts
2122 if (!InitScriptEngine())
2123 { LogFatal(LoadResStr("IDS_PRC_FAIL")); return false; }
2124 SetInitProgress(8);
2125
2126 // Scenario components
2127 if (!LoadScenarioComponents())
2128 { LogFatal(LoadResStr("IDS_PRC_FAIL")); return false; }
2129 SetInitProgress(9);
2130
2131 // join local players for regular games
2132 // should be done before record/replay is initialized, so the players are stored in PlayerInfos.txt
2133 // for local savegame resumes, players are joined into PlayerInfos and later associated in InitPlayers
2134 if (!::Network.isEnabled())
2135 if (!PlayerInfos.InitLocal())
2136 { LogFatal(LoadResStr("IDS_PRC_FAIL")); return false; }
2137
2138 // for replays, make sure teams are assigned correctly
2139 if (C4S.Head.Replay)
2140 {
2141 PlayerInfos.RecheckAutoGeneratedTeams(); // checks that all teams used in playerinfos exist
2142 Teams.RecheckPlayers(); // syncs player list of teams with teams set in PlayerInfos
2143 }
2144
2145 // set up control (inits Record/Replay)
2146 if (!InitControl()) return false;
2147
2148 // Graphics and fonts (may reinit main font, too)
2149 // redundant call in NETWORK2; but it may do scenario local overloads
2150 Log(LoadResStr("IDS_PRC_GFXRES"));
2151 if (!GraphicsResource.Init())
2152 { LogFatal(LoadResStr("IDS_PRC_FAIL")); return false; }
2153 SetInitProgress(25);
2154
2155 // Definitions
2156 if (!InitDefs()) return false;
2157 SetInitProgress(55);
2158
2159 // Scenario scripts (and local system.ocg)
2160 ::GameScript.Load(ScenarioFile, C4CFN_Script, Config.General.LanguageEx, &ScenarioLangStringTable);
2161 // Map scripts
2162 ::MapScript.Load(ScenarioFile, C4CFN_MapScript, Config.General.LanguageEx, &ScenarioLangStringTable);
2163 // Scenario objects
2164 pScenarioObjectsScript->Load(ScenarioFile, C4CFN_ScenarioObjectsScript, Config.General.LanguageEx, &ScenarioLangStringTable);
2165 // After defs to get overloading priority
2166 if (!LoadAdditionalSystemGroup(ScenarioFile))
2167 { LogFatal(LoadResStr("IDS_PRC_FAIL")); return false; }
2168 SetInitProgress(57);
2169
2170 // Final init for loaded player commands. Before linking scripts, so CON_* constants are registered
2171 PlayerControlDefs.FinalInit();
2172
2173 // Register constants for scenario options
2174 ScenarioParameterDefs.RegisterScriptConstants(Parameters.ScenarioParameters);
2175
2176 // Now that all controls and assignments are known, resolve user overloads on control assignments
2177 if (!InitPlayerControlUserSettings()) return false;
2178 // Sort assignments by priority. Should be done last, because the user should not see this order in the control config dialog
2179 PlayerControlUserAssignmentSets.SortAssignments();
2180 // (Theoretically, PlayerControlDefaultAssignmentSets could be cleared now. However, the amount of memory used is negligible)
2181
2182 // Link scripts
2183 if (!LinkScriptEngine()) return false;
2184 SetInitProgress(58);
2185
2186 // Materials
2187 if (!InitMaterialTexture())
2188 { LogFatal(LoadResStr("IDS_PRC_MATERROR")); return false; }
2189 SetInitProgress(60);
2190
2191 // prepare script menus
2192 assert(!ScriptGuiRoot);
2193 ScriptGuiRoot = std::make_unique<C4ScriptGuiWindow>();
2194 }
2195 else if (fLoadSky)
2196 {
2197 // Sky needs graphics loaded, for shaders
2198 if (!GraphicsResource.Init())
2199 { LogFatal(LoadResStr("IDS_PRC_FAIL")); return false; }
2200 }
2201
2202 // Load section sounds
2203 Application.SoundSystem.LoadEffects(hGroup, nullptr, true);
2204
2205 // determine startup player and team count, which may be used for initial map generation
2206 if (!FrameCounter)
2207 {
2208 StartupPlayerCount = PlayerInfos.GetStartupCount();
2209 StartupTeamCount = Teams.GetStartupTeamCount(StartupPlayerCount);
2210 }
2211
2212 // The Landscape is the last long chunk of loading time, so it's a good place to start the music fadeout
2213 if (init_mode == IM_Normal) Application.MusicSystem.FadeOut(2000);
2214 // Landscape
2215 Log(LoadResStr("IDS_PRC_LANDSCAPE"));
2216 bool fLandscapeLoaded = false;
2217 if (!Landscape.Init(hGroup, init_mode != IM_Normal, fLoadSky, fLandscapeLoaded, !!C4S.Head.SaveGame))
2218 { LogFatal(LoadResStr("IDS_ERR_GBACK")); return false; }
2219 SetInitProgress(88);
2220 // the savegame flag is set if runtime data is present, in which case this is to be used
2221 // except for scenario sections
2222 if (fLandscapeLoaded && (!C4S.Head.SaveGame || init_mode == IM_Section))
2223 Landscape.ScenarioInit();
2224 // clear old landscape data
2225 if (init_mode != IM_Normal && fLandscapeLoaded) { PXS.Clear(); MassMover.Clear(); }
2226 SetInitProgress(89);
2227 // Init main object list
2228 Objects.Init(Landscape.GetWidth(), Landscape.GetHeight());
2229
2230 // Pathfinder
2231 if (init_mode == IM_Normal) PathFinder.Init( &LandscapeFree, &TransferZones );
2232 SetInitProgress(90);
2233
2234 // PXS
2235 if (hGroup.FindEntry(C4CFN_PXS))
2236 if (!PXS.Load(hGroup))
2237 { LogFatal(LoadResStr("IDS_ERR_PXS")); return false; }
2238 SetInitProgress(91);
2239
2240 // MassMover
2241 if (hGroup.FindEntry(C4CFN_MassMover))
2242 if (!MassMover.Load(hGroup))
2243 { LogFatal(LoadResStr("IDS_ERR_MOVER")); return false; }
2244 SetInitProgress(92);
2245
2246 // definition value overloads
2247 // TODO: Remove this function? We could move value to script and allow it through regular overloads
2248 if (init_mode == IM_Normal) InitValueOverloads();
2249
2250 // runtime data
2251 if (!CompileRuntimeData(hGroup, init_mode, C4S.Head.SaveGame, C4S.Head.NetworkGame, numbers))
2252 { LogFatal(LoadResStr("IDS_PRC_FAIL")); return false; }
2253
2254 SetInitProgress(93);
2255
2256 // Load round results
2257 if (init_mode == IM_Normal)
2258 {
2259 if (hGroup.FindEntry(C4CFN_RoundResults))
2260 {
2261 if (!RoundResults.Load(hGroup, C4CFN_RoundResults))
2262 { LogFatal(LoadResStr("IDS_ERR_ERRORLOADINGROUNDRESULTS")); return false; }
2263 }
2264 else
2265 {
2266 RoundResults.Init();
2267 }
2268 // Per-scenario flag, ignored in sections.
2269 EvaluateOnAbort = C4S.Game.EvaluateOnAbort;
2270 }
2271
2272 // Denumerate game data pointers
2273 if (init_mode == IM_Normal) ScriptEngine.Denumerate(numbers);
2274 if (init_mode == IM_Normal) GlobalSoundModifier.Denumerate(numbers);
2275 numbers->Denumerate();
2276 if (init_mode == IM_Normal) ScriptGuiRoot->Denumerate(numbers);
2277 // Object.PostLoad must happen after number->Denumerate(), becuase UpdateFace() will access Action proplist,
2278 // which might have a non-denumerated prototype otherwise
2279 Objects.PostLoad(init_mode == IM_Section, numbers);
2280
2281 // Check object enumeration
2282 if (!CheckObjectEnumeration()) return false;
2283
2284 // Okay; everything in denumerated state from now on
2285 PointersDenumerated = true;
2286
2287 // scenario objects script
2288 if (!GameText.GetData() && pScenarioObjectsScript && pScenarioObjectsScript->GetPropList())
2289 pScenarioObjectsScript->GetPropList()->Call(PSF_InitializeObjects);
2290
2291 // Environment
2292 if (!C4S.Head.NoInitialize && fLandscapeLoaded)
2293 {
2294 Log(LoadResStr("IDS_PRC_ENVIRONMENT"));
2295 InitVegetation();
2296 InitInEarth();
2297 InitAnimals();
2298 InitEnvironment();
2299 InitRules();
2300 InitGoals();
2301 Landscape.PostInitMap();
2302 }
2303 SetInitProgress(94);
2304
2305 // Weather
2306 if (fLandscapeLoaded) Weather.Init(!C4S.Head.SaveGame);
2307 SetInitProgress(96);
2308
2309 // close any gfx groups, because they are no longer needed (after sky is initialized)
2310 GraphicsResource.CloseFiles();
2311
2312 if (init_mode == IM_Normal) // reload doesn't affect the music (takes too long)
2313 {
2314 // Music
2315 ::Application.MusicSystem.InitForScenario(ScenarioFile);
2316 ::Application.MusicSystem.UpdateVolume();
2317 if (::Config.Sound.RXMusic)
2318 {
2319 // Play something that is not Frontend.mid
2320 ::Application.MusicSystem.Play();
2321 }
2322 else
2323 ::Application.MusicSystem.Stop();
2324 SetInitProgress(97);
2325 }
2326
2327 return true;
2328 }
2329
InitGameFinal(InitMode init_mode)2330 bool C4Game::InitGameFinal(InitMode init_mode)
2331 {
2332 // Validate object owners & assign loaded info objects
2333 Objects.ValidateOwners();
2334 Objects.AssignInfo();
2335 Objects.AssignLightRange(); // update FoW-repellers
2336
2337 // Ambience init (before scenario construction, so the scenario can easily modify ambience in Initialize)
2338 if (!C4S.Head.SaveGame) ::GameScript.Call(PSF_InitializeAmbience);
2339
2340 // Script constructor call
2341 int32_t iObjCount = Objects.ObjectCount();
2342 if (!C4S.Head.SaveGame) ::GameScript.Call(PSF_Initialize);
2343 if (Objects.ObjectCount()!=iObjCount) fScriptCreatedObjects=true;
2344
2345 // Player final init
2346 C4Player *pPlr;
2347 for (pPlr = Players.First; pPlr; pPlr = pPlr->Next)
2348 {
2349 if (init_mode == IM_ReInit) pPlr->ScenarioInit();
2350 pPlr->FinalInit(!C4S.Head.SaveGame);
2351 }
2352
2353 // Create viewports
2354 if (init_mode == IM_Normal)
2355 {
2356 for (pPlr = Players.First; pPlr; pPlr = pPlr->Next)
2357 if (pPlr->LocalControl)
2358 ::Viewports.CreateViewport(pPlr->Number);
2359 // Check fullscreen viewports
2360 FullScreen.ViewportCheck();
2361 // update halt state
2362 Console.UpdateHaltCtrls(!!HaltCount);
2363
2364 // Host: players without connected clients: remove via control queue
2365 if (Network.isEnabled() && Network.isHost())
2366 for (int32_t cnt = 0; cnt < Players.GetCount(); cnt++)
2367 if (Players.GetByIndex(cnt)->AtClient < 0)
2368 Players.Remove(Players.GetByIndex(cnt), true, false);
2369
2370 // It should be safe now to reload stuff
2371 if (pFileMonitor) pFileMonitor->StartMonitoring();
2372 }
2373 return true;
2374 }
2375
InitScriptEngine()2376 bool C4Game::InitScriptEngine()
2377 {
2378 // engine functions
2379 InitCoreFunctionMap(&ScriptEngine);
2380 InitObjectFunctionMap(&ScriptEngine);
2381 InitGameFunctionMap(&ScriptEngine);
2382 ::MapScript.InitFunctionMap(&ScriptEngine);
2383
2384 // system functions: check if system group is open
2385 if (!Application.OpenSystemGroup())
2386 { LogFatal(LoadResStr("IDS_ERR_INVALIDSYSGRP")); return false; }
2387 C4Group &File = Application.SystemGroup;
2388
2389 // get scripts
2390 char fn[_MAX_FNAME+1] = { 0 };
2391 File.ResetSearch();
2392 while (File.FindNextEntry(C4CFN_ScriptFiles, fn, nullptr, !!fn[0]))
2393 {
2394 // host will be destroyed by script engine, so drop the references
2395 C4ScriptHost *scr = new C4ExtraScriptHost();
2396 scr->Reg2List(&ScriptEngine);
2397 scr->Load(File, fn, Config.General.LanguageEx, &MainSysLangStringTable);
2398 }
2399
2400 // if it's a physical group: watch out for changes
2401 if (!File.IsPacked() && Game.pFileMonitor)
2402 Game.pFileMonitor->AddDirectory(File.GetFullName().getData());
2403
2404 // Prepare host for Objects.c script
2405 pScenarioObjectsScript = new C4ScenarioObjectsScriptHost();
2406 pScenarioObjectsScript->Reg2List(&::ScriptEngine);
2407 C4Value scen_obj_script_val;
2408 scen_obj_script_val.SetPropList(pScenarioObjectsScript->GetPropList());
2409 ::ScriptEngine.RegisterGlobalConstant("ScenarioObjects", scen_obj_script_val);
2410
2411 // load standard clonk names
2412 Names.Load(File, C4CFN_Names);
2413
2414 return true;
2415 }
2416
LinkScriptEngine()2417 bool C4Game::LinkScriptEngine()
2418 {
2419 // Link script engine (resolve includes/appends, generate code)
2420 ScriptEngine.Link(&::Definitions);
2421
2422 // display errors
2423 LogF("C4AulScriptEngine linked - %d line%s, %d warning%s, %d error%s",
2424 ScriptEngine.lineCnt, (ScriptEngine.lineCnt != 1 ? "s" : ""),
2425 ScriptEngine.warnCnt, (ScriptEngine.warnCnt != 1 ? "s" : ""),
2426 ScriptEngine.errCnt, (ScriptEngine.errCnt != 1 ? "s" : ""));
2427
2428 // update material pointers
2429 ::MaterialMap.UpdateScriptPointers();
2430
2431 if (C4AulDebug *pDebug = C4AulDebug::GetDebugger())
2432 if (!pDebug->Listen(DebugPort, !!DebugWait))
2433 return false;
2434
2435 return true;
2436 }
2437
ReLinkScriptEngine()2438 bool C4Game::ReLinkScriptEngine()
2439 {
2440 ::ScriptEngine.ReLink(&::Definitions);
2441
2442 // update effect pointers
2443 ::Objects.UpdateScriptPointers();
2444
2445 // update material pointers
2446 ::MaterialMap.UpdateScriptPointers();
2447
2448 return true;
2449 }
2450
InitPlayers(C4ValueNumbers * numbers)2451 bool C4Game::InitPlayers(C4ValueNumbers * numbers)
2452 {
2453 int32_t iPlrCnt = 0;
2454
2455 if (C4S.Head.NetworkRuntimeJoin)
2456 {
2457 // Load players to restore from scenario
2458 C4PlayerInfoList LocalRestorePlayerInfos;
2459 LocalRestorePlayerInfos.Load(ScenarioFile, C4CFN_SavePlayerInfos, &ScenarioLangStringTable);
2460 // -- runtime join player restore
2461 // all restore functions will be executed on RestorePlayerInfos, because the main playerinfos may be more up-to-date
2462 // extract all players to temp store and update filenames to point there
2463 if (!LocalRestorePlayerInfos.RecreatePlayerFiles())
2464 { LogFatal(LoadResStr("IDS_ERR_NOPLRFILERECR")); return false; }
2465 // recreate the files
2466 if (!LocalRestorePlayerInfos.RecreatePlayers(numbers))
2467 { LogFatal(LoadResStr("IDS_ERR_NOPLRNETRECR")); return false; }
2468 }
2469 else if (RestorePlayerInfos.GetActivePlayerCount(true))
2470 {
2471 // -- savegame player restore
2472 // for savegames or regular scenarios with restore infos, the player info list should have been loaded from the savegame
2473 // or got restored from game text in OpenScenario()
2474 // merge restore player info into main player info list now
2475 // -for single-host games, this will move all infos
2476 // -for network games, it will merge according to savegame association done in the lobby
2477 // for all savegames, script players get restored by adding one new script player for earch savegame script player to the host
2478 if (!PlayerInfos.RestoreSavegameInfos(RestorePlayerInfos))
2479 { LogFatal(LoadResStr("IDS_ERR_NOPLRSAVEINFORECR")); return false; }
2480 RestorePlayerInfos.Clear();
2481 // try to associate local filenames (non-net+replay) or resources (net) with all player infos
2482 if (!PlayerInfos.RecreatePlayerFiles())
2483 { LogFatal(LoadResStr("IDS_ERR_NOPLRFILERECR")); return false; }
2484 // recreate players by joining all players whose joined-flag is already set
2485 if (!PlayerInfos.RecreatePlayers(numbers))
2486 { LogFatal(LoadResStr("IDS_ERR_NOPLRSAVERECR")); return false; }
2487 }
2488
2489 // any regular non-net non-replay game: Do the normal control queue join
2490 // this includes additional player joins in savegames
2491 if (!Network.isEnabled() && !Control.NoInput())
2492 if (!PlayerInfos.LocalJoinUnjoinedPlayersInQueue())
2493 {
2494 // error joining local players - either join was done earlier somehow,
2495 // or the player count check will soon end this round
2496 }
2497
2498 // non-replay player joins will be done by player info list when go tick is reached
2499 // this is handled by C4Network2Players and needs no further treatment here
2500 // set iPlrCnt for player count check in host/single games
2501 iPlrCnt = PlayerInfos.GetJoinIssuedPlayerCount();
2502
2503 // Check valid participating player numbers (host/single only)
2504 if (!Network.isEnabled() || (Network.isHost() && !fLobby))
2505 {
2506 #ifndef USE_CONSOLE
2507 // No players in fullscreen
2508 if (iPlrCnt==0)
2509 if (!Application.isEditor && !Control.NoInput())
2510 {
2511 LogFatal(LoadResStr("IDS_CNS_NOFULLSCREENPLRS")); return false;
2512 }
2513 #endif
2514 // Too many players
2515 if (iPlrCnt>Game.Parameters.MaxPlayers)
2516 {
2517 if (!Application.isEditor)
2518 {
2519 LogFatal(FormatString(LoadResStr("IDS_PRC_TOOMANYPLRS"),Game.Parameters.MaxPlayers).getData());
2520 return false;
2521 }
2522 else
2523 {
2524 Console.Message(FormatString(LoadResStr("IDS_PRC_TOOMANYPLRS"),Game.Parameters.MaxPlayers).getData());
2525 }
2526 }
2527 }
2528 // Console and no real players: halt
2529 if (Console.Active)
2530 if (!fLobby)
2531 if (!(PlayerInfos.GetActivePlayerCount(false) - PlayerInfos.GetActiveScriptPlayerCount(true, false)))
2532 ++HaltCount;
2533 return true;
2534 }
2535
InitControl()2536 bool C4Game::InitControl()
2537 {
2538 // update random seed
2539 if (C4S.Head.NetworkGame || C4S.Head.Replay)
2540 {
2541 RandomSeed = C4S.Head.RandomSeed;
2542 }
2543 // Randomize
2544 FixRandom(RandomSeed);
2545
2546 // Replay?
2547 if (C4S.Head.Replay)
2548 {
2549 // no joins
2550 PlayerFilenames[0]=0;
2551 // start playback
2552 if (!Control.InitReplay(ScenarioFile))
2553 return false;
2554 // no record!
2555 Record = false;
2556 }
2557 else if (Network.isEnabled())
2558 {
2559 // initialize
2560 if (!Control.InitNetwork(Clients.getLocal()))
2561 return false;
2562 // league? always record
2563 if (Parameters.isLeague())
2564 Record = true;
2565 }
2566 // Otherwise: local game
2567 else
2568 {
2569 // init
2570 if (!Control.InitLocal(Clients.getLocal()))
2571 return false;
2572 }
2573
2574 // record?
2575 if (Record)
2576 if (!Control.StartRecord(true, Parameters.doStreaming()))
2577 {
2578 // Special: If this happens for a league host, the game must not start.
2579 if (Network.isEnabled() && Network.isHost() && Parameters.isLeague())
2580 {
2581 LogFatal(LoadResStr("IDS_ERR_NORECORD"));
2582 return false;
2583 }
2584 else
2585 {
2586 Log(LoadResStr("IDS_ERR_NORECORD"));
2587 }
2588 }
2589
2590 return true;
2591 }
2592
ListExpandValids(C4IDList & rlist,C4ID * idlist,int32_t maxidlist)2593 int32_t ListExpandValids(C4IDList &rlist,
2594 C4ID *idlist, int32_t maxidlist)
2595 {
2596 int32_t cnt,cnt2,ccount,cpos;
2597 for (cpos=0,cnt=0; rlist.GetID(cnt); cnt++)
2598 if (C4Id2Def(rlist.GetID(cnt,&ccount)))
2599 for (cnt2=0; cnt2<ccount; cnt2++)
2600 if (cpos<maxidlist)
2601 { idlist[cpos]=rlist.GetID(cnt); cpos++; }
2602 return cpos;
2603 }
2604
PlaceInEarth(C4ID id)2605 bool C4Game::PlaceInEarth(C4ID id)
2606 {
2607 int32_t cnt,tx,ty;
2608 for (cnt=0; cnt<35; cnt++) // cheap trys
2609 {
2610 tx=Random(::Landscape.GetWidth()); ty=Random(::Landscape.GetHeight());
2611 if (GBackMat(tx,ty)==MEarth)
2612 if (CreateObject(id,nullptr,NO_OWNER,tx,ty,Random(360)))
2613 return true;
2614 }
2615 return false;
2616 }
2617
PlaceVegetation_GetRandomPoint(int32_t iX,int32_t iY,int32_t iWdt,int32_t iHgt,C4PropList * shape_proplist,C4PropList * out_pos_proplist,int32_t * piTx,int32_t * piTy)2618 static bool PlaceVegetation_GetRandomPoint(int32_t iX, int32_t iY, int32_t iWdt, int32_t iHgt, C4PropList * shape_proplist, C4PropList * out_pos_proplist, int32_t *piTx, int32_t *piTy)
2619 {
2620 // Helper for C4Game::PlaceVegetation: return random position in rectangle. Use shape_proplist if provided.
2621 if (shape_proplist && out_pos_proplist)
2622 {
2623 C4AulParSet pars(C4VPropList(out_pos_proplist));
2624 if (!shape_proplist->Call(P_GetRandomPoint, &pars)) return false;
2625 *piTx = out_pos_proplist->GetPropertyInt(P_x);
2626 *piTy = out_pos_proplist->GetPropertyInt(P_y);
2627 }
2628 else
2629 {
2630 *piTx = iX + Random(iWdt);
2631 *piTy = iY + Random(iHgt);
2632 }
2633 return true;
2634 }
2635
PlaceVegetation_IsPosInBounds(int32_t iTx,int32_t iTy,int32_t iX,int32_t iY,int32_t iWdt,int32_t iHgt,C4PropList * shape_proplist)2636 static bool PlaceVegetation_IsPosInBounds(int32_t iTx, int32_t iTy, int32_t iX, int32_t iY, int32_t iWdt, int32_t iHgt, C4PropList * shape_proplist)
2637 {
2638 if (shape_proplist)
2639 {
2640 // check using shape proplist
2641 C4AulParSet pars(C4VInt(iTx), C4VInt(iTy));
2642 if (!shape_proplist->Call(P_IsPointContained, &pars)) return false;
2643 }
2644 else
2645 {
2646 // check using bounds rect
2647 if (iTy < iY) return false;
2648 }
2649 return true;
2650 }
2651
PlaceVegetation(C4PropList * PropList,int32_t iX,int32_t iY,int32_t iWdt,int32_t iHgt,int32_t iGrowth,C4PropList * shape_proplist,C4PropList * out_pos_proplist)2652 C4Object* C4Game::PlaceVegetation(C4PropList * PropList, int32_t iX, int32_t iY, int32_t iWdt, int32_t iHgt, int32_t iGrowth, C4PropList * shape_proplist, C4PropList * out_pos_proplist)
2653 {
2654 int32_t cnt,iTx,iTy,iMaterial;
2655
2656 // Get definition
2657 C4Def* pDef;
2658 if (!PropList || !(pDef = PropList->GetDef())) return nullptr;
2659
2660 // No growth specified: full growth
2661 if (iGrowth<=0)
2662 {
2663 iGrowth=FullCon;
2664 }
2665
2666 // Place by placement type
2667 switch (PropList->GetPropertyInt(P_Placement))
2668 {
2669
2670 // Surface soil
2671 case C4D_Place_Surface:
2672 for (cnt=0; cnt<20; cnt++)
2673 {
2674 // Random hit within target area
2675 if (!PlaceVegetation_GetRandomPoint(iX, iY, iWdt, iHgt, shape_proplist, out_pos_proplist, &iTx, &iTy)) break;
2676 // Above tunnel
2677 while ((iTy>0) && Landscape.GetBackPix(iTx,iTy) == 0) iTy--;
2678 // Above semi solid
2679 if (!AboveSemiSolid(iTx,iTy) || !Inside<int32_t>(iTy,50,::Landscape.GetHeight()-50))
2680 continue;
2681 // Still inside bounds?
2682 if (!PlaceVegetation_IsPosInBounds(iTx, iTy, iX, iY, iWdt, iHgt, shape_proplist)) continue;
2683 // Free above
2684 if (GBackSemiSolid(iTx,iTy-pDef->Shape.Hgt) || GBackSemiSolid(iTx,iTy-pDef->Shape.Hgt/2))
2685 continue;
2686 // Free upleft and upright
2687 if (GBackSemiSolid(iTx-pDef->Shape.Wdt/2,iTy-pDef->Shape.Hgt*2/3) || GBackSemiSolid(iTx+pDef->Shape.Wdt/2,iTy-pDef->Shape.Hgt*2/3))
2688 continue;
2689 // Soil check
2690 iTy+=3; // two pix into ground
2691 iMaterial = GBackMat(iTx,iTy);
2692 if (iMaterial!=MNone) if (::MaterialMap.Map[iMaterial].Soil)
2693 {
2694 iTy+=5;
2695 return CreateObjectConstruction(PropList,nullptr,NO_OWNER,iTx,iTy,iGrowth);
2696 }
2697 }
2698 break;
2699
2700 // Underwater
2701 case C4D_Place_Liquid:
2702 // Random range
2703 if (!PlaceVegetation_GetRandomPoint(iX, iY, iWdt, iHgt, shape_proplist, out_pos_proplist, &iTx, &iTy)) return nullptr;
2704 // Find liquid
2705 if (!FindSurfaceLiquid(iTx,iTy,pDef->Shape.Wdt,pDef->Shape.Hgt))
2706 if (!FindLiquid(iTx,iTy,pDef->Shape.Wdt,pDef->Shape.Hgt))
2707 return nullptr;
2708 // Liquid bottom
2709 if (!SemiAboveSolid(iTx,iTy)) return nullptr;
2710 iTy+=3;
2711 // Still inside bounds?
2712 if (!PlaceVegetation_IsPosInBounds(iTx, iTy, iX, iY, iWdt, iHgt, shape_proplist)) return nullptr;
2713 // Create object
2714 return CreateObjectConstruction(PropList,nullptr,NO_OWNER,iTx,iTy,iGrowth);
2715 break;
2716
2717 // Underground/Tunnel
2718 case C4D_Place_Subsurface:
2719 for (cnt=0; cnt<5; cnt++)
2720 {
2721 // Random range
2722 if (!PlaceVegetation_GetRandomPoint(iX, iY, iWdt, iHgt, shape_proplist, out_pos_proplist, &iTx, &iTy)) break;
2723 // Find tunnel
2724 if (!FindTunnel(iTx,iTy,pDef->Shape.Wdt,pDef->Shape.Hgt))
2725 continue;
2726 // Tunnel bottom
2727 if (!AboveSemiSolid(iTx,iTy)) continue;
2728 // Still inside bounds?
2729 if (!PlaceVegetation_IsPosInBounds(iTx, iTy, iX, iY, iWdt, iHgt, shape_proplist)) continue;
2730 // Soil check
2731 iTy+=3; // two pix into ground
2732 iMaterial = GBackMat(iTx,iTy);
2733 if (iMaterial!=MNone) if (::MaterialMap.Map[iMaterial].Soil)
2734 {
2735 // Create object
2736 iTy+=5;
2737 return CreateObjectConstruction(PropList,nullptr,NO_OWNER,iTx,iTy,iGrowth);
2738 }
2739 }
2740
2741 // Under- or aboveground
2742 case C4D_Place_BothSurface:
2743 for (cnt=0; cnt<20; cnt++)
2744 {
2745 // Random hit within target area
2746 if (!PlaceVegetation_GetRandomPoint(iX, iY, iWdt, iHgt, shape_proplist, out_pos_proplist, &iTx, &iTy)) break;
2747 // Above semi solid
2748 if (!AboveSemiSolid(iTx,iTy) || !Inside<int32_t>(iTy,50,::Landscape.GetHeight()-50))
2749 continue;
2750 // Free above
2751 if (GBackSemiSolid(iTx,iTy-pDef->Shape.Hgt) || GBackSemiSolid(iTx,iTy-pDef->Shape.Hgt/2))
2752 continue;
2753 // Still inside bounds?
2754 if (!PlaceVegetation_IsPosInBounds(iTx, iTy, iX, iY, iWdt, iHgt, shape_proplist))
2755 continue;
2756 // Free upleft and upright
2757 if (GBackSemiSolid(iTx-pDef->Shape.Wdt/2,iTy-pDef->Shape.Hgt*2/3) || GBackSemiSolid(iTx+pDef->Shape.Wdt/2,iTy-pDef->Shape.Hgt*2/3))
2758 continue;
2759 // Soil check
2760 iTy+=3; // two pix into ground
2761 iMaterial = GBackMat(iTx,iTy);
2762 if (iMaterial!=MNone) if (::MaterialMap.Map[iMaterial].Soil)
2763 {
2764 iTy+=5;
2765 return CreateObjectConstruction(PropList,nullptr,NO_OWNER,iTx,iTy,iGrowth);
2766 }
2767 }
2768
2769 }
2770
2771 // Undefined placement type
2772 return nullptr;
2773 }
2774
PlaceAnimal(C4PropList * PropList)2775 C4Object* C4Game::PlaceAnimal(C4PropList* PropList)
2776 {
2777 C4Def * pDef;
2778 if (!PropList || !(pDef = PropList->GetDef())) return nullptr;
2779 int32_t iX,iY;
2780 // Placement
2781 switch (PropList->GetPropertyInt(P_Placement))
2782 {
2783 // Running free
2784 case C4D_Place_Surface:
2785 iX=Random(::Landscape.GetWidth()); iY=Random(::Landscape.GetHeight());
2786 if (!FindSolidGround(iX,iY,pDef->Shape.Wdt)) return nullptr;
2787 break;
2788 // In liquid
2789 case C4D_Place_Liquid:
2790 iX=Random(::Landscape.GetWidth()); iY=Random(::Landscape.GetHeight());
2791 if (!FindSurfaceLiquid(iX,iY,pDef->Shape.Wdt,pDef->Shape.Hgt))
2792 if (!FindLiquid(iX,iY,pDef->Shape.Wdt,pDef->Shape.Hgt))
2793 return nullptr;
2794 iY+=pDef->Shape.Hgt/2;
2795 break;
2796 // Floating in air
2797 case C4D_Place_Air:
2798 iX=Random(::Landscape.GetWidth());
2799 for (iY=0; (iY<::Landscape.GetHeight()) && !GBackSemiSolid(iX,iY); iY++) {}
2800 if (iY<=0) return nullptr;
2801 iY=Random(iY);
2802 break;
2803 default:
2804 return nullptr;
2805 }
2806 // Create object
2807 return CreateObject(PropList,nullptr,NO_OWNER,iX,iY);
2808 }
2809
InitInEarth()2810 void C4Game::InitInEarth()
2811 {
2812 const int32_t maxvid=100;
2813 int32_t cnt,vidnum;
2814 C4ID vidlist[maxvid];
2815 // Amount
2816 int32_t amt=(::Landscape.GetWidth()*::Landscape.GetHeight()/5000)*C4S.Landscape.InEarthLevel.Evaluate()/100;
2817 // List all valid IDs from C4S
2818 vidnum=ListExpandValids(C4S.Landscape.InEarth,vidlist,maxvid);
2819 // Place
2820 if (vidnum>0)
2821 for (cnt=0; cnt<amt; cnt++)
2822 PlaceInEarth(vidlist[Random(vidnum)]);
2823
2824 }
2825
InitVegetation()2826 void C4Game::InitVegetation()
2827 {
2828 const int32_t maxvid=100;
2829 int32_t cnt,vidnum;
2830 C4ID vidlist[maxvid];
2831 // Amount
2832 int32_t amt=(::Landscape.GetWidth()/50)*C4S.Landscape.VegLevel.Evaluate()/100;
2833 // Get percentage vidlist from C4S
2834 vidnum=ListExpandValids(C4S.Landscape.Vegetation,vidlist,maxvid);
2835 // Place vegetation
2836 if (vidnum>0)
2837 for (cnt=0; cnt<amt; cnt++)
2838 PlaceVegetation(C4Id2Def(vidlist[Random(vidnum)]),0,0,::Landscape.GetWidth(),::Landscape.GetHeight(),-1,nullptr,nullptr);
2839 }
2840
InitAnimals()2841 void C4Game::InitAnimals()
2842 {
2843 int32_t cnt,cnt2;
2844 C4ID idAnimal; int32_t iCount;
2845 // Place animals
2846 for (cnt=0; (idAnimal=C4S.Animals.FreeLife.GetID(cnt,&iCount)); cnt++)
2847 for (cnt2=0; cnt2<iCount; cnt2++)
2848 PlaceAnimal(C4Id2Def(idAnimal));
2849 // Place nests
2850 for (cnt=0; (idAnimal=C4S.Animals.EarthNest.GetID(cnt,&iCount)); cnt++)
2851 for (cnt2=0; cnt2<iCount; cnt2++)
2852 PlaceInEarth(idAnimal);
2853 }
2854
2855
LoadScenarioComponents()2856 bool C4Game::LoadScenarioComponents()
2857 {
2858 // Info
2859 Info.Load(ScenarioFile,C4CFN_Info);
2860 // Overload clonk names from scenario file
2861 if (ScenarioFile.EntryCount(C4CFN_Names))
2862 Names.Load(ScenarioFile, C4CFN_Names);
2863 // scenario sections
2864 char fn[_MAX_FNAME+1] = { 0 };
2865 ScenarioFile.ResetSearch(); *fn=0;
2866 while (ScenarioFile.FindNextEntry(C4CFN_ScenarioSections, fn, nullptr, !!*fn))
2867 {
2868 // get section name
2869 char SctName[_MAX_FNAME+1];
2870 int32_t iWildcardPos = SCharPos('*', C4CFN_ScenarioSections);
2871 SCopy(fn + iWildcardPos, SctName, _MAX_FNAME);
2872 RemoveExtension(SctName);
2873 if (std::strlen(SctName)>C4MaxName || !*SctName)
2874 {
2875 DebugLog("invalid section name");
2876 LogFatal(FormatString(LoadResStr("IDS_ERR_SCENSECTION"), fn).getData()); return false;
2877 }
2878 // load this section into temp store
2879 C4ScenarioSection *pSection = new C4ScenarioSection(SctName);
2880 if (!pSection->ScenarioLoad(fn, false))
2881 { LogFatal(FormatString(LoadResStr("IDS_ERR_SCENSECTION"), fn).getData()); return false; }
2882 }
2883 // Success
2884 return true;
2885 }
2886
LoadAdditionalSystemGroup(C4Group & parent_group)2887 bool C4Game::LoadAdditionalSystemGroup(C4Group &parent_group)
2888 {
2889 // called for scenario local and definition local System.ocg groups
2890 C4Group SysGroup;
2891 char fn[_MAX_FNAME+1] = { 0 };
2892 if (SysGroup.OpenAsChild(&parent_group, C4CFN_System))
2893 {
2894 C4LangStringTable *pSysGroupString = new C4LangStringTable();
2895 C4Language::LoadComponentHost(pSysGroupString, SysGroup, C4CFN_ScriptStringTbl, Config.General.LanguageEx);
2896 // load custom scenario control definitions
2897 if (SysGroup.FindEntry(C4CFN_PlayerControls))
2898 {
2899 Log(LoadResStr("IDS_PRC_LOADSCEPLRCTRL"));
2900 C4PlayerControlFile PlayerControlFile;
2901 if (!PlayerControlFile.Load(SysGroup, C4CFN_PlayerControls, pSysGroupString))
2902 {
2903 // non-fatal error here
2904 Log(LoadResStr("IDS_PRC_LOADSCEPLRCTRLFAIL"));
2905 }
2906 else
2907 {
2908 // local definitions loaded successfully - merge into global definitions
2909 PlayerControlDefs.MergeFrom(PlayerControlFile.GetControlDefs());
2910 PlayerControlDefaultAssignmentSets.MergeFrom(PlayerControlFile.GetAssignmentSets(), C4PlayerControlAssignmentSet::MM_LowPrio);
2911 PlayerControlDefaultAssignmentSets.ResolveRefs(&PlayerControlDefs);
2912 }
2913 }
2914 // load all scripts in there
2915 SysGroup.ResetSearch();
2916 while (SysGroup.FindNextEntry(C4CFN_ScriptFiles, fn, nullptr, !!fn[0]))
2917 {
2918 // host will be destroyed by script engine, so drop the references
2919 C4ScriptHost *scr = new C4ExtraScriptHost();
2920 scr->Reg2List(&ScriptEngine);
2921 scr->Load(SysGroup, fn, Config.General.LanguageEx, pSysGroupString);
2922 }
2923 // if it's a physical group: watch out for changes
2924 if (!SysGroup.IsPacked() && Game.pFileMonitor)
2925 Game.pFileMonitor->AddDirectory(SysGroup.GetFullName().getData());
2926 SysGroup.Close();
2927 // release string table if no longer used
2928 pSysGroupString->DelRef();
2929 }
2930 return true;
2931 }
2932
InitKeyboard()2933 bool C4Game::InitKeyboard()
2934 {
2935 C4CustomKey::CodeList Keys;
2936
2937 // clear previous
2938 KeyboardInput.Clear();
2939
2940 // globals
2941 KeyboardInput.RegisterKey(new C4CustomKey(C4KeyCodeEx(K_F3 ), "MusicToggle", C4KeyScope(KEYSCOPE_Generic | KEYSCOPE_Gui), new C4KeyCB <C4MusicSystem> (Application.MusicSystem, &C4MusicSystem::ToggleOnOff)));
2942 KeyboardInput.RegisterKey(new C4CustomKey(C4KeyCodeEx(K_F9 ), "Screenshot", C4KeyScope(KEYSCOPE_Fullscreen | KEYSCOPE_Gui), new C4KeyCBEx<C4GraphicsSystem, bool>(GraphicsSystem, false, &C4GraphicsSystem::SaveScreenshotKey)));
2943 KeyboardInput.RegisterKey(new C4CustomKey(C4KeyCodeEx(K_F9, KEYS_Control), "ScreenshotEx", KEYSCOPE_Fullscreen, new C4KeyCBEx<C4GraphicsSystem, bool>(GraphicsSystem, true, &C4GraphicsSystem::SaveScreenshotKey)));
2944 KeyboardInput.RegisterKey(new C4CustomKey(C4KeyCodeEx(K_C, KEYS_Alt), "ToggleChat", C4KeyScope(KEYSCOPE_Generic | KEYSCOPE_Gui), new C4KeyCB <C4Game> (*this, &C4Game::ToggleChat)));
2945
2946 // main ingame
2947 KeyboardInput.RegisterKey(new C4CustomKey(C4KeyCodeEx(K_F1 ), "ToggleShowHelp", KEYSCOPE_Generic, new C4KeyCB <C4GraphicsSystem>(GraphicsSystem, &C4GraphicsSystem::ToggleShowHelp)));
2948 KeyboardInput.RegisterKey(new C4CustomKey(C4KeyCodeEx(K_F4 ), "NetClientListDlgToggle", KEYSCOPE_Generic, new C4KeyCB <C4Network2> (Network, &C4Network2::ToggleClientListDlg)));
2949 KeyboardInput.RegisterKey(new C4CustomKey(C4KeyCodeEx(K_F5 ), "ZoomIn", KEYSCOPE_Generic, new C4KeyCB <C4ViewportList> (::Viewports, &C4ViewportList::ViewportZoomIn)));
2950 KeyboardInput.RegisterKey(new C4CustomKey(C4KeyCodeEx(K_F6 ), "ZoomOut", KEYSCOPE_Generic, new C4KeyCB <C4ViewportList> (::Viewports, &C4ViewportList::ViewportZoomOut)));
2951
2952 // debug mode & debug displays
2953 KeyboardInput.RegisterKey(new C4CustomKey(C4KeyCodeEx(K_F5, KEYS_Control), "DbgModeToggle", KEYSCOPE_Generic, new C4KeyCB <C4Game> (*this, &C4Game::ToggleDebugMode)));
2954 KeyboardInput.RegisterKey(new C4CustomKey(C4KeyCodeEx(K_F6, KEYS_Control), "DbgShowVtxToggle", KEYSCOPE_Generic, new C4KeyCB <C4GraphicsSystem>(GraphicsSystem, &C4GraphicsSystem::ToggleShowVertices)));
2955 KeyboardInput.RegisterKey(new C4CustomKey(C4KeyCodeEx(K_F7, KEYS_Control), "DbgShowActionToggle", KEYSCOPE_Generic, new C4KeyCB <C4GraphicsSystem>(GraphicsSystem, &C4GraphicsSystem::ToggleShowAction)));
2956 KeyboardInput.RegisterKey(new C4CustomKey(C4KeyCodeEx(K_F8, KEYS_Control), "DbgShow8BitSurface", KEYSCOPE_Generic, new C4KeyCB <C4GraphicsSystem>(GraphicsSystem, &C4GraphicsSystem::ToggleShow8BitSurface)));
2957
2958 // playback speed - improve...
2959 KeyboardInput.RegisterKey(new C4CustomKey(C4KeyCodeEx(K_ADD, KEYS_Shift), "GameSpeedUp", KEYSCOPE_Generic, new C4KeyCB <C4Game> (*this, &C4Game::SpeedUp)));
2960 KeyboardInput.RegisterKey(new C4CustomKey(C4KeyCodeEx(K_SUBTRACT, KEYS_Shift), "GameSlowDown", KEYSCOPE_Generic, new C4KeyCB <C4Game> (*this, &C4Game::SlowDown)));
2961
2962 // fullscreen menu
2963 Keys.clear(); Keys.emplace_back(K_LEFT);
2964 if (Config.Controls.GamepadGuiControl) ControllerKeys::Left(Keys);
2965 KeyboardInput.RegisterKey(new C4CustomKey(Keys, "FullscreenMenuLeft", KEYSCOPE_FullSMenu, new C4KeyCBEx<C4FullScreen, BYTE> (FullScreen, COM_MenuLeft, &C4FullScreen::MenuKeyControl)));
2966 Keys.clear(); Keys.emplace_back(K_RIGHT);
2967 if (Config.Controls.GamepadGuiControl) ControllerKeys::Right(Keys);
2968 KeyboardInput.RegisterKey(new C4CustomKey(Keys, "FullscreenMenuRight", KEYSCOPE_FullSMenu, new C4KeyCBEx<C4FullScreen, BYTE> (FullScreen, COM_MenuRight, &C4FullScreen::MenuKeyControl)));
2969 Keys.clear(); Keys.emplace_back(K_UP);
2970 if (Config.Controls.GamepadGuiControl) ControllerKeys::Up(Keys);
2971 KeyboardInput.RegisterKey(new C4CustomKey(Keys, "FullscreenMenuUp", KEYSCOPE_FullSMenu, new C4KeyCBEx<C4FullScreen, BYTE> (FullScreen, COM_MenuUp, &C4FullScreen::MenuKeyControl)));
2972 Keys.clear(); Keys.emplace_back(K_DOWN);
2973 if (Config.Controls.GamepadGuiControl) ControllerKeys::Down(Keys);
2974 KeyboardInput.RegisterKey(new C4CustomKey(Keys, "FullscreenMenuDown", KEYSCOPE_FullSMenu, new C4KeyCBEx<C4FullScreen, BYTE> (FullScreen, COM_MenuDown, &C4FullScreen::MenuKeyControl)));
2975 Keys.clear(); Keys.emplace_back(K_SPACE); Keys.emplace_back(K_RETURN);
2976 if (Config.Controls.GamepadGuiControl) ControllerKeys::Ok(Keys);
2977 KeyboardInput.RegisterKey(new C4CustomKey(Keys, "FullscreenMenuOK", KEYSCOPE_FullSMenu, new C4KeyCBEx<C4FullScreen, BYTE> (FullScreen, COM_MenuEnter, &C4FullScreen::MenuKeyControl))); // name used by PlrControlKeyName
2978 Keys.clear(); Keys.emplace_back(K_ESCAPE);
2979 if (Config.Controls.GamepadGuiControl) ControllerKeys::Cancel(Keys);
2980 KeyboardInput.RegisterKey(new C4CustomKey(Keys, "FullscreenMenuCancel", KEYSCOPE_FullSMenu, new C4KeyCBEx<C4FullScreen, BYTE> (FullScreen, COM_MenuClose, &C4FullScreen::MenuKeyControl))); // name used by PlrControlKeyName
2981 Keys.clear(); Keys.emplace_back(K_SPACE);
2982 if (Config.Controls.GamepadGuiControl) ControllerKeys::Any(Keys);
2983 KeyboardInput.RegisterKey(new C4CustomKey(Keys, "FullscreenMenuOpen", KEYSCOPE_FreeView, new C4KeyCB <C4FullScreen> (FullScreen, &C4FullScreen::ActivateMenuMain))); // name used by C4MainMenu!
2984 KeyboardInput.RegisterKey(new C4CustomKey(C4KeyCodeEx(K_RIGHT ), "FilmNextPlayer", KEYSCOPE_FilmView, new C4KeyCB <C4ViewportList>(::Viewports, &C4ViewportList::ViewportNextPlayer)));
2985
2986 // chat
2987 Keys.clear();
2988 Keys.emplace_back(K_RETURN);
2989 Keys.emplace_back(K_F2); // alternate chat key, if RETURN is blocked by player control
2990 KeyboardInput.RegisterKey(new C4CustomKey(Keys, "ChatOpen", KEYSCOPE_Generic, new C4KeyCBEx<C4MessageInput, bool>(MessageInput, false, &C4MessageInput::KeyStartTypeIn)));
2991 KeyboardInput.RegisterKey(new C4CustomKey(C4KeyCodeEx(K_RETURN, KEYS_Shift), "ChatOpen2Allies", KEYSCOPE_Generic, new C4KeyCBEx<C4MessageInput, bool>(MessageInput, true, &C4MessageInput::KeyStartTypeIn)));
2992
2993 KeyboardInput.RegisterKey(new C4CustomKey(C4KeyCodeEx(K_LEFT ), "FreeViewScrollLeft", KEYSCOPE_FreeView, new C4KeyCBEx<C4ViewportList, C4Vec2D>(::Viewports, C4Vec2D(-5, 0), &C4ViewportList::FreeScroll)));
2994 KeyboardInput.RegisterKey(new C4CustomKey(C4KeyCodeEx(K_RIGHT ), "FreeViewScrollRight", KEYSCOPE_FreeView, new C4KeyCBEx<C4ViewportList, C4Vec2D>(::Viewports, C4Vec2D(+5, 0), &C4ViewportList::FreeScroll)));
2995 KeyboardInput.RegisterKey(new C4CustomKey(C4KeyCodeEx(K_UP ), "FreeViewScrollUp", KEYSCOPE_FreeView, new C4KeyCBEx<C4ViewportList, C4Vec2D>(::Viewports, C4Vec2D(0, -5), &C4ViewportList::FreeScroll)));
2996 KeyboardInput.RegisterKey(new C4CustomKey(C4KeyCodeEx(K_DOWN ), "FreeViewScrollDown", KEYSCOPE_FreeView, new C4KeyCBEx<C4ViewportList, C4Vec2D>(::Viewports, C4Vec2D(0, +5), &C4ViewportList::FreeScroll)));
2997 KeyboardInput.RegisterKey(new C4CustomKey(C4KeyCodeEx(K_TAB ), "ScoreboardToggle", KEYSCOPE_Generic, new C4KeyCB <C4Scoreboard>(Scoreboard, &C4Scoreboard::KeyUserShow)));
2998 KeyboardInput.RegisterKey(new C4CustomKey(C4KeyCodeEx(K_ESCAPE ), "GameAbort", KEYSCOPE_Fullscreen, new C4KeyCB <C4FullScreen>(FullScreen, &C4FullScreen::ShowAbortDlg)));
2999 KeyboardInput.RegisterKey(new C4CustomKey(C4KeyCodeEx(K_PAUSE ), "FullscreenPauseToggle", KEYSCOPE_Fullscreen, new C4KeyCB <C4Game>(Game, &C4Game::TogglePause)));
3000
3001 // console keys
3002 KeyboardInput.RegisterKey(new C4CustomKey(C4KeyCodeEx(K_PAUSE ), "ConsolePauseToggle", KEYSCOPE_Console, new C4KeyCB <C4Console>(Console, &C4Console::TogglePause)));
3003 KeyboardInput.RegisterKey(new C4CustomKey(C4KeyCodeEx(K_ADD ), "ToolsDlgGradeUp", KEYSCOPE_Console, new C4KeyCBEx<C4ToolsDlg, int32_t>(Console.ToolsDlg, +5, &C4ToolsDlg::ChangeGrade)));
3004 KeyboardInput.RegisterKey(new C4CustomKey(C4KeyCodeEx(K_SUBTRACT ), "ToolsDlgGradeDown", KEYSCOPE_Console, new C4KeyCBEx<C4ToolsDlg, int32_t>(Console.ToolsDlg, -5, &C4ToolsDlg::ChangeGrade)));
3005 KeyboardInput.RegisterKey(new C4CustomKey(C4KeyCodeEx(K_M, KEYS_Control), "ToolsDlgPopMaterial", KEYSCOPE_Console, new C4KeyCB <C4ToolsDlg>(Console.ToolsDlg, &C4ToolsDlg::PopMaterial)));
3006 KeyboardInput.RegisterKey(new C4CustomKey(C4KeyCodeEx(K_T, KEYS_Control), "ToolsDlgPopTextures", KEYSCOPE_Console, new C4KeyCB <C4ToolsDlg>(Console.ToolsDlg, &C4ToolsDlg::PopTextures)));
3007 KeyboardInput.RegisterKey(new C4CustomKey(C4KeyCodeEx(K_I, KEYS_Control), "ToolsDlgIFTToggle", KEYSCOPE_Console, new C4KeyCB <C4ToolsDlg>(Console.ToolsDlg, &C4ToolsDlg::ToggleIFT)));
3008 KeyboardInput.RegisterKey(new C4CustomKey(C4KeyCodeEx(K_W, KEYS_Control), "ToolsDlgToolToggle", KEYSCOPE_Console, new C4KeyCB <C4ToolsDlg>(Console.ToolsDlg, &C4ToolsDlg::ToggleTool)));
3009 KeyboardInput.RegisterKey(new C4CustomKey(C4KeyCodeEx(K_DELETE ), "EditCursorDelete", KEYSCOPE_Console, new C4KeyCB <C4EditCursor>(Console.EditCursor, &C4EditCursor::Delete)));
3010
3011 // no default keys assigned
3012 KeyboardInput.RegisterKey(new C4CustomKey(C4KeyCodeEx(KEY_Default ), "EditCursorModeToggle", KEYSCOPE_Console, new C4KeyCB <C4EditCursor>(Console.EditCursor, &C4EditCursor::ToggleMode)));
3013 KeyboardInput.RegisterKey(new C4CustomKey(C4KeyCodeEx(KEY_Default ), "ChartToggle", C4KeyScope(KEYSCOPE_Generic | KEYSCOPE_Gui), new C4KeyCB <C4Game> (*this, &C4Game::ToggleChart)));
3014 KeyboardInput.RegisterKey(new C4CustomKey(C4KeyCodeEx(KEY_Default ), "NetObsNextPlayer", KEYSCOPE_FreeView, new C4KeyCB <C4ViewportList>(::Viewports, &C4ViewportList::ViewportNextPlayer)));
3015 KeyboardInput.RegisterKey(new C4CustomKey(C4KeyCodeEx(KEY_Default ), "CtrlRateDown", KEYSCOPE_Generic, new C4KeyCBEx<C4GameControl, int32_t>(Control, -1, &C4GameControl::KeyAdjustControlRate)));
3016 KeyboardInput.RegisterKey(new C4CustomKey(C4KeyCodeEx(KEY_Default ), "CtrlRateUp", KEYSCOPE_Generic, new C4KeyCBEx<C4GameControl, int32_t>(Control, +1, &C4GameControl::KeyAdjustControlRate)));
3017 KeyboardInput.RegisterKey(new C4CustomKey(C4KeyCodeEx(KEY_Default ), "NetAllowJoinToggle", KEYSCOPE_Generic, new C4KeyCB <C4Network2> (Network, &C4Network2::ToggleAllowJoin)));
3018 KeyboardInput.RegisterKey(new C4CustomKey(C4KeyCodeEx(KEY_Default ), "NetStatsToggle", KEYSCOPE_Generic, new C4KeyCB <C4GraphicsSystem>(GraphicsSystem, &C4GraphicsSystem::ToggleShowNetStatus)));
3019
3020 // load any custom keysboard overloads
3021 KeyboardInput.LoadCustomConfig();
3022
3023 // done, success
3024 return true;
3025 }
3026
UpdateLanguage()3027 void C4Game::UpdateLanguage()
3028 {
3029 // Reload System.ocg string table
3030 C4Language::LoadComponentHost(&MainSysLangStringTable, Application.SystemGroup, C4CFN_ScriptStringTbl, Config.General.LanguageEx);
3031 }
3032
InitPlayerControlSettings()3033 bool C4Game::InitPlayerControlSettings()
3034 {
3035 // Load controls and default control sets
3036 C4PlayerControlFile PlayerControlFile;
3037 if (!PlayerControlFile.Load(Application.SystemGroup, C4CFN_PlayerControls, &MainSysLangStringTable)) { LogFatal("[!]Error loading player controls"); return false; }
3038 PlayerControlDefs = PlayerControlFile.GetControlDefs();
3039 PlayerControlDefaultAssignmentSets.Clear();
3040 PlayerControlDefaultAssignmentSets.MergeFrom(PlayerControlFile.GetAssignmentSets(), C4PlayerControlAssignmentSet::MM_Normal);
3041 PlayerControlDefaultAssignmentSets.ResolveRefs(&PlayerControlDefs);
3042 // Merge w/ config settings into user control sets
3043 // User settings will be cleared and re-merged again later after scenario/definition control overloads, but initialization
3044 // is needed already for config dialogs
3045 if (!InitPlayerControlUserSettings()) return false;
3046 return true;
3047 }
3048
InitPlayerControlUserSettings()3049 bool C4Game::InitPlayerControlUserSettings()
3050 {
3051 // Merge config control settings with user settings
3052 PlayerControlUserAssignmentSets.Clear();
3053 PlayerControlUserAssignmentSets.MergeFrom(PlayerControlDefaultAssignmentSets, C4PlayerControlAssignmentSet::MM_Inherit);
3054 PlayerControlUserAssignmentSets.MergeFrom(Config.Controls.UserSets, C4PlayerControlAssignmentSet::MM_ConfigOverload);
3055 PlayerControlUserAssignmentSets.ResolveRefs(&PlayerControlDefs);
3056 return true;
3057 }
3058
JoinPlayer(const char * szFilename,int32_t iAtClient,const char * szAtClientName,C4PlayerInfo * pInfo)3059 C4Player *C4Game::JoinPlayer(const char *szFilename, int32_t iAtClient, const char *szAtClientName, C4PlayerInfo *pInfo)
3060 {
3061 assert(pInfo);
3062 C4Player *pPlr;
3063 // Join
3064 if (!( pPlr = Players.Join(szFilename,true,iAtClient,szAtClientName, pInfo, nullptr) )) return nullptr;
3065 // Player final init
3066 pPlr->FinalInit(true);
3067 // Create player viewport
3068 if (pPlr->LocalControl) ::Viewports.CreateViewport(pPlr->Number);
3069 // Check fullscreen viewports
3070 FullScreen.ViewportCheck();
3071 // Update menus
3072 Console.UpdateMenus();
3073 // Append player name to list of session player names (no duplicates)
3074 if (!SIsModule(PlayerNames.getData(), pPlr->GetName()))
3075 {
3076 if (PlayerNames) PlayerNames += ";";
3077 PlayerNames += pPlr->GetName();
3078 }
3079 // Success
3080 return pPlr;
3081 }
3082
OnPlayerJoinFinished()3083 void C4Game::OnPlayerJoinFinished()
3084 {
3085 // Do the InitializePlayers callback once all player joins have finished with at least one human player
3086 if (!InitialPlayersJoined && !PlayerInfos.GetJoinPendingPlayerCount() && !::Players.HasPlayerInTeamSelection() && (::Players.GetCount(C4PT_User) > 0))
3087 {
3088 InitialPlayersJoined = true;
3089 GRBroadcast(PSF_InitializePlayers);
3090 }
3091 }
3092
FixRandom(uint64_t iSeed)3093 void C4Game::FixRandom(uint64_t iSeed)
3094 {
3095 FixedRandom(iSeed);
3096 }
3097
DoGameOver()3098 bool C4Game::DoGameOver()
3099 {
3100 // Duplication safety
3101 if (GameOver) return false;
3102 // Flag, log, call
3103 GameOver=true;
3104 Log(LoadResStr("IDS_PRC_GAMEOVER"));
3105 GRBroadcast(PSF_OnGameOver);
3106 // Flag all surviving players as winners
3107 for (C4Player *pPlayer = Players.First; pPlayer; pPlayer = pPlayer->Next)
3108 if (!pPlayer->Eliminated)
3109 pPlayer->EvaluateLeague(false, true);
3110 // Immediately save config so mission access gained by this round is stored if the game crashes during shutdown
3111 // or in a following round
3112 Config.Save();
3113 return true;
3114 }
3115
ShowGameOverDlg()3116 void C4Game::ShowGameOverDlg()
3117 {
3118 // safety
3119 if (GameOverDlgShown) return;
3120 // flag, show
3121 GameOverDlgShown = true;
3122 #ifndef USE_CONSOLE
3123 if (!Application.isEditor)
3124 {
3125 C4GameOverDlg *pDlg = new C4GameOverDlg();
3126 pDlg->SetDelOnClose();
3127 if (!pDlg->Show(pGUI, true)) { delete pDlg; Application.QuitGame(); }
3128 }
3129 #endif
3130 }
3131
SyncClearance()3132 void C4Game::SyncClearance()
3133 {
3134 Objects.SyncClearance();
3135 }
3136
Synchronize(bool fSavePlayerFiles)3137 void C4Game::Synchronize(bool fSavePlayerFiles)
3138 {
3139 // Log
3140 LogSilentF("Network: Synchronization (Frame %i) [PlrSave: %d]",FrameCounter, fSavePlayerFiles);
3141 // callback to control (to start record)
3142 Control.OnGameSynchronizing();
3143 // Fix random
3144 FixRandom(RandomSeed);
3145 // Synchronize members
3146 ::Definitions.Synchronize();
3147 Landscape.Synchronize();
3148 MassMover.Synchronize();
3149 Objects.Synchronize();
3150 // synchronize local player files if desired
3151 // this will reset any InActionTimes!
3152 // (not in replay mode)
3153 if (fSavePlayerFiles && !Control.isReplay()) Players.SynchronizeLocalFiles();
3154 // callback to network
3155 if (Network.isEnabled()) Network.OnGameSynchronized();
3156 // TransferZone synchronization: Must do this after dynamic creation to avoid synchronization loss
3157 // if OnSynchronized-callbacks do sync-relevant changes
3158 TransferZones.Synchronize();
3159 }
3160
InitNetworkFromAddress(const char * szAddress)3161 bool C4Game::InitNetworkFromAddress(const char *szAddress)
3162 {
3163 StdCopyStrBuf strRefQueryFailed(LoadResStr("IDS_NET_REFQUERY_FAILED"));
3164 // Query reference
3165 C4Network2RefClient RefClient;
3166 if (!RefClient.Init() ||
3167 !RefClient.SetServer(szAddress) ||
3168 !RefClient.QueryReferences())
3169 { LogFatal(FormatString(strRefQueryFailed.getData(), RefClient.GetError()).getData()); return false; }
3170 // We have to wait for the answer
3171 StdStrBuf Message = FormatString(LoadResStr("IDS_NET_REFQUERY_QUERYMSG"), szAddress);
3172 Log(Message.getData());
3173 // Set up wait dialog
3174 C4GUI::MessageDialog *pDlg = nullptr;
3175 if (!Application.isEditor)
3176 {
3177 // create & show
3178 pDlg = new C4GUI::MessageDialog(Message.getData(), LoadResStr("IDS_NET_REFQUERY_QUERYTITLE"),
3179 C4GUI::MessageDialog::btnAbort, C4GUI::Ico_NetWait, C4GUI::MessageDialog::dsMedium);
3180 if (!pDlg || !pDlg->Show(::pGUI, true)) return false;
3181 }
3182 // Wait for response
3183 while (RefClient.isBusy())
3184 {
3185 // Execute GUI
3186 if (!Application.ScheduleProcs(100) ||
3187 (pDlg && pDlg->IsAborted()))
3188 {
3189 delete pDlg;
3190 return false;
3191 }
3192 // Check if reference is received
3193 if (!RefClient.Execute(0))
3194 break;
3195 }
3196 // Close dialog
3197 delete pDlg;
3198 // Error?
3199 if (!RefClient.isSuccess())
3200 { LogFatal(FormatString(strRefQueryFailed.getData(), RefClient.GetError()).getData()); return false; }
3201 // Get references
3202 C4Network2Reference **ppRefs = nullptr; int32_t iRefCount;
3203 if (!RefClient.GetReferences(ppRefs, iRefCount) || iRefCount <= 0)
3204 { LogFatal(FormatString(strRefQueryFailed.getData(), LoadResStr("IDS_NET_REFQUERY_NOREF")).getData()); return false; }
3205 // Connect to first reference
3206 bool fSuccess = InitNetworkFromReference(*ppRefs[0]);
3207 // Remove references
3208 for (int i = 0; i < iRefCount; i++)
3209 delete ppRefs[i];
3210 delete[] ppRefs;
3211 return fSuccess;
3212 }
3213
InitNetworkFromReferenceFile(const char * temp_filename)3214 bool C4Game::InitNetworkFromReferenceFile(const char *temp_filename)
3215 {
3216 // We need winsock for address parsing
3217 WinSockHolder ws;
3218 // Load reference from temp file + delete the temp file
3219 bool success = false;
3220 C4Network2Reference ref;
3221 StdStrBuf join_data;
3222 if (join_data.LoadFromFile(temp_filename))
3223 {
3224 CompileFromBuf<StdCompilerINIRead>(mkNamingAdapt(ref, "Reference"), join_data);
3225 success = true;
3226 }
3227 EraseFile(temp_filename);
3228 if (!success) return false;
3229 return InitNetworkFromReference(ref);
3230 }
3231
InitNetworkFromReference(const C4Network2Reference & Reference)3232 bool C4Game::InitNetworkFromReference(const C4Network2Reference &Reference)
3233 {
3234 // Find host data
3235 C4Client *pHostData = Reference.Parameters.Clients.getClientByID(C4ClientIDHost);
3236 if (!pHostData) { LogFatal(LoadResStr("IDS_NET_INVALIDREF")); return false; }
3237 // Save scenario title
3238 ScenarioTitle = Reference.getTitle();
3239 // Log
3240 LogF(LoadResStr("IDS_NET_JOINGAMEBY"), pHostData->getName());
3241 // Init clients
3242 if (!Clients.Init())
3243 return false;
3244 // Connect
3245 if (Network.InitClient(Reference, false) != C4Network2::IR_Success)
3246 {
3247 LogFatal(FormatString(LoadResStr("IDS_NET_NOHOSTCON"), pHostData->getName()).getData());
3248 return false;
3249 }
3250 // init control
3251 if (!Control.InitNetwork(Clients.getLocal())) return false;
3252 // init local player info list
3253 Network.Players.Init();
3254 return true;
3255 }
3256
InitNetworkHost()3257 bool C4Game::InitNetworkHost()
3258 {
3259 // Network not active?
3260 if (!NetworkActive)
3261 {
3262 // Clear client list
3263 if (!C4S.Head.Replay)
3264 Clients.Init();
3265 return true;
3266 }
3267 // network not active?
3268 if (C4S.Head.NetworkGame)
3269 { LogFatal(LoadResStr("IDS_NET_NODIRECTSTART")); return Clients.Init(); }
3270 // replay?
3271 if (C4S.Head.Replay)
3272 { LogFatal(LoadResStr("IDS_PRC_NONETREPLAY")); return true; }
3273 // clear client list
3274 if (!Clients.Init())
3275 return false;
3276 // init network as host
3277 if (!Network.InitHost(fLobby)) return false;
3278 // init control
3279 if (!Control.InitNetwork(Clients.getLocal())) return false;
3280 // init local player info list
3281 Network.Players.Init();
3282 // allow connect
3283 Network.AllowJoin(true);
3284 // do lobby (if desired)
3285 if (fLobby)
3286 {
3287 if (!Network.DoLobby()) return false;
3288 }
3289 else
3290 {
3291 // otherwise: start manually
3292 if (!Network.Start()) return false;
3293 }
3294 // ok
3295 return true;
3296 }
3297
CheckObjectEnumeration()3298 bool C4Game::CheckObjectEnumeration()
3299 {
3300
3301 struct Check
3302 {
3303 int32_t maxNumber{0};
3304 Check() = default;
3305 // Check valid & maximum number & duplicate numbers
3306 bool that(C4Object* cObj)
3307 {
3308 // Invalid number
3309 if (cObj->Number<1)
3310 {
3311 LogFatal(FormatString("Invalid object enumeration number (%d) of object %s (x=%d)", cObj->Number, cObj->id.ToString(), cObj->GetX()).getData()); return false;
3312 }
3313 // Max
3314 if (cObj->Number>maxNumber) maxNumber=cObj->Number;
3315 // Duplicate
3316 for (C4Object *cObj2 : Objects)
3317 if (cObj2!=cObj)
3318 if (cObj->Number==cObj2->Number)
3319 { LogFatal(FormatString("Duplicate object enumeration number %d (%s and %s)",cObj2->Number,cObj->GetName(),cObj2->GetName()).getData()); return false; }
3320 for (C4Object *cObj2 : Objects.InactiveObjects)
3321 if (cObj2!=cObj)
3322 if (cObj->Number==cObj2->Number)
3323 { LogFatal(FormatString("Duplicate object enumeration number %d (%s and %s(i))",cObj2->Number,cObj->GetName(),cObj2->GetName()).getData()); return false; }
3324 return true;
3325 }
3326 };
3327
3328 Check check;
3329 for (C4Object *cObj : Objects)
3330 {
3331 if (!check.that(cObj))
3332 return false;
3333 }
3334
3335 for (C4Object *cObj : Objects.InactiveObjects)
3336 {
3337 if (!check.that(cObj))
3338 return false;
3339 }
3340
3341 // Adjust enumeration index
3342 C4PropListNumbered::SetEnumerationIndex(check.maxNumber);
3343 // Done
3344 return true;
3345 }
3346
InitValueOverloads()3347 void C4Game::InitValueOverloads()
3348 {
3349 C4ID idOvrl; C4Def *pDef;
3350 // set new values
3351 for (int32_t cnt=0; (idOvrl=C4S.Game.Realism.ValueOverloads.GetID(cnt)); cnt++)
3352 if ((pDef=::Definitions.ID2Def(idOvrl)))
3353 pDef->Value=C4S.Game.Realism.ValueOverloads.GetIDCount(idOvrl);
3354 }
3355
InitEnvironment()3356 void C4Game::InitEnvironment()
3357 {
3358 // Place environment objects
3359 int32_t cnt,cnt2;
3360 C4ID idType; int32_t iCount;
3361 for (cnt=0; (idType=C4S.Environment.Objects.GetID(cnt,&iCount)); cnt++)
3362 for (cnt2=0; cnt2<iCount; cnt2++)
3363 CreateObject(idType,nullptr);
3364 }
3365
InitRules()3366 void C4Game::InitRules()
3367 {
3368 // Place rule objects
3369 int32_t cnt,cnt2;
3370 C4ID idType; int32_t iCount;
3371 for (cnt=0; (idType=Parameters.Rules.GetID(cnt,&iCount)); cnt++)
3372 for (cnt2=0; cnt2<std::max<int32_t>(iCount,1); cnt2++)
3373 CreateObject(idType,nullptr);
3374 }
3375
InitGoals()3376 void C4Game::InitGoals()
3377 {
3378 // Place goal objects
3379 int32_t cnt,cnt2;
3380 C4ID idType; int32_t iCount;
3381 for (cnt=0; (idType=Parameters.Goals.GetID(cnt,&iCount)); cnt++)
3382 for (cnt2=0; cnt2<iCount; cnt2++)
3383 CreateObject(idType,nullptr);
3384 }
3385
SetInitProgress(float fToProgress)3386 void C4Game::SetInitProgress(float fToProgress)
3387 {
3388 // set new progress
3389 InitProgress=int32_t(fToProgress);
3390 // if progress is more than one percent, display it
3391 if (InitProgress > LastInitProgress)
3392 {
3393 LastInitProgress=InitProgress;
3394 GraphicsSystem.MessageBoard->LogNotify();
3395 }
3396 // Cheap hack to get the Console window updated while loading
3397 // (unless game is running, i.e. section change - section change would be quick and timer execution can mess with things unpredictably)
3398 if (!IsRunning)
3399 {
3400 Application.FlushMessages();
3401 #ifdef WITH_QT_EDITOR
3402 Application.ProcessQtEvents();
3403 #endif
3404 }
3405 }
3406
OnResolutionChanged(unsigned int iXRes,unsigned int iYRes)3407 void C4Game::OnResolutionChanged(unsigned int iXRes, unsigned int iYRes)
3408 {
3409 // update anything that's dependant on screen resolution
3410 pGUI->SetBounds(C4Rect(0,0,iXRes,iYRes));
3411 if (FullScreen.Active)
3412 InitFullscreenComponents(!!IsRunning);
3413 // note that this may fail if the gfx groups are closed already (runtime resolution change)
3414 // doesn't matter; old gfx are kept in this case
3415 GraphicsResource.ReloadResolutionDependantFiles();
3416 ::Viewports.RecalculateViewports();
3417 }
3418
OnKeyboardLayoutChanged()3419 void C4Game::OnKeyboardLayoutChanged()
3420 {
3421 // Layout changed: Re-resolve keys
3422 PlayerControlDefaultAssignmentSets.ResolveRefs(&PlayerControlDefs);
3423 PlayerControlUserAssignmentSets.ResolveRefs(&PlayerControlDefs);
3424 }
3425
CreateSectionFromTempFile(const char * section_name,const char * temp_filename)3426 bool C4Game::CreateSectionFromTempFile(const char *section_name, const char *temp_filename)
3427 {
3428 // Remove existing (temp) section of same name
3429 C4ScenarioSection *existing_section = pScenarioSections, *prev = nullptr;
3430 while (existing_section) if (SEqualNoCase(existing_section->name.getData(), section_name)) break; else existing_section = (prev = existing_section)->pNext;
3431 bool deleted_current_section = false;
3432 if (existing_section)
3433 {
3434 deleted_current_section = (existing_section == pCurrentScenarioSection);
3435 if (deleted_current_section)
3436 {
3437 pCurrentScenarioSection = nullptr;
3438 pScenarioObjectsScript = nullptr;
3439 }
3440 if (existing_section->pObjectScripts)
3441 {
3442 delete existing_section->pObjectScripts;
3443 }
3444 if (prev) prev->pNext = existing_section->pNext; else pScenarioSections = existing_section->pNext;
3445 existing_section->pNext = nullptr;
3446 delete existing_section;
3447 }
3448 // Create new (temp) section
3449 C4ScenarioSection *new_section = new C4ScenarioSection(section_name);
3450 if (!new_section->ScenarioLoad(temp_filename, true))
3451 {
3452 pScenarioSections = new_section->pNext;
3453 new_section->pNext = nullptr;
3454 delete new_section;
3455 return false;
3456 }
3457 // Re-Link current section into newly created section
3458 if (deleted_current_section)
3459 {
3460 pCurrentScenarioSection = new_section;
3461 pScenarioObjectsScript = new_section->pObjectScripts;
3462 }
3463 // Link new Objects.c (or re-link because old Objects.c was removed)
3464 ReLinkScriptEngine();
3465 return !!new_section;
3466 }
3467
LoadScenarioSection(const char * szSection,DWORD dwFlags)3468 bool C4Game::LoadScenarioSection(const char *szSection, DWORD dwFlags)
3469 {
3470 // note on scenario section saving:
3471 // if a scenario section overwrites a value that had used the default values in the main scenario section,
3472 // returning to the main section with an unsaved landscape (and thus an unsaved scenario core),
3473 // would leave those values in the altered state of the previous section
3474 // scenario designers should regard this and always define any values, that are defined in subsections as well
3475 C4Group hGroup, *pGrp;
3476 // if current section was the loaded section (maybe main, but need not for resumed savegames)
3477 if (!pCurrentScenarioSection)
3478 {
3479 if (!*CurrentScenarioSection) SCopy(C4ScenSect_Main, CurrentScenarioSection, C4MaxName);
3480 pCurrentScenarioSection = new C4ScenarioSection(CurrentScenarioSection);
3481 pCurrentScenarioSection->pObjectScripts = Game.pScenarioObjectsScript;
3482 }
3483 // find section to load
3484 C4ScenarioSection *pLoadSect = pScenarioSections;
3485 while (pLoadSect) if (SEqualNoCase(pLoadSect->name.getData(), szSection)) break; else pLoadSect = pLoadSect->pNext;
3486 if (!pLoadSect)
3487 {
3488 DebugLogF("LoadScenarioSection: scenario section %s not found!", szSection);
3489 return false;
3490 }
3491 // save current section state
3492 if (pLoadSect != pCurrentScenarioSection && dwFlags & (C4S_SAVE_LANDSCAPE | C4S_SAVE_OBJECTS))
3493 {
3494 // ensure that the section file does point to temp store
3495 if (!pCurrentScenarioSection->EnsureTempStore(!(dwFlags & C4S_SAVE_LANDSCAPE), !(dwFlags & C4S_SAVE_OBJECTS)))
3496 {
3497 DebugLogF("LoadScenarioSection(%s): could not extract section files of current section %s", szSection, pCurrentScenarioSection->name.getData());
3498 return false;
3499 }
3500 // open current group
3501 if (!(pGrp = pCurrentScenarioSection->GetGroupfile(hGroup)))
3502 {
3503 DebugLog("LoadScenarioSection: error opening current group file");
3504 return false;
3505 }
3506 // store landscape, if desired (w/o material enumeration - that's assumed to stay consistent during the game)
3507 if (dwFlags & C4S_SAVE_LANDSCAPE)
3508 {
3509 // storing the landscape implies storing the scenario core
3510 // otherwise, the ExactLandscape-flag would be lost
3511 // maybe imply exact landscapes by the existance of Landscape.png-files?
3512 C4Scenario rC4S = C4S;
3513 rC4S.SetExactLandscape();
3514 if (!rC4S.Save(*pGrp, true))
3515 {
3516 DebugLog("LoadScenarioSection: Error saving C4S");
3517 return false;
3518 }
3519 // landscape
3520 {
3521 C4DebugRecOff DBGRECOFF;
3522 if (!Landscape.Save(*pGrp))
3523 {
3524 DebugLog("LoadScenarioSection: Error saving Landscape");
3525 return false;
3526 }
3527 }
3528 // PXS
3529 if (!PXS.Save(*pGrp))
3530 {
3531 DebugLog("LoadScenarioSection: Error saving PXS");
3532 return false;
3533 }
3534 // MassMover (create copy, may not modify running data)
3535 C4MassMoverSet MassMoverSet;
3536 MassMoverSet.Copy(MassMover);
3537 if (!MassMoverSet.Save(*pGrp))
3538 {
3539 DebugLog("LoadScenarioSection: Error saving MassMover");
3540 return false;
3541 }
3542 }
3543 // store objects
3544 if (dwFlags & C4S_SAVE_OBJECTS)
3545 {
3546 C4ValueNumbers numbers;
3547 // objects: do not save info objects or inactive objects
3548 if (!SaveData(*pGrp,true,false, false, &numbers))
3549 {
3550 DebugLog("LoadScenarioSection: Error saving objects");
3551 return false;
3552 }
3553 }
3554 // close current group
3555 if (hGroup.IsOpen()) hGroup.Close();
3556 // mark modified
3557 pCurrentScenarioSection->fModified = true;
3558 }
3559 // open section group
3560 if (!(pGrp=pLoadSect->GetGroupfile(hGroup)))
3561 {
3562 DebugLog("LoadScenarioSection: error opening group file");
3563 return false;
3564 }
3565 // remove all objects
3566 // do correct removal calls, because this will stop fire sounds, etc.
3567 for (C4Object *obj : Objects)
3568 obj->AssignRemoval();
3569 for (C4Object *obj : Objects)
3570 if (obj->Status)
3571 {
3572 DebugLogF("LoadScenarioSection: WARNING: Object %d created in destruction process!", (int) obj->Number);
3573 ClearPointers(obj);
3574 }
3575 // Final removal in case objects got recreated
3576 // Also kill inactive objects if scenario is reinitialized
3577 DeleteObjects(!!(dwFlags & C4S_REINIT_SCENARIO));
3578 // remove global effects
3579 if (::ScriptEngine.pGlobalEffects && !(dwFlags & C4S_KEEP_EFFECTS))
3580 {
3581 ::ScriptEngine.pGlobalEffects->ClearAll(C4FxCall_RemoveClear);
3582 // scenario section call might have been done from a global effect
3583 // rely on dead effect removal for actually removing the effects; do not clear the array here!
3584 }
3585 if (::GameScript.pScenarioEffects && !(dwFlags & C4S_KEEP_EFFECTS))
3586 ::GameScript.pScenarioEffects->ClearAll(C4FxCall_RemoveClear);
3587 // del particles as well
3588 Particles.ClearAllParticles();
3589 // clear transfer zones
3590 TransferZones.Clear();
3591 // backup old sky
3592 std::string old_sky;
3593 old_sky = C4S.Landscape.SkyDef;
3594 // do not warn on ignored values in main section
3595 // they are caused because not all parts of scenario core are compiled on section change
3596 bool is_main_section = SEqualNoCase(pLoadSect->name.getData(), C4ScenSect_Main);
3597 // overload scenario values (fails if no scenario core is present; that's OK)
3598 C4S.Load(*pGrp, true, is_main_section);
3599 // determine whether a new sky has to be loaded
3600 bool fLoadNewSky = !SEqualNoCase(old_sky.c_str(), C4S.Landscape.SkyDef.c_str()) || pGrp->FindEntry(C4CFN_Sky ".*");
3601 // set new Objects.c source
3602 Game.pScenarioObjectsScript = pLoadSect->pObjectScripts;
3603 // remove reference to FoW from viewports, so that we can safely
3604 // reload the landscape and its FoW.
3605 Viewports.DisableFoW();
3606 // landscape initialization resets the RNG
3607 // set a new seed here to get new dynamic landscapes
3608 // TODO: add an option to disable this?
3609 RandomSeed = Random(2147483647);
3610 FixRandom(RandomSeed);
3611 // re-init game in new section
3612 C4ValueNumbers numbers;
3613 if (!InitGame(*pGrp, (dwFlags & C4S_REINIT_SCENARIO) ? IM_ReInit : IM_Section, fLoadNewSky, &numbers))
3614 {
3615 DebugLog("LoadScenarioSection: Error reiniting game");
3616 ::Viewports.EnableFoW();
3617 return false;
3618 }
3619 // restore shelved proplists in case loading failed
3620 C4PropListNumbered::UnshelveNumberedPropLists();
3621 // set new current section
3622 pCurrentScenarioSection = pLoadSect;
3623 SCopy(pCurrentScenarioSection->name.getData(), CurrentScenarioSection);
3624 // Final init on game re-init (doing mostly player initialization)
3625 if (dwFlags & C4S_REINIT_SCENARIO)
3626 {
3627 InitGameFinal(IM_ReInit);
3628 // Extra InitializePlayers callback on the already-joined players to start intros, etc.
3629 // (unless the call is still pending - can happen if section is loaded during player join)
3630 if (::Game.InitialPlayersJoined && ::Players.GetCount())
3631 ::Game.GRBroadcast(PSF_InitializePlayers);
3632 }
3633 // resize viewports, and enable lighting again
3634 ::Viewports.RecalculateViewports();
3635 ::Viewports.EnableFoW();
3636 // done, success
3637 return true;
3638 }
3639
ToggleDebugMode()3640 bool C4Game::ToggleDebugMode()
3641 {
3642 // debug mode not allowed
3643 if (!Parameters.AllowDebug && !DebugMode) { GraphicsSystem.FlashMessage(LoadResStr("IDS_MSG_DEBUGMODENOTALLOWED")); return false; }
3644 DebugMode = !DebugMode;
3645 if (!DebugMode) GraphicsSystem.DeactivateDebugOutput();
3646 GraphicsSystem.FlashMessageOnOff(LoadResStr("IDS_CTL_DEBUGMODE"), DebugMode);
3647 return true;
3648 }
3649
ActivateMenu(const char * szCommand)3650 bool C4Game::ActivateMenu(const char *szCommand)
3651 {
3652 // no new menu during round evaluation
3653 if (C4GameOverDlg::IsShown()) return false;
3654 // forward to primary player
3655 C4Player *pPlr=::Players.GetLocalByIndex(0);
3656 if (!pPlr) return false;
3657 pPlr->Menu.ActivateCommand(pPlr->Number, szCommand);
3658 return true;
3659 }
3660
ToggleChart()3661 bool C4Game::ToggleChart()
3662 {
3663 C4ChartDialog::Toggle();
3664 return true;
3665 }
3666
Abort(bool fApproved)3667 void C4Game::Abort(bool fApproved)
3668 {
3669 // league needs approval
3670 if (Network.isEnabled() && Parameters.isLeague() && !fApproved)
3671 {
3672 if (Control.isCtrlHost() && !Game.GameOver)
3673 {
3674 Network.Vote(VT_Cancel);
3675 return;
3676 }
3677 if (!Control.isCtrlHost() && !Game.GameOver && ::Players.GetLocalByIndex(0))
3678 {
3679 Network.Vote(VT_Kick, true, Control.ClientID());
3680 return;
3681 }
3682 }
3683 if (EvaluateOnAbort)
3684 {
3685 // Scenario forces evaluation. This is intended for scenarios that
3686 // always save progress in the players, such as Tower of Despair.
3687 Evaluate();
3688 }
3689 // hard-abort: eval league and quit
3690 // manually evaluate league
3691 Players.RemoveLocal(true, true);
3692 Players.RemoveAtRemoteClient(true, true);
3693 // normal quit
3694 Application.QuitGame();
3695 }
3696
3697 static const std::unordered_map<std::string, C4GUI::Icons> str_to_icon =
3698 {
3699 { "Locked", C4GUI::Ico_Ex_LockedFrontal },
3700 { "League", C4GUI::Ico_Ex_League },
3701 { "GameRunning", C4GUI::Ico_GameRunning },
3702 { "Lobby", C4GUI::Ico_Lobby },
3703 { "RuntimeJoin", C4GUI::Ico_RuntimeJoin },
3704
3705 { "A", C4GUI::Ico_Controller_A },
3706 { "B", C4GUI::Ico_Controller_B },
3707 { "X", C4GUI::Ico_Controller_X },
3708 { "Y", C4GUI::Ico_Controller_Y },
3709 { "Back", C4GUI::Ico_Controller_Back },
3710 { "Start", C4GUI::Ico_Controller_Start },
3711 { "Dpad", C4GUI::Ico_Controller_Dpad },
3712 { "DpadLeft", C4GUI::Ico_Controller_DpadLeft },
3713 { "DpadRight", C4GUI::Ico_Controller_DpadRight },
3714 { "DpadDown", C4GUI::Ico_Controller_DpadDown },
3715 { "DpadUp", C4GUI::Ico_Controller_DpadUp },
3716 { "LeftShoulder", C4GUI::Ico_Controller_LeftShoulder },
3717 { "RightShoulder", C4GUI::Ico_Controller_RightShoulder },
3718 { "LeftTrigger", C4GUI::Ico_Controller_LeftTrigger },
3719 { "RightTrigger", C4GUI::Ico_Controller_RightTrigger },
3720 { "LeftStick", C4GUI::Ico_Controller_LeftStick },
3721 { "RightStick", C4GUI::Ico_Controller_RightStick },
3722 };
3723
GetTextSpecFacet(const char * szSpec,C4Facet & fct)3724 bool GetTextSpecFacet(const char* szSpec, C4Facet& fct)
3725 {
3726 // safety
3727 assert(szSpec && *szSpec);
3728 if (!szSpec) return false;
3729 // Special icon?
3730 if (SEqual2(szSpec, "@Ico:"))
3731 {
3732 szSpec += 5;
3733 auto it = str_to_icon.find(szSpec);
3734 if (it != str_to_icon.end())
3735 {
3736 fct = C4GUI::Icon::GetIconFacet(it->second);
3737 return true;
3738 }
3739 }
3740
3741 return false;
3742 }
3743
DrawTextSpecImage(C4Facet & fctTarget,const char * szSpec,C4DrawTransform * pTransform,uint32_t dwClr)3744 bool C4Game::DrawTextSpecImage(C4Facet &fctTarget, const char *szSpec, C4DrawTransform* pTransform, uint32_t dwClr)
3745 {
3746 // safety
3747 assert(szSpec && *szSpec);
3748 if (!szSpec) return false;
3749
3750 C4Facet fctSource;
3751 if(GetTextSpecFacet(szSpec, fctSource))
3752 {
3753 fctSource.DrawXT(fctTarget.Surface, fctTarget.X, fctTarget.Y, fctTarget.Wdt, fctTarget.Hgt, 0, 0, pTransform);
3754 return true;
3755 }
3756 else
3757 {
3758 C4Def *pDef = C4Id2Def(C4ID(szSpec));
3759 if (!pDef) return false;
3760
3761 pDef->Draw(fctTarget, false, dwClr, nullptr, 0, 0, pTransform);
3762 return true;
3763 }
3764 }
3765
GetTextSpecImageAspect(const char * szSpec)3766 float C4Game::GetTextSpecImageAspect(const char* szSpec)
3767 {
3768 // safety
3769 assert(szSpec && *szSpec);
3770 if (!szSpec) return -1.0f;
3771
3772 C4Facet fctSource;
3773 if(GetTextSpecFacet(szSpec, fctSource))
3774 {
3775 return static_cast<float>(fctSource.Wdt) / static_cast<float>(fctSource.Hgt);
3776 }
3777 else
3778 {
3779 C4Def *pDef = C4Id2Def(C4ID(szSpec));
3780 if (!pDef) return -1.0f;
3781
3782 C4DefGraphics* pGfx = &pDef->Graphics;
3783 if(pGfx->Type == C4DefGraphics::TYPE_Bitmap)
3784 {
3785 return static_cast<float>(pDef->PictureRect.Wdt) / static_cast<float>(pDef->PictureRect.Hgt);
3786 }
3787 else if (pGfx->Type == C4DefGraphics::TYPE_Mesh)
3788 {
3789 const StdMesh& mesh = *pGfx->Mesh;
3790 const StdMeshBox& box = mesh.GetBoundingBox();
3791 return (box.x2 - box.x1) / (box.y2 - box.y1);
3792 }
3793
3794 return -1.0f;
3795 }
3796 }
3797
DrawPropListSpecImage(C4Facet & fctTarget,C4PropList * pSpec)3798 bool C4Game::DrawPropListSpecImage(C4Facet &fctTarget, C4PropList *pSpec)
3799 {
3800 // safety
3801 assert(pSpec);
3802 if (!pSpec) return false;
3803
3804 // get source definition
3805 C4PropList *source_def_proplist = pSpec->GetPropertyPropList(P_Source);
3806 if (!source_def_proplist) return false;
3807 C4Def *source_def = source_def_proplist->GetDef();
3808 if (!source_def) return false;
3809
3810 // get custom color
3811 uint32_t color = (uint32_t)pSpec->GetPropertyInt(P_Color);
3812
3813 C4String *source_name = pSpec->GetPropertyStr(P_Name);
3814 if (!source_name)
3815 {
3816 // Base graphics
3817 source_def->Draw(fctTarget, false, color);
3818 }
3819 else
3820 {
3821 // Alternative named graphics
3822 C4DefGraphics *source_graphics = source_def->Graphics.Get(source_name->GetCStr());
3823 if (!source_graphics) return false;
3824 source_graphics->Draw(fctTarget, color, nullptr, 0,0, nullptr);
3825 }
3826 return true;
3827 }
3828
SpeedUp()3829 bool C4Game::SpeedUp()
3830 {
3831 // As these functions work stepwise, there's the old maximum speed of 50.
3832 // Use /fast to set to even higher speeds.
3833 FrameSkip = Clamp<int32_t>(FrameSkip + 1, 1, 50);
3834 FullSpeed = true;
3835 GraphicsSystem.FlashMessage(FormatString(LoadResStr("IDS_MSG_SPEED"), FrameSkip).getData());
3836 return true;
3837 }
3838
SlowDown()3839 bool C4Game::SlowDown()
3840 {
3841 FrameSkip = Clamp<int32_t>(FrameSkip - 1, 1, 50);
3842 if (FrameSkip == 1)
3843 FullSpeed = false;
3844 GraphicsSystem.FlashMessage(FormatString(LoadResStr("IDS_MSG_SPEED"), FrameSkip).getData());
3845 return true;
3846 }
3847
ToggleChat()3848 bool C4Game::ToggleChat()
3849 {
3850 return C4ChatDlg::ToggleChat();
3851 }
3852
GRBroadcast(const char * szFunction,C4AulParSet * pPars,bool fPassError,bool fRejectTest)3853 C4Value C4Game::GRBroadcast(const char *szFunction, C4AulParSet *pPars, bool fPassError, bool fRejectTest)
3854 {
3855 std::string func{ szFunction };
3856 if (func[0] != '~')
3857 func.insert(0, 1, '~');
3858
3859 // call objects first - scenario script might overwrite hostility, etc...
3860 C4Value vResult = ::Objects.GRBroadcast(func.c_str(), pPars, fPassError, fRejectTest);
3861 // rejection tests abort on first nonzero result
3862 if (fRejectTest) if (!!vResult) return vResult;
3863 // scenario script call
3864 return ::GameScript.Call(func.c_str(), pPars, fPassError);
3865 }
3866
SetDefaultGamma()3867 void C4Game::SetDefaultGamma()
3868 {
3869 // Skip this if graphics haven't been initialized yet (happens when
3870 // we bail during initialization)
3871 if (!pDraw) return;
3872 // Default gamma
3873 pDraw->ResetGamma();
3874 pDraw->SetGamma(float(Config.Graphics.Gamma) / 100.0,
3875 float(Config.Graphics.Gamma) / 100.0,
3876 float(Config.Graphics.Gamma) / 100.0,
3877 C4MaxGammaUserRamps);
3878 }
3879
SetGlobalSoundModifier(C4PropList * new_modifier)3880 void C4Game::SetGlobalSoundModifier(C4PropList *new_modifier)
3881 {
3882 // set in prop list (for savegames) and in sound system::
3883 C4SoundModifier *mod;
3884 if (new_modifier)
3885 {
3886 GlobalSoundModifier.SetPropList(new_modifier);
3887 mod = ::Application.SoundSystem.Modifiers.Get(new_modifier, true);
3888 }
3889 else
3890 {
3891 GlobalSoundModifier.Set0();
3892 mod = nullptr;
3893 }
3894 ::Application.SoundSystem.Modifiers.SetGlobalModifier(mod, NO_OWNER);
3895 }
3896
GetTranslatedString(const C4Value & input_string,C4Value * selected_language,bool fail_silently) const3897 C4String *C4Game::GetTranslatedString(const C4Value &input_string, C4Value *selected_language, bool fail_silently) const
3898 {
3899 // Resolve a localized string
3900 // If a string is passed, just return it
3901 // If a proplist like { DE="Hallo, Welt!", US="Hello, world!" } is passed, return the string matching the selected language
3902 // Nothing?
3903 if (input_string.GetType() == C4V_Nil)
3904 {
3905 return nullptr;
3906 }
3907 // Non-localized string?
3908 if (input_string.GetType() == C4V_String)
3909 {
3910 return input_string._getStr();
3911 }
3912 // Invalid type for this function?
3913 C4PropList *p = input_string._getPropList();
3914 if (!p || p->GetPropertyStr(P_Function) != &::Strings.P[P_Translate])
3915 {
3916 if (fail_silently)
3917 {
3918 return nullptr;
3919 }
3920 else
3921 {
3922 throw C4AulExecError(FormatString("Invalid value for translation: %s", input_string.GetDataString().getData()).getData());
3923 }
3924 }
3925 // This is a proplist. Resolve the language as the key.
3926 char lang_code[3] = "";
3927 for (int32_t lang_index = 0; SCopySegment(Config.General.LanguageEx, lang_index, lang_code, ',', 2); ++lang_index)
3928 {
3929 C4String *lang_string = ::Strings.FindString(lang_code);
3930 if (lang_string) // If the string is not found, it cannot be the key in a prop list
3931 {
3932 C4Value localized_string_val;
3933 if (p->GetPropertyByS(lang_string, &localized_string_val))
3934 {
3935 C4String *localized_string = localized_string_val.getStr();
3936 if (localized_string)
3937 {
3938 // Found it!
3939 if (selected_language)
3940 {
3941 selected_language->SetString(lang_string);
3942 }
3943 return localized_string;
3944 }
3945 }
3946 }
3947 }
3948 // No language matched. Just use any property and assume it's a language key.
3949 for (C4String *lang_string : p->GetSortedLocalProperties(false))
3950 {
3951 C4Value localized_string_val;
3952 if (p->GetPropertyByS(lang_string, &localized_string_val))
3953 {
3954 C4String *localized_string = localized_string_val.getStr();
3955 if (localized_string)
3956 {
3957 // Found it!
3958 if (selected_language)
3959 {
3960 selected_language->SetString(lang_string);
3961 }
3962 return localized_string;
3963 }
3964 }
3965 }
3966 // No string properties. There's no localized information to be found.
3967 return nullptr;
3968 }
3969
AllocateTranslatedString()3970 C4PropList *C4Game::AllocateTranslatedString()
3971 {
3972 C4PropListScript *value_proplist = new C4PropListScript();
3973 value_proplist->SetProperty(P_Function, C4VString(&::Strings.P[P_Translate]));
3974 return value_proplist;
3975 }
3976