"""Actual start setting parse/save and admin command bodies; native IO/UI are doubled. Frozen-mode lifecycle itself is tested by the separate core start fixture. """ import os,re from pathlib import Path from policy_harness import ROOT,body,run BASE=Path(os.environ.get('ERR_START_SETTINGS_ROOT',ROOT));CORE=BASE/'addons/core/Scripts/Game/ERR/00_Core' S=Path(os.environ.get('ERR_SETTINGS_SOURCE', CORE/'ERR_Settings.c'));A=CORE/'ERR_AdminService.c';M=CORE/'ERR_AdminMenu.c' def cpp(s): s=s.replace('protected ','').replace('out int ','int& ').replace('out map values','Values& values').replace('new map()','Values()').replace('map','Values') s=s.replace('array parts','Array parts').replace('array replies','Array& replies').replace('array','Array').replace('foreach (','for (') s=s.replace('JsonLoadContext json = new JsonLoadContext();','JsonLoadContext json;').replace('JsonSaveContext json = new JsonSaveContext();','JsonSaveContext json;').replace('JsonLoadContext json,','JsonLoadContext& json,') s=s.replace('ERR_GameMode mode','Mode* mode').replace('mode.','mode->').replace('Settings().','Settings()->').replace('Math.Round','std::round') for n in ['ERR_Settings','ERR_ProfileConfig','ERR_CommandArguments','ERR_AdminMenuFields','ERR_TimeService','ERR_EnvironmentService','Replication','FileIO']:s=s.replace(n+'.',n+'::') s=s.replace('"0123456789".IndexOf','string("0123456789").IndexOf') for n in ['value','saved','current']:s=s.replace(n+'.ToString()','string(std::to_string('+n+'))') return s settings='\n'.join(cpp(body(S,sig)) for sig in ['static array Keys(','static bool Valid(','static bool Randomizable(','static bool ValidSelection(','static string SelectionText(','static int AddedDefault(','protected bool Read(','static bool Parse(','bool Set(']) args='\n'.join(cpp(body(A,sig)) for sig in ['static bool Integer(','static bool Selection(']) fields='\n'.join(cpp(body(CORE/'ERR_AdminMenuFields.c',sig)) for sig in ['static string Display(','static string StartSummary(']) command=cpp(body(A,'protected bool StartCommand(')) run(r''' #include #include #include #include #include #include struct string:std::string{using std::string::string;string(std::string s):std::string(s){}bool IsEmpty(){return empty();}int Length(){return size();}int ToInt(){return std::stoi(*this);}int IndexOf(string x){auto n=find(x);return n==npos?-1:int(n);}string Substring(int a,int n){return substr(a,n);}}; templatestruct Array:std::vector{using std::vector::vector;int Count(){return this->size();}void Insert(T v){this->push_back(v);}}; struct Values{std::map data;int Get(string k){return data.at(k);}void Set(string k,int v){data[k]=v;}void Remove(string k){data.erase(k);}}; std::map> disk;bool failSave=false; struct JsonLoadContext{std::map data;bool DoesKeyExist(string k){return data.count(k);}bool ReadValue(string k,float& v){if(!data.count(k))return false;v=data[k];return true;}}; struct JsonSaveContext{std::map data;void EnableTypeDiscriminator(bool){}void WriteValue(string k,int v){data[k]=v;}bool SaveToFile(string p){if(failSave)return false;disk[p]=data;return true;}}; namespace ERR_ProfileConfig{bool Load(string p,JsonLoadContext& j){if(!disk.count(p))return false;j.data=disk[p];return true;}bool Save(string p,JsonSaveContext&j){return j.SaveToFile(p);}bool Exists(string p){return disk.count(p);}} namespace FileIO{bool CopyFile(string a,string b){if(!disk.count(a))return false;disk[b]=disk[a];return true;}void DeleteFile(string p){disk.erase(p);}} namespace Replication{bool server=true;bool IsServer(){return server;}} struct ERR_Settings{static constexpr auto PATH="settings";string m_Path=PATH;bool m_ReadOnly=false;Values m_Values,m_Resolved;int Get(string k){return m_Values.Get(k);} '''+settings+r''' }; namespace ERR_TimeService{bool ParseClock(string,int&){return false;}string Clock(int){return "clock";}} namespace ERR_EnvironmentService{bool ParseDate(string,int&){return false;}bool ParseProfile(string,int&){return false;}string Date(int){return "date";}string ProfileName(int){return "weather";}} struct ERR_CommandArguments{ '''+args+r''' }; struct ERR_AdminMenuFields{ '''+fields+r''' }; struct Mode{ERR_Settings* settings;int frozen;ERR_Settings*Settings(){return settings;}int StartMode(){return frozen;}}; struct Admin{ '''+command+r''' }; int main(){ assert(ERR_Settings::AddedDefault("startMode")==0); assert(!ERR_Settings::Randomizable("startMode")&&!ERR_Settings::ValidSelection("startMode",-1)&&!ERR_Settings::ValidSelection("startMode",-2)); disk["old"]={{"reportIntervalSeconds",10},{"supportSquadLimit",4},{"responseRadiusMetres",2000},{"aiIntelChancePercent",60},{"intelRevealChancePercent",15},{"radioDefenceSeconds",180}}; ERR_Settings settings;assert(settings.Read("old",settings.m_Values)&&settings.Get("startMode")==0); // Older configurations gain the enabled/five-hour defaults. Each restart // setting survives a save/reload, while invalid selections cannot change it. for(string key:{"restartOnEmpty","emptyRestartHours"}){ int expected=key=="restartOnEmpty"?1:5; assert(ERR_Settings::AddedDefault(key)==expected&&settings.Get(key)==expected); assert(!ERR_Settings::Randomizable(key)); for(float bad:{-2.f,-1.f,.5f,169.f}){ disk["bad"]=disk["old"];disk["bad"][key]=bad;Values ignored; assert(!settings.Read("bad",ignored)); } int choice=key=="restartOnEmpty"?0:168; assert(settings.Set(key,choice)); ERR_Settings restored;assert(restored.Read("settings",restored.m_Values)); assert(restored.Get(key)==choice); failSave=true;assert(!settings.Set(key,expected));failSave=false; assert(settings.Get(key)==choice); assert(settings.Set(key,expected)); } int restartSelection=-1; assert(ERR_CommandArguments::Selection("restartOnEmpty","on",restartSelection)&&restartSelection==1); assert(ERR_CommandArguments::Selection("restartOnEmpty","off",restartSelection)&&restartSelection==0); for(float bad:{-1.f,-2.f,2.f,.5f}){disk["bad"]=disk["old"];disk["bad"]["startMode"]=bad;Values ignored;assert(!settings.Read("bad",ignored));} // Newly introduced doctrine choices migrate safely and survive the same transaction. for(string key:{"aiDoctrine","aiShadow"}){ assert(ERR_Settings::AddedDefault(key)==0&&settings.Get(key)==0); assert(!ERR_Settings::Randomizable(key)); for(float bad:{-2.f,-1.f,2.f,.5f}){ disk["bad"]=disk["old"];disk["bad"][key]=bad;Values ignored; assert(!settings.Read("bad",ignored)); } assert(settings.Set(key,1));ERR_Settings restored; assert(restored.Read("settings",restored.m_Values)&&restored.Get(key)==1); failSave=true;assert(!settings.Set(key,0)&&settings.Get(key)==1);failSave=false; assert(!settings.Set(key,2)&&!settings.Set(key,-1)&&settings.Get(key)==1); assert(settings.Set(key,0)); } int doctrine=-1; assert(ERR_CommandArguments::Selection("aiDoctrine","current",doctrine)&&doctrine==0); assert(ERR_CommandArguments::Selection("aiDoctrine","coordinated",doctrine)&&doctrine==1); assert(ERR_CommandArguments::Selection("aiShadow","off",doctrine)&&doctrine==0); assert(ERR_CommandArguments::Selection("aiShadow","on",doctrine)&&doctrine==1); for(string key:{"aiDoctrine","aiShadow"}) for(string invalid:{"random","random-day","enabled","currentjunk","coordinatedjunk","1.0","-1"}) assert(!ERR_CommandArguments::Selection(key,invalid,doctrine)); assert(ERR_AdminMenuFields::Display("aiDoctrine","0")=="Current"); assert(ERR_AdminMenuFields::Display("aiDoctrine","1")=="Coordinated"); assert(ERR_AdminMenuFields::Display("aiShadow","0")=="Off"); assert(ERR_AdminMenuFields::Display("aiShadow","1")=="On"); assert(settings.Get("fieldCampCount")==12&&settings.Get("fieldHospitalCount")==6); for(string key:{"fieldCampCount","fieldHospitalCount"}){ int maximum=key=="fieldCampCount"?16:8; assert(!settings.Set(key,0)&&!settings.Set(key,maximum+1)); assert(settings.Set(key,maximum));ERR_Settings savedSites; assert(savedSites.Read("settings",savedSites.m_Values)&&savedSites.Get(key)==maximum); } assert(settings.Get("startWeaponCount")==5&&settings.Get("startMagazineCount")==8); for(string key:{"startWeaponCount","startMagazineCount"}){ int maximum=key=="startWeaponCount"?12:48; assert(!ERR_Settings::Randomizable(key)); for(float bad:{-1.f,float(maximum+1),.5f}){ disk["bad"]=disk["old"];disk["bad"][key]=bad;Values ignored;assert(!settings.Read("bad",ignored)); } assert(!settings.Set(key,-1)&&!settings.Set(key,maximum+1)); for(int count:{0,1,maximum}){ assert(settings.Set(key,count));ERR_Settings restored; assert(restored.Read("settings",restored.m_Values)&&restored.Get(key)==count); } failSave=true;assert(!settings.Set(key,0)&&settings.Get(key)==maximum);failSave=false; } assert(settings.Get("objectGuardCount")==8); assert(!ERR_Settings::Randomizable("objectGuardCount")); assert(!settings.Set("objectGuardCount",-1)&&!settings.Set("objectGuardCount",17)); for(int guards:{0,1,8,16}){ assert(settings.Set("objectGuardCount",guards)); ERR_Settings savedGuards;assert(savedGuards.Read("settings",savedGuards.m_Values)&&savedGuards.Get("objectGuardCount")==guards); } failSave=true;assert(!settings.Set("objectGuardCount",8)&&settings.Get("objectGuardCount")==16);failSave=false; assert(settings.Get("hunterSpawnDistanceMetres")==1000); assert(!ERR_Settings::Randomizable("hunterSpawnDistanceMetres")); assert(!settings.Set("hunterSpawnDistanceMetres",799)&&!settings.Set("hunterSpawnDistanceMetres",3001)); assert(settings.Set("hunterSpawnDistanceMetres",1800)); ERR_Settings savedRadius;assert(savedRadius.Read("settings",savedRadius.m_Values)&&savedRadius.Get("hunterSpawnDistanceMetres")==1800); failSave=true;assert(!settings.Set("hunterSpawnDistanceMetres",1000)&&settings.Get("hunterSpawnDistanceMetres")==1800);failSave=false; assert(ERR_Settings::AddedDefault("objectGuardEvery")==3 && ERR_Settings::AddedDefault("objectGuardHQMode")==1); for(int every=1;every<=4;every++) assert(settings.Set("objectGuardEvery",every)); for(int mode=0;mode<=2;mode++) assert(settings.Set("objectGuardHQMode",mode)); assert(!settings.Set("objectGuardEvery",0)&&!settings.Set("objectGuardEvery",5)); assert(!settings.Set("objectGuardHQMode",-1)&&!settings.Set("objectGuardHQMode",3)); ERR_Settings guardsReloaded;assert(guardsReloaded.Read("settings",guardsReloaded.m_Values)); assert(guardsReloaded.Get("objectGuardEvery")==4&&guardsReloaded.Get("objectGuardHQMode")==2); Mode mode{&settings,0};Admin admin;Array replies; assert(!admin.StartCommand(&mode,{"mission","radio","show"},replies)); assert(admin.StartCommand(&mode,{"mission","start","set","crash"},replies)); assert(settings.Get("startMode")==1&&mode.StartMode()==0&&disk["settings"]["startMode"]==1); assert(replies.back().find("Next start: Crash | Current round: Prison")!=string::npos); assert(replies.back().find("Saved for next round")!=string::npos); ERR_Settings restarted;assert(restarted.Read("settings",restarted.m_Values));Mode next{&restarted,restarted.Get("startMode")};assert(next.StartMode()==1); auto saved=disk["settings"];failSave=true;admin.StartCommand(&mode,{"mission","start","set","prison"},replies);assert(settings.Get("startMode")==1&&disk["settings"]==saved&&mode.StartMode()==0);failSave=false; for(string bad:{"random","random-day","2","-1","0.5","crashjunk"}){admin.StartCommand(&mode,{"mission","start","set",bad},replies);assert(settings.Get("startMode")==1&&disk["settings"]==saved);} admin.StartCommand(&mode,{"mission","start","set","prison","extra"},replies);assert(settings.Get("startMode")==1); Replication::server=false;admin.StartCommand(&mode,{"mission","start","set","prison"},replies);assert(settings.Get("startMode")==1);Replication::server=true; settings.m_ReadOnly=true;admin.StartCommand(&mode,{"mission","start","set","prison"},replies);assert(settings.Get("startMode")==1);settings.m_ReadOnly=false; admin.StartCommand(&mode,{"mission","start","set","0"},replies);assert(settings.Get("startMode")==0&&mode.StartMode()==0); admin.StartCommand(&next,{"mission","start","show"},replies);assert(replies.back()=="Next start: Crash | Current round: Crash"); } ''') assert 'm_Values.Set("startMode", AddedDefault("startMode"));' in S.read_text() execute=body(A,'void Execute(');assert execute.index('if (!authorized)')