Files
EscapistsReintroduced/tests/test_terrain_presentation.py
T

197 lines
14 KiB
Python

"""Execute the native name callback and reversible runtime header presentation."""
from policy_harness import ROOT, body, run
p = ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainTakeoverPresentation.c'
s = '\n'.join(body(p, signature) for signature in [
'static string Path(', 'string ERR_TerrainMissionTitle(', 'override string GetMissionName(',
'void ERR_PresentTerrainTakeover(', 'void ERR_RefreshTerrainPresentation(', 'string ERR_TerrainLoadingTitle(', 'override void OnUpdate(', 'protected void ERR_RestoreTerrainPresentation(',
'override protected void OnBeforeWorldCleanup('])
for a, b in [('override ', ''), ('protected ', ''),
('SCR_MissionHeader header', 'SCR_MissionHeader* header'), ('SCR_MissionHeader requested','SCR_MissionHeader* requested'), ('BaseWorld world','World* world'), ('requested.','requested->'),
('SCR_MissionHeader.Cast', 'SCR_MissionHeader::Cast'),
('ResourceName ', 'string '), ('GetWorld().', 'GetWorld()->'), ('ERR_TerrainDisplayName.Resolve', 'Resolve'), ('ERR_TerrainDisplayName.Path', 'Path'), ('header.', 'header->'), ('m_ERRPresentedTerrainHeader.', 'm_ERRPresentedTerrainHeader->'),
('ERR_TerrainTakeoverStage.', 'Stage::'), ('ERR_TerrainTakeoverSettings.Enabled', 'Enabled'),
('SCR_Global.IsEditMode', 'EditMode'), ('Replication.IsServer', 'Server'), ('super.', 'Base::'), ('null', 'nullptr')]:
s = s.replace(a, b)
run(r'''
#include <cassert>
#include <string>
struct string:std::string {using std::string::string;string(std::string v):std::string(v){}bool IsEmpty(){return empty();}int Length(){return size();}string Substring(int a,int n){return substr(a,n);}string GetPath(){assert(false && "native resource lookup in title hot path");return *this;}};
int resolutions=0;string Resolve(string world){resolutions++;return world;}
namespace Stage {enum {INACTIVE,RUNNING,FAILED};}
bool enabled=true,editor=false,server=true;
bool Server(){return server;}
bool Enabled(){return enabled&&!editor;}bool EditMode(){return editor;}
template<class... T>void PrintFormat(T...){}
struct SCR_MissionHeader {
string m_sName="Nogova — Exploration Alpha",m_sGameMode="Terrain exploration";
string world="Nogova";string GetWorldResourceName(){return world;}
string resource="{469880DFE1B48F95}Missions/Nogova_TerrainTest.conf";
static SCR_MissionHeader* Cast(SCR_MissionHeader* p){return p;}
string GetHeaderResourceName(){return resource;}
};
struct World {string root="Armenhof";int count=1;int GetNumSubScenes(){return count;}string GetSubSceneName(int){return root;}};
struct Base {World* world=nullptr;string loaded;World* GetWorld(){return world;}string GetWorldFile(){return loaded;}
SCR_MissionHeader* header=nullptr;int cleanup=0;
SCR_MissionHeader* GetMissionHeader(){return header;}
string GetMissionName(){return header?header->m_sName:"";}
int updates=0;void OnUpdate(World*,float){updates++;}
void OnBeforeWorldCleanup(){cleanup++;}
};
struct Game:Base {
SCR_MissionHeader* m_ERRPresentedTerrainHeader=nullptr;
string m_ERROriginalTerrainTitle,m_ERROriginalTerrainGameMode,m_ERRAppliedTerrainTitle,m_ERRTitleWorld,m_ERRTerrainLabel;
World* m_ERRPresentedTerrainWorld=nullptr;bool m_ERRTitleLoaded=false;float m_ERRTerrainPresentationWait=0;
int m_ERRTerrainStage=Stage::INACTIVE;
''' + s + r'''
};
int main(){
Game g;SCR_MissionHeader h;g.header=&h;
// Native DS request occurs before the original mode has been replaced.
assert(g.GetMissionName()=="Co10 Escape Reintroduced - Nogova");
assert(h.m_sName=="Nogova — Exploration Alpha");
assert(g.GetMissionName()=="Co10 Escape Reintroduced - Nogova"&&resolutions==1);
h.world="Armenhof";assert(g.GetMissionName()=="Co10 Escape Reintroduced - Armenhof");
h.world="Nogova";
World loaded;g.world=&loaded;g.loaded="different";assert(g.GetMissionName()=="Co10 Escape Reintroduced - Nogova");
g.loaded=h.world;assert(g.GetMissionName()=="Co10 Escape Reintroduced - Armenhof");g.world=nullptr;
server=false;assert(g.GetMissionName()==h.m_sName);server=true;
enabled=false;assert(g.GetMissionName()==h.m_sName);enabled=true;
editor=true;assert(g.GetMissionName()==h.m_sName);g.ERR_PresentTerrainTakeover();assert(!g.m_ERRPresentedTerrainHeader);editor=false;
g.m_ERRTerrainStage=Stage::FAILED;assert(g.GetMissionName()==h.m_sName);g.m_ERRTerrainStage=Stage::RUNNING;
// Confirmed takeover changes display metadata, never the terrain selection.
g.ERR_PresentTerrainTakeover();g.ERR_PresentTerrainTakeover();
assert(h.m_sName=="Co10 Escape Reintroduced - Nogova"&&h.m_sGameMode=="Escape");
assert(h.resource=="{469880DFE1B48F95}Missions/Nogova_TerrainTest.conf");
g.OnBeforeWorldCleanup();assert(g.cleanup==1&&!g.m_ERRPresentedTerrainHeader);
assert(h.m_sName=="Nogova — Exploration Alpha"&&h.m_sGameMode=="Terrain exploration");
// A reused header cannot keep Escape's name after an opt-out/restart.
enabled=false;assert(g.GetMissionName()==h.m_sName);enabled=true;
SCR_MissionHeader authored;authored.m_sName="Escape — Authored island";authored.m_sGameMode="Escape";
g.header=&authored;assert(g.GetMissionName()==authored.m_sName);
g.header=&h;g.ERR_PresentTerrainTakeover();h.m_sName="Another system's title";
g.ERR_RestoreTerrainPresentation();assert(h.m_sName=="Another system's title"&&h.m_sGameMode=="Terrain exploration");
h.world="";g.ERR_RestoreTerrainPresentation();assert(g.GetMissionName()=="Co10 Escape Reintroduced");
// A confirmed running terrain wins over a stale source header, on server and client.
g.OnBeforeWorldCleanup();h.m_sName="Original Everon scenario";h.world="Everon";g.header=&h;
World actual;actual.root="Nogova";actual.count=0;g.world=&actual;g.loaded="Nogova";server=false;
assert(g.ERR_TerrainLoadingTitle(&h)=="Original Everon scenario");
g.ERR_PresentTerrainTakeover();assert(h.m_sName=="Co10 Escape Reintroduced - Nogova");
// A late root resolution must refresh the SAME header even after GameMode became Escape.
actual.count=1;actual.root="Armenhof";g.OnUpdate(&actual,.6);
assert(h.m_sName=="Co10 Escape Reintroduced - Armenhof"&&g.updates==1);
assert(g.GetMissionName()==h.m_sName);server=true;assert(g.GetMissionName()==h.m_sName);
// Raw addon-qualified paths must never reach the native GetPath resolver.
g.loaded="$ChernarusMinus:Worlds/GM_Cherno/GM_Cherno.ent";actual.root="$ChernarusMinus:Worlds/ChernarusMinusBeta.ent";
g.ERR_RefreshTerrainPresentation();int warm=resolutions;
for(int i=0;i<120;i++){g.OnUpdate(&actual,.5);g.GetMissionName();g.ERR_TerrainLoadingTitle(&h);}
assert(resolutions==warm&&h.m_sName=="Co10 Escape Reintroduced - $ChernarusMinus:Worlds/ChernarusMinusBeta.ent");
assert(g.Path("{0123456789ABCDEF}Worlds/Island.ent")=="Worlds/Island.ent");
assert(g.Path(g.loaded)==g.loaded);
SCR_MissionHeader copy=h;copy.m_sName="Original Everon scenario";
assert(g.ERR_TerrainLoadingTitle(&copy)==h.m_sName);
copy.resource="different incoming scenario";copy.m_sName="Next map";
assert(g.ERR_TerrainLoadingTitle(&copy)=="Next map");
// A different current header cannot inherit the outgoing world's confirmed title.
SCR_MissionHeader incoming;incoming.world="New island";g.header=&incoming;
assert(g.GetMissionName()=="Co10 Escape Reintroduced - New island");
g.header=&h;h.m_sName="Later mod title";actual.root="Other";g.ERR_PresentTerrainTakeover();
assert(h.m_sName=="Later mod title");g.ERR_RestoreTerrainPresentation();assert(h.m_sName=="Later mod title");
h.m_sName="Original Everon scenario";g.ERR_PresentTerrainTakeover();g.OnBeforeWorldCleanup();
assert(h.m_sName=="Original Everon scenario"&&!g.m_ERRPresentedTerrainWorld);
g.header=nullptr;assert(g.GetMissionName().empty());g.ERR_PresentTerrainTakeover();g.OnBeforeWorldCleanup();
}
''')
print('PASS: native name callback preserves authored modes, opt-outs and failures; terrain suffix follows world changes, cached resolution and confirmed takeover metadata restores on cleanup without changing scenario resource or overwriting later titles')
# Resolve actual parent chains without requiring a loaded game world.
resolver='\n'.join(body(p,signature) for signature in ('static string Path(', 'static string Label(', 'static string Resolve('))
for a,b in [('foreach (','for ('),('ResourceName','string'),('array<string>','Array<string>'),('Resource resource','Resource* resource'),('BaseContainer container','Container* container'),('BaseContainerTools.LoadContainer','Load'),('resource.','resource->'),('container.','container->'),('GetResource().','GetResource()->'),('FilePath.StripPath','StripPath'),('container->Get("Parent", parent)','container->Get(parent)')]:resolver=resolver.replace(a,b)
run(r'''
#include <cassert>
#include <string>
#include <vector>
#include <map>
#include <algorithm>
struct string:std::string {
using std::string::string;string(std::string v):std::string(v){}bool IsEmpty(){return empty();}int Length(){return size();}
int IndexOf(string x){auto n=find(x);return n==npos?-1:int(n);}string Substring(int a,int n){return substr(a,n);}
void ToLower(){std::transform(begin(),end(),begin(),[](unsigned char c){return std::tolower(c);});}
void ToUpper(){std::transform(begin(),end(),begin(),[](unsigned char c){return std::toupper(c);});}
void Replace(string a,string b){size_t n=0;while((n=find(a,n))!=npos){replace(n,a.size(),b);n+=b.size();}}
string Trim(){auto a=find_first_not_of(" \r\n");if(a==npos)return "";return substr(a,find_last_not_of(" \r\n")-a+1);}
string GetPath(){auto n=find('}');return n==npos?*this:string(substr(n+1));}
bool IsExternal(){assert(false && "Do not resolve a raw terrain path");return true;}
bool IsInternal(){assert(false && "Do not resolve a raw terrain path");return true;}
};
string StripPath(string s){auto n=s.find_last_of('/');return n==s.npos?s:string(s.substr(n+1));}
template<class T>struct Array:std::vector<T>{void Insert(T v){this->push_back(v);}bool Contains(T v){return std::find(this->begin(),this->end(),v)!=this->end();}};
struct Container{string parent;bool Get(string& p){p=parent;return !p.IsEmpty();}};
struct Resource{Container container;bool valid=true;bool IsValid(){return valid;}Resource* GetResource(){return this;}Container* ToBaseContainer(){return &container;}};
std::map<string,Resource> resources;int calls=0;
Resource* Load(string world){calls++;auto it=resources.find(world);return it==resources.end()?nullptr:&it->second;}
string Res(string path){return "{0123456789ABCDEF}"+path;}
struct Names {
'''+resolver+r'''
};
int main(){
resources[Res("Worlds/GM/Conflict.ent")].container.parent=Res("Worlds/Armenhof/Armenhof.ent");
resources[Res("Worlds/Armenhof/Armenhof.ent")];
assert(Names::Resolve(Res("Worlds/GM/Conflict.ent"))=="Armenhof");
resources[Res("Worlds/Nogova/Nogova_Test.ent")].container.parent=Res("Worlds/Nogova/Nogova.ent");
resources[Res("Worlds/Nogova/Nogova.ent")];
assert(Names::Resolve(Res("Worlds/Nogova/Nogova_Test.ent"))=="Nogova");
calls=0;assert(Names::Resolve("worlds/Eden/Eden.ent")=="Everon"&&calls==0);
assert(Names::Resolve("$NogovaReforger:Worlds/Nogova/Nogova.ent")=="Nogova"&&calls==0);
assert(Names::Label("{853E92315D1D9EFE}worlds/Eden/Eden.ent")=="Everon");
assert(Names::Resolve("Worlds/New_Island.ent")=="New Island");
assert(Names::Label("Worlds/GameMaster/GM_Armenhof.ent")=="Armenhof");
assert(Names::Label("Worlds/Nogova/Nogova_Test.ent")=="Nogova");
assert(Names::Label("Worlds/ERR_Everon.ent")=="Everon");
assert(Names::Label("Worlds/armenhof.ent")=="Armenhof");
assert(Names::Resolve("")=="");
assert(Names::Path("$ChernarusMinus:Worlds/GM_Cherno/GM_Cherno.ent")=="$ChernarusMinus:Worlds/GM_Cherno/GM_Cherno.ent");
assert(Names::Path("{0123456789ABCDEF}Worlds/Island.ent")=="Worlds/Island.ent");
assert(Names::Path("{bad}Worlds/Island.ent")=="{bad}Worlds/Island.ent");
assert(Names::Label("$NogovaReforger:Worlds/Nogova/Nogova_Test.ent")=="Nogova");
resources[Res("a.ent")].container.parent=Res("b.ent");resources[Res("b.ent")].container.parent=Res("a.ent");
calls=0;assert(Names::Resolve(Res("a.ent"))=="B"&&calls==2);
for(int i=0;i<30;i++)resources[Res(std::to_string(i)+".ent")].container.parent=Res(std::to_string(i+1)+".ent");
calls=0;assert(Names::Resolve(Res("0.ent"))=="16"&&calls==16);
}
''')
print('PASS: actual terrain parent traversal, Armenhof/Nogova names, Everon alias, missing-resource fallback, empty names and bounded cycles/depth')
# Run the real loading component hooks with widget/native-super boundaries.
loading='\n'.join(body(p,x) for x in ('override void OnMissionDataRetrieved(', 'protected void ERR_RefreshLoadingTerrainTitle(', 'override void Update(float timeSlice'))
for a,b in [('override ',''),('protected ',''),('SCR_MissionHeader header','SCR_MissionHeader* header'),('super.','Base::'),('m_Widgets.','m_Widgets->'),('m_wTitle.','m_wTitle->'),('GetGame().','GetGame()->')]:loading=loading.replace(a,b)
run(r'''
#include <cassert>
#include <string>
using string=std::string;
struct SCR_MissionHeader{string title;};
struct Text{string text;int writes=0;void SetText(string t){text=t;writes++;}};
struct Widgets{Text*m_wTitle=nullptr;};
struct Game{bool confirmed=false;string actual="Nogova";string ERR_TerrainLoadingTitle(SCR_MissionHeader*h){return confirmed?"Co10 Escape Reintroduced - "+actual:h->title;}};
Game game;Game*current=&game;Game*GetGame(){return current;}
struct Base{Widgets*m_Widgets=nullptr;int inits=0,updates=0;void OnMissionDataRetrieved(SCR_MissionHeader*h){inits++;if(h&&m_Widgets&&m_Widgets->m_wTitle)m_Widgets->m_wTitle->SetText(h->title);}void Update(float,float,float){updates++;}};
struct Loading:Base{SCR_MissionHeader*m_ERRLoadingTerrainHeader=nullptr;string m_ERRLoadingTerrainTitle;float m_ERRLoadingTerrainWait=0;
'''+loading+r'''
};int main(){
Loading screen;Widgets widgets;Text text;widgets.m_wTitle=&text;screen.m_Widgets=&widgets;
SCR_MissionHeader source{"Original Everon scenario"};screen.OnMissionDataRetrieved(&source);
assert(text.text==source.title&&screen.inits==1);
game.confirmed=true;screen.Update(.25);assert(text.text==source.title);
screen.Update(.25);assert(text.text=="Co10 Escape Reintroduced - Nogova"&&screen.inits==1&&screen.updates==2);
int writes=text.writes;screen.Update(.6);assert(text.writes==writes); // unchanged titles don't churn widgets
game.actual="Armenhof";screen.Update(.6);assert(text.text=="Co10 Escape Reintroduced - Armenhof");
game.confirmed=false;screen.Update(.6);assert(text.text==source.title);
screen.OnMissionDataRetrieved(nullptr);screen.Update(.6);assert(screen.inits==2);
screen.m_Widgets=nullptr;screen.OnMissionDataRetrieved(&source);screen.Update(.6); // layout not attached yet
screen.m_Widgets=&widgets;current=nullptr;screen.Update(.6); // game teardown
assert(screen.updates==8);
}
''')
print('PASS: actual loading title hooks follow late takeover/terrain changes, keep native initialization/progress updates, bound refreshes and tolerate missing headers/widgets/game')