Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
79 lines
6.7 KiB
Diff
79 lines
6.7 KiB
Diff
--- a/addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainTakeover.c
|
|
+++ b/addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainTakeover.c
|
|
@@ -173,7 +173,7 @@
|
|
protected override void OnWorldPostProcess(World world)
|
|
{
|
|
// Do not opt an existing authored Escape mission into replacement.
|
|
- if (ERR_TerrainTakeoverSettings.Enabled() && !ERR_GameMode.Get() && Replication.IsServer())
|
|
+ if (ERR_TerrainTakeoverSettings.Enabled() && !ERR_GameMode.Get() && Replication.IsServer() && GetGameMode())
|
|
{
|
|
ERR_ResetTerrainTakeover();
|
|
m_ERRTerrainWorld = world;
|
|
--- a/tests/test_terrain_takeover.py
|
|
+++ b/tests/test_terrain_takeover.py
|
|
@@ -318,3 +318,64 @@
|
|
}
|
|
''')
|
|
print('PASS: standalone old faction delegates collected once, with null/deleted owners ignored')
|
|
+
|
|
+
|
|
+# Main-menu world regression: actual startup body and enable policy.
|
|
+"""Actual world-postprocess and enable policy: menu worlds never enter terrain preflight."""
|
|
+from policy_harness import ROOT,body,run
|
|
+from pathlib import Path
|
|
+import os
|
|
+P=Path(os.environ.get('ERR_TAKEOVER_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainTakeover.c'))
|
|
+S=Path(os.environ.get('ERR_TAKEOVER_SETTINGS_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainTakeoverSettings.c'))
|
|
+def cpp(s):
|
|
+ for a,b in [('protected ',''),('override ',''),('super.','Base::'),('World world','World* world'),('SaveGameManager saves','SaveGameManager* saves'),('SCR_MissionHeader header','SCR_MissionHeader* header'),('GetGame().','GetGame()->'),('header.','header->'),('saves.','saves->'),('null','nullptr')]:s=s.replace(a,b)
|
|
+ for name in ['ERR_TerrainTakeoverSettings','ERR_GameMode','Replication','SCR_BaseGameMode','SCR_MissionHeader','SCR_Global','System','ERR_TerrainTakeoverStage']:s=s.replace(name+'.',name+'::')
|
|
+ return s
|
|
+run(r'''
|
|
+#include <cassert>
|
|
+#include <string>
|
|
+#include <map>
|
|
+#include <vector>
|
|
+using string=std::string;
|
|
+struct World{};
|
|
+struct Mode {virtual ~Mode()=default;};
|
|
+struct SCR_BaseGameMode:Mode {static SCR_BaseGameMode*Cast(Mode*m){return dynamic_cast<SCR_BaseGameMode*>(m);}};
|
|
+struct ERR_GameMode:SCR_BaseGameMode{inline static ERR_GameMode*current=nullptr;static ERR_GameMode*Get(){return current;}};
|
|
+struct SCR_MissionHeader{bool m_bERR_EnableEscape=true;static SCR_MissionHeader*Cast(SCR_MissionHeader*h){return h;}};
|
|
+struct Game{SCR_MissionHeader*header=nullptr;SCR_MissionHeader*GetMissionHeader(){return header;}}game;
|
|
+Game*currentGame=&game;Game*GetGame(){return currentGame;}
|
|
+namespace SCR_Global{bool edit=false;bool IsEditMode(){return edit;}}
|
|
+namespace System{std::map<string,string>cli;bool GetCLIParam(string key,string&value){auto i=cli.find(key);if(i==cli.end())return false;value=i->second;return true;}}
|
|
+namespace Replication{bool server=true;bool IsServer(){return server;}}
|
|
+struct ERR_TerrainTakeoverSettings{
|
|
+'''+cpp(body(S,'static bool Enabled('))+r'''
|
|
+};
|
|
+namespace ERR_TerrainTakeoverStage{enum Stage{INACTIVE,SETTLING,FAILED};}
|
|
+struct SaveGameManager{int writes=0;bool allowed=true;void SetSavingAllowed(bool b){writes++;allowed=b;}};
|
|
+struct Array{int Count(){return 0;}};
|
|
+struct Base{int superCalls=0;World*superWorld=nullptr;void OnWorldPostProcess(World*w){superCalls++;superWorld=w;}};
|
|
+struct App:Base{
|
|
+ Mode*mode=nullptr;World*m_ERRTerrainWorld=nullptr;SCR_BaseGameMode*m_ERRTerrainOriginal=nullptr;ERR_TerrainTakeoverStage::Stage m_ERRTerrainStage=ERR_TerrainTakeoverStage::INACTIVE;Array m_ERRTerrainAirfields;SaveGameManager saves;int resets=0,preflights=0,failures=0,prints=0;bool nativePrerequisites=true;
|
|
+ Mode*GetGameMode(){return mode;}SaveGameManager*GetSaveGameManager(){return &saves;}
|
|
+ void ERR_ResetTerrainTakeover(){resets++;m_ERRTerrainStage=ERR_TerrainTakeoverStage::INACTIVE;m_ERRTerrainWorld=nullptr;m_ERRTerrainOriginal=nullptr;}
|
|
+ bool ERR_PreflightTerrainTakeover(){preflights++;if(!m_ERRTerrainOriginal||!nativePrerequisites){failures++;m_ERRTerrainStage=ERR_TerrainTakeoverStage::FAILED;return false;}return true;}
|
|
+ void PrintFormat(const char*,int){prints++;}
|
|
+'''+cpp(body(P,'protected override void OnWorldPostProcess('))+r'''
|
|
+};
|
|
+int main(){
|
|
+ World menu,world;SCR_BaseGameMode native;ERR_GameMode own;Mode unsupported;
|
|
+ // Actual Enabled defaults true without a header. A menu has no mode, not an unsupported scenario.
|
|
+ {App a;assert(ERR_TerrainTakeoverSettings::Enabled());a.OnWorldPostProcess(&menu);assert(a.superCalls==1&&a.superWorld==&menu);assert(a.resets==0&&a.preflights==0&&a.failures==0&&a.saves.writes==0&&!a.m_ERRTerrainWorld);}
|
|
+ // A mode-less callback must not erase an already retained operation either.
|
|
+ {App a;a.m_ERRTerrainStage=ERR_TerrainTakeoverStage::SETTLING;a.m_ERRTerrainWorld=&world;a.m_ERRTerrainOriginal=&native;a.OnWorldPostProcess(&menu);assert(a.superCalls==1&&a.resets==0&&a.preflights==0&&a.m_ERRTerrainStage==ERR_TerrainTakeoverStage::SETTLING&&a.m_ERRTerrainWorld==&world&&a.m_ERRTerrainOriginal==&native);}
|
|
+ {App a;a.mode=&native;a.OnWorldPostProcess(&world);assert(a.superCalls==1&&a.resets==1&&a.preflights==1&&!a.failures&&a.m_ERRTerrainOriginal==&native&&a.m_ERRTerrainWorld==&world&&a.m_ERRTerrainStage==ERR_TerrainTakeoverStage::SETTLING&&a.saves.writes==1&&!a.saves.allowed);}
|
|
+ // Actual unsupported game modes still reach the diagnostic, preserving original scenario and saving.
|
|
+ {App a;a.mode=&unsupported;a.OnWorldPostProcess(&world);assert(a.superCalls==1&&a.resets==1&&a.preflights==1&&a.failures==1&&a.m_ERRTerrainStage==ERR_TerrainTakeoverStage::FAILED&&a.saves.writes==0&&a.saves.allowed);}
|
|
+ {App a;a.mode=&native;a.nativePrerequisites=false;a.OnWorldPostProcess(&world);assert(a.superCalls==1&&a.failures==1&&a.saves.writes==0);}
|
|
+ // Authored successor, client, editor and explicit disabled configurations keep original behavior.
|
|
+ for(int i=0;i<6;i++){App a;a.mode=&native;SCR_MissionHeader header;game.header=&header;if(i==0){ERR_GameMode::current=&own;a.mode=&own;}if(i==1)Replication::server=false;if(i==2)SCR_Global::edit=true;if(i==3)header.m_bERR_EnableEscape=false;if(i==4)System::cli["errTerrainTakeover"]="0";if(i==5)currentGame=nullptr;a.OnWorldPostProcess(&world);assert(a.superCalls==1&&a.resets==0&&a.preflights==0&&a.saves.writes==0);ERR_GameMode::current=nullptr;Replication::server=true;SCR_Global::edit=false;System::cli.clear();game.header=nullptr;currentGame=&game;}
|
|
+ // Explicit enabling never creates a mode for a menu; same app can later enter an ordinary scenario.
|
|
+ {System::cli["errTerrainTakeover"]="1";App a;a.OnWorldPostProcess(&menu);assert(!a.resets);a.mode=&native;a.OnWorldPostProcess(&world);assert(a.superCalls==2&&a.resets==1&&a.preflights==1&&a.saves.writes==1);System::cli.clear();}
|
|
+}
|
|
+''')
|
|
+print('PASS: actual terrain takeover startup skips mode-less/menu worlds, always forwards super, retains eligible/unsupported/authored/client/editor semantics')
|