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