diff --git a/src/game/C4Game.cpp b/src/game/C4Game.cpp index a9b35ef3f..561bba8ae 100644 --- a/src/game/C4Game.cpp +++ b/src/game/C4Game.cpp @@ -458,7 +458,7 @@ bool C4Game::Init() DebugMode = false; // Init game - if (!InitGame(ScenarioFile, false, true, &numbers)) return false; + if (!InitGame(ScenarioFile, IM_Normal, true, &numbers)) return false; // Network final init if (Network.isEnabled()) @@ -480,7 +480,7 @@ bool C4Game::Init() SetInitProgress(98); // Final init - if (!InitGameFinal()) return false; + if (!InitGameFinal(IM_Normal)) return false; SetInitProgress(99); // Sound modifier from savegames @@ -1651,7 +1651,7 @@ void C4Game::Ticks() void C4Game::CompileFunc(StdCompiler *pComp, CompileSettings comp, C4ValueNumbers * numbers) { - if (!comp.fScenarioSection && comp.fExact) + if (comp.init_mode == IM_Normal && comp.fExact) { pComp->Name("Game"); pComp->Value(mkNamingAdapt(Time, "Time", 0)); @@ -1680,8 +1680,6 @@ void C4Game::CompileFunc(StdCompiler *pComp, CompileSettings comp, C4ValueNumber pComp->Value(mkNamingAdapt(NextMissionDesc, "NextMissionDesc", StdCopyStrBuf())); pComp->NameEnd(); - - // Music settings pComp->Value(mkNamingAdapt(::Application.MusicSystem, "Music")); @@ -1698,7 +1696,7 @@ void C4Game::CompileFunc(StdCompiler *pComp, CompileSettings comp, C4ValueNumber pComp->Value(mkNamingAdapt(Landscape.GetSky(), "Sky")); // save custom GUIs only if a real savegame and not for editor-scenario-saves or section changes - if (!comp.fScenarioSection) + if (comp.init_mode == IM_Normal) { pComp->Name("GUI"); if (pComp->isCompiler()) @@ -1737,18 +1735,18 @@ void C4Game::CompileFunc(StdCompiler *pComp, CompileSettings comp, C4ValueNumber // Section load: Clear existing prop list numbering to make room for the new objects // Numbers will be re-acquired in C4GameObjects::PostLoad - if (comp.fScenarioSection) C4PropListNumbered::ShelveNumberedPropLists(); + if (comp.init_mode == IM_Section) C4PropListNumbered::ShelveNumberedPropLists(); pComp->Value(mkParAdapt(Objects, !comp.fExact, numbers)); - pComp->Value(mkNamingAdapt(mkParAdapt(ScriptEngine, comp.fScenarioSection, numbers), "Script")); + pComp->Value(mkNamingAdapt(mkParAdapt(ScriptEngine, comp.init_mode == IM_Section, numbers), "Script")); } -bool C4Game::CompileRuntimeData(C4Group &hGroup, bool fLoadSection, bool exact, bool sync, C4ValueNumbers * numbers) +bool C4Game::CompileRuntimeData(C4Group &hGroup, InitMode init_mode, bool exact, bool sync, C4ValueNumbers * numbers) { - ::Objects.Clear(!fLoadSection); + ::Objects.Clear(init_mode != IM_Section); GameText.Load(hGroup,C4CFN_Game); - CompileSettings Settings(fLoadSection, false, exact, sync); + CompileSettings Settings(init_mode, false, exact, sync); // C4Game is not defaulted on compilation. // Loading of runtime data overrides only certain values. // Doesn't compile players; those will be done later @@ -1772,7 +1770,7 @@ bool C4Game::SaveData(C4Group &hGroup, bool fSaveSection, bool fSaveExact, bool { StdStrBuf Buf; // Decompile (without players for scenario sections) - DecompileToBuf_Log(mkParAdapt(*this, CompileSettings(fSaveSection, !fSaveSection && fSaveExact, fSaveExact, fSaveSync), numbers), &Buf, "Game"); + DecompileToBuf_Log(mkParAdapt(*this, CompileSettings(fSaveSection ? IM_Section : IM_Normal, !fSaveSection && fSaveExact, fSaveExact, fSaveSync), numbers), &Buf, "Game"); // Empty? All default save a Game.txt anyway because it is used to signal the engine to not load Objects.c if (!Buf.getLength()) Buf.Copy(" "); @@ -2091,7 +2089,7 @@ bool C4Game::ReloadParticle(const char *szName) return true; } -bool C4Game::InitGame(C4Group &hGroup, bool fLoadSection, bool fLoadSky, C4ValueNumbers * numbers) +bool C4Game::InitGame(C4Group &hGroup, InitMode init_mode, bool fLoadSky, C4ValueNumbers * numbers) { // Activate debugger if requested // needs to happen before any scripts are compiled to bytecode so AB_DEBUG chunks will be inserted @@ -2104,7 +2102,7 @@ bool C4Game::InitGame(C4Group &hGroup, bool fLoadSection, bool fLoadSky, C4Value return false; } - if (!fLoadSection) + if (init_mode == IM_Normal) { // file monitor @@ -2197,25 +2195,25 @@ bool C4Game::InitGame(C4Group &hGroup, bool fLoadSection, bool fLoadSky, C4Value } // The Landscape is the last long chunk of loading time, so it's a good place to start the music fadeout - if (!fLoadSection) Application.MusicSystem.FadeOut(2000); + if (init_mode == IM_Normal) Application.MusicSystem.FadeOut(2000); // Landscape Log(LoadResStr("IDS_PRC_LANDSCAPE")); bool fLandscapeLoaded = false; - if (!Landscape.Init(hGroup, fLoadSection, fLoadSky, fLandscapeLoaded, !!C4S.Head.SaveGame)) + if (!Landscape.Init(hGroup, init_mode != IM_Normal, fLoadSky, fLandscapeLoaded, !!C4S.Head.SaveGame)) { LogFatal(LoadResStr("IDS_ERR_GBACK")); return false; } SetInitProgress(88); // the savegame flag is set if runtime data is present, in which case this is to be used // except for scenario sections - if (fLandscapeLoaded && (!C4S.Head.SaveGame || fLoadSection)) + if (fLandscapeLoaded && (!C4S.Head.SaveGame || init_mode == IM_Section)) Landscape.ScenarioInit(); // clear old landscape data - if (fLoadSection && fLandscapeLoaded) { PXS.Clear(); MassMover.Clear(); } + if (init_mode != IM_Normal && fLandscapeLoaded) { PXS.Clear(); MassMover.Clear(); } SetInitProgress(89); // Init main object list Objects.Init(Landscape.GetWidth(), Landscape.GetHeight()); // Pathfinder - if (!fLoadSection) PathFinder.Init( &LandscapeFree, &TransferZones ); + if (init_mode == IM_Normal) PathFinder.Init( &LandscapeFree, &TransferZones ); SetInitProgress(90); // PXS @@ -2231,16 +2229,17 @@ bool C4Game::InitGame(C4Group &hGroup, bool fLoadSection, bool fLoadSky, C4Value SetInitProgress(92); // definition value overloads - if (!fLoadSection) InitValueOverloads(); + // TODO: Remove this function? We could move value to script and allow it through regular overloads + if (init_mode == IM_Normal) InitValueOverloads(); // runtime data - if (!CompileRuntimeData(hGroup, fLoadSection, C4S.Head.SaveGame, C4S.Head.NetworkGame, numbers)) + if (!CompileRuntimeData(hGroup, init_mode, C4S.Head.SaveGame, C4S.Head.NetworkGame, numbers)) { LogFatal(LoadResStr("IDS_PRC_FAIL")); return false; } SetInitProgress(93); // Load round results - if (!fLoadSection) + if (init_mode == IM_Normal) { if (hGroup.FindEntry(C4CFN_RoundResults)) { @@ -2254,13 +2253,13 @@ bool C4Game::InitGame(C4Group &hGroup, bool fLoadSection, bool fLoadSky, C4Value } // Denumerate game data pointers - if (!fLoadSection) ScriptEngine.Denumerate(numbers); - if (!fLoadSection) GlobalSoundModifier.Denumerate(numbers); + if (init_mode == IM_Normal) ScriptEngine.Denumerate(numbers); + if (init_mode == IM_Normal) GlobalSoundModifier.Denumerate(numbers); numbers->Denumerate(); - if (!fLoadSection) ScriptGuiRoot->Denumerate(numbers); + if (init_mode == IM_Normal) ScriptGuiRoot->Denumerate(numbers); // Object.PostLoad must happen after number->Denumerate(), becuase UpdateFace() will access Action proplist, // which might have a non-denumerated prototype otherwise - Objects.PostLoad(fLoadSection, numbers); + Objects.PostLoad(init_mode == IM_Section, numbers); // Check object enumeration if (!CheckObjectEnumeration()) return false; @@ -2293,7 +2292,7 @@ bool C4Game::InitGame(C4Group &hGroup, bool fLoadSection, bool fLoadSky, C4Value // close any gfx groups, because they are no longer needed (after sky is initialized) GraphicsResource.CloseFiles(); - if (!fLoadSection) + if (init_mode == IM_Normal) // reload doesn't affect the music (takes too long) { // Music ::Application.MusicSystem.InitForScenario(ScenarioFile); @@ -2311,9 +2310,8 @@ bool C4Game::InitGame(C4Group &hGroup, bool fLoadSection, bool fLoadSky, C4Value return true; } -bool C4Game::InitGameFinal() +bool C4Game::InitGameFinal(InitMode init_mode) { - // Validate object owners & assign loaded info objects Objects.ValidateOwners(); Objects.AssignInfo(); @@ -2329,26 +2327,32 @@ bool C4Game::InitGameFinal() // Player final init C4Player *pPlr; - for (pPlr=Players.First; pPlr; pPlr=pPlr->Next) + for (pPlr = Players.First; pPlr; pPlr = pPlr->Next) + { + if (init_mode == IM_ReInit) pPlr->ScenarioInit(); pPlr->FinalInit(!C4S.Head.SaveGame); + } // Create viewports - for (pPlr=Players.First; pPlr; pPlr=pPlr->Next) - if (pPlr->LocalControl) - ::Viewports.CreateViewport(pPlr->Number); - // Check fullscreen viewports - FullScreen.ViewportCheck(); - // update halt state - Console.UpdateHaltCtrls(!!HaltCount); + if (init_mode == IM_Normal) + { + for (pPlr = Players.First; pPlr; pPlr = pPlr->Next) + if (pPlr->LocalControl) + ::Viewports.CreateViewport(pPlr->Number); + // Check fullscreen viewports + FullScreen.ViewportCheck(); + // update halt state + Console.UpdateHaltCtrls(!!HaltCount); - // Host: players without connected clients: remove via control queue - if (Network.isEnabled() && Network.isHost()) - for (int32_t cnt = 0; cnt < Players.GetCount(); cnt++) - if (Players.GetByIndex(cnt)->AtClient < 0) - Players.Remove(Players.GetByIndex(cnt), true, false); + // Host: players without connected clients: remove via control queue + if (Network.isEnabled() && Network.isHost()) + for (int32_t cnt = 0; cnt < Players.GetCount(); cnt++) + if (Players.GetByIndex(cnt)->AtClient < 0) + Players.Remove(Players.GetByIndex(cnt), true, false); - // It should be safe now to reload stuff - if (pFileMonitor) pFileMonitor->StartMonitoring(); + // It should be safe now to reload stuff + if (pFileMonitor) pFileMonitor->StartMonitoring(); + } return true; } @@ -3368,10 +3372,14 @@ void C4Game::SetInitProgress(float fToProgress) GraphicsSystem.MessageBoard->LogNotify(); } // Cheap hack to get the Console window updated while loading - Application.FlushMessages(); + // (unless game is running, i.e. section change - section change would be quick and timer execution can mess with things unpredictably) + if (!IsRunning) + { + Application.FlushMessages(); #ifdef WITH_QT_EDITOR - Application.ProcessQtEvents(); + Application.ProcessQtEvents(); #endif + } } void C4Game::OnResolutionChanged(unsigned int iXRes, unsigned int iYRes) @@ -3490,7 +3498,7 @@ bool C4Game::LoadScenarioSection(const char *szSection, DWORD dwFlags) DebugLog("LoadScenarioSection: error opening group file"); return false; } - // remove all objects (except inactive) + // remove all objects // do correct removal calls, because this will stop fire sounds, etc. for (C4Object *obj : Objects) obj->AssignRemoval(); @@ -3500,7 +3508,9 @@ bool C4Game::LoadScenarioSection(const char *szSection, DWORD dwFlags) DebugLogF("LoadScenarioSection: WARNING: Object %d created in destruction process!", (int) obj->Number); ClearPointers(obj); } - DeleteObjects(false); + // Final removal in case objects got recreated + // Also kill inactive objects if scenario is reinitialized + DeleteObjects(!!(dwFlags & C4S_REINIT_SCENARIO)); // remove global effects if (::ScriptEngine.pGlobalEffects && !(dwFlags & C4S_KEEP_EFFECTS)) { @@ -3536,7 +3546,7 @@ bool C4Game::LoadScenarioSection(const char *szSection, DWORD dwFlags) FixRandom(RandomSeed); // re-init game in new section C4ValueNumbers numbers; - if (!InitGame(*pGrp, true, fLoadNewSky, &numbers)) + if (!InitGame(*pGrp, (dwFlags & C4S_REINIT_SCENARIO) ? IM_ReInit : IM_Section, fLoadNewSky, &numbers)) { DebugLog("LoadScenarioSection: Error reiniting game"); ::Viewports.EnableFoW(); @@ -3547,6 +3557,15 @@ bool C4Game::LoadScenarioSection(const char *szSection, DWORD dwFlags) // set new current section pCurrentScenarioSection = pLoadSect; SCopy(pCurrentScenarioSection->szName, CurrentScenarioSection); + // Final init on game re-init (doing mostly player initialization) + if (dwFlags & C4S_REINIT_SCENARIO) + { + InitGameFinal(IM_ReInit); + // Extra InitializePlayers on the already-joined players to start intros, etc. + // (unless the call is still pending - can happen if section is loaded during player join) + if (::Game.InitialPlayersJoined && ::Players.GetCount()) + ::Game.GRBroadcast(PSF_InitializePlayers); + } // resize viewports, and enable lighting again ::Viewports.RecalculateViewports(); ::Viewports.EnableFoW(); diff --git a/src/game/C4Game.h b/src/game/C4Game.h index 477b2bd7e..28c601df5 100644 --- a/src/game/C4Game.h +++ b/src/game/C4Game.h @@ -33,17 +33,25 @@ class C4ScriptGuiWindow; class C4Game { +public: + // Initialization mode: Regular game start, section load or editor reload + enum InitMode + { + IM_Normal = 0, + IM_Section = 1, + IM_ReInit = 2 + }; private: // used as StdCompiler-parameter struct CompileSettings { - bool fScenarioSection; + InitMode init_mode; bool fPlayers; bool fExact; bool fSync; - CompileSettings(bool fScenarioSection, bool fPlayers, bool fExact, bool fSync) - : fScenarioSection(fScenarioSection), fPlayers(fPlayers), fExact(fExact), fSync(fSync) { } + CompileSettings(InitMode init_mode, bool fPlayers, bool fExact, bool fSync) + : init_mode(init_mode), fPlayers(fPlayers), fExact(fExact), fSync(fSync) { } }; // struct of keyboard set and indexed control key @@ -242,8 +250,8 @@ public: bool LoadAdditionalSystemGroup(class C4Group &parent_group); bool SaveGameTitle(C4Group &hGroup); protected: - bool InitGame(C4Group &hGroup, bool fLoadSection, bool fLoadSky, C4ValueNumbers *); - bool InitGameFinal(); + bool InitGame(C4Group &hGroup, InitMode init_mode, bool fLoadSky, C4ValueNumbers *); + bool InitGameFinal(InitMode init_mode); bool InitNetworkFromAddress(const char *szAddress); bool InitNetworkFromReferenceFile(const char *temp_filename); bool InitNetworkFromReference(const C4Network2Reference &Reference); @@ -262,7 +270,7 @@ public: void CompileFunc(StdCompiler *pComp, CompileSettings comp, C4ValueNumbers *); bool SaveData(C4Group &hGroup, bool fSaveSection, bool fSaveExact, bool fSaveSync, C4ValueNumbers *); protected: - bool CompileRuntimeData(C4Group &hGroup, bool fLoadSection, bool exact, bool sync, C4ValueNumbers *); + bool CompileRuntimeData(C4Group &hGroup, InitMode init_mode, bool exact, bool sync, C4ValueNumbers *); // Object function internals C4Object *NewObject( C4PropList *ndef, C4Object *pCreator, diff --git a/src/game/C4GameScript.cpp b/src/game/C4GameScript.cpp index cd24e46fc..3af3ca678 100644 --- a/src/game/C4GameScript.cpp +++ b/src/game/C4GameScript.cpp @@ -2950,6 +2950,7 @@ C4ScriptConstDef C4ScriptGameConstMap[]= { "C4SECT_SaveLandscape" ,C4V_Int, C4S_SAVE_LANDSCAPE }, { "C4SECT_SaveObjects" ,C4V_Int, C4S_SAVE_OBJECTS }, { "C4SECT_KeepEffects" ,C4V_Int, C4S_KEEP_EFFECTS }, + { "C4SECT_ReinitScenario" ,C4V_Int, C4S_REINIT_SCENARIO }, { "TEAMID_New" ,C4V_Int, TEAMID_New }, diff --git a/src/landscape/C4Scenario.h b/src/landscape/C4Scenario.h index 2a19c38e7..052323bb9 100644 --- a/src/landscape/C4Scenario.h +++ b/src/landscape/C4Scenario.h @@ -48,6 +48,7 @@ public: #define C4S_SAVE_LANDSCAPE 1 #define C4S_SAVE_OBJECTS 2 #define C4S_KEEP_EFFECTS 4 +#define C4S_REINIT_SCENARIO 8 enum C4SFilmMode { diff --git a/src/script/C4Aul.cpp b/src/script/C4Aul.cpp index 0e0288513..056a036ff 100644 --- a/src/script/C4Aul.cpp +++ b/src/script/C4Aul.cpp @@ -149,9 +149,9 @@ void C4AulScriptEngine::CompileFunc(StdCompiler *pComp, bool fScenarioSection, C pComp->Value(mkNamingAdapt(mkParAdapt(GlobalNamed, numbers), "StaticVariables", GlobalNamedDefault)); pComp->Value(mkNamingAdapt(mkParAdapt(*GameScript.ScenPropList._getPropList(), numbers), "Scenario")); } - if (fScenarioSection && pComp->isCompiler()) + if (pComp->isCompiler() && pGlobalEffects) { - // loading scenario section: Merge effects + // loading scenario section or game re-init: Merge effects // Must keep old effects here even if they're dead, because the LoadScenarioSection call typically came from execution of a global effect // and otherwise dead pointers would remain on the stack GlobalEffectsMergeCompileFunc(pComp, pGlobalEffects, "Effects", this, numbers);