Simplify content controls and add vehicle roles; record Workshop 0.0.144
continuous-integration/drone/push Build is passing
continuous-integration/drone/push Build is passing
Discover installed mod vehicles and save independent garage, traffic, patrol and convoy assignments. Retire the superseded RHS 1.9 overrides. Include native menu, crew/driving and publication evidence.
This commit is contained in:
@@ -217,7 +217,7 @@ weight_layout=(ROOT/'addons/core/UI/layouts/ERR/ItemWeightRow.layout').read_text
|
||||
validate_layout_structure(weight_layout)
|
||||
weight_names=re.findall(r'\bName "([^"]+)"', weight_layout)
|
||||
assert len(weight_names)==len(set(weight_names))
|
||||
for name in ('Sidebar_Items','OpenItems','OpenAllowedItems','AllowedItemsPage','OpenItemPresets','ItemPresetsPage','OpenWeights','WeightsPage','WeightsMod','WeightsShow','WeightsPrevious','WeightsNext','ModWeight','ModWeightApply','ModWeightNormal','ModWeightHigh','ModWeightSaved','WeightsChance','WeightsPageState','WeightsContent','WeightsScroll'):
|
||||
for name in ('Sidebar_Content','OpenItems','OpenAllowedItems','AllowedItemsPage','OpenItemPresets','ItemPresetsPage','OpenWeights','WeightsPage','WeightsMod','WeightsShow','WeightsPrevious','WeightsNext','ModWeight','ModWeightApply','ModWeightNormal','ModWeightHigh','ModWeightSaved','WeightsChance','WeightsPageState','WeightsContent','WeightsScroll'):
|
||||
assert name in names, name
|
||||
for name in ('WeightLabel','WeightState','WeightValue','WeightApply','WeightNormal'):
|
||||
assert name in weight_names, name
|
||||
|
||||
@@ -115,7 +115,9 @@ int main(){
|
||||
# Integration boundaries: no military admission or late observer-driven retargets.
|
||||
s=p.read_text()
|
||||
assert '.RegisterGroup(' not in s and '.Own(' not in s
|
||||
assert 'm_Serial >= 64' in body(p,'protected bool Spawn(')
|
||||
spawn=body(p,'protected bool Spawn(')
|
||||
assert 'm_Serial >= 64' in spawn
|
||||
assert spawn.count('m_Serial++') == 1 and spawn.index('ERR_Actors.Spawn(prefab') < spawn.index('m_Serial++') < spawn.index('!ERR_VehicleSelection.RoadFit')
|
||||
assert 'Observers(observers, actors)' in body(p,'protected bool Spawn(')
|
||||
assert 'trace.ExcludeArray = excluded' in body(p,'protected bool Hidden(')
|
||||
assert 'sample = -1; sample <= 1' in body(p,'protected bool Hidden(')
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
"""Search the entire server result set before paging; exercise real filter rules."""
|
||||
from policy_harness import ROOT, body, run
|
||||
p=ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_ServerItemCatalog.c'
|
||||
s=body(p,'static bool Matches(').replace('array<string>', 'Array<string>').replace('foreach (','for (').replace('WidgetManager.Translate','Translate')
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
struct string;
|
||||
template<class T>struct Array:std::vector<T>{};
|
||||
struct string:std::string{using std::string::string;string(std::string x):std::string(x){}bool IsEmpty(){return empty();}void ToLower(){std::transform(begin(),end(),begin(),[](unsigned char c){return std::tolower(c);});}bool Contains(string s){return find(s)!=npos;}void Split(string,Array<string>& out,bool){std::istringstream in(*this);std::string s;while(in>>s)out.push_back(s);}};
|
||||
string Translate(string s){return s;}
|
||||
''' + s + r'''
|
||||
int main(){
|
||||
assert(Matches("First Aid Kit","items/medical/kit.et",true,0,"first kit"));
|
||||
assert(Matches("FIRST AID KIT","items/medical/kit.et",true,1,"medical"));
|
||||
assert(!Matches("First Aid Kit","items/medical/kit.et",true,2,""));
|
||||
assert(Matches("V2101 Red","cars/V2101_red.et",false,2,"v2101 RED"));
|
||||
assert(!Matches("V2101 Red","cars/V2101_red.et",false,1,""));
|
||||
assert(!Matches("V2101 Red","cars/V2101_red.et",true,0,"v2101 blue"));
|
||||
assert(Matches("anything","",false,0," "));
|
||||
int matches=0;for(int i=0;i<5000;i++)if(Matches("First Aid Kit","items/kit.et",i%2,2,"kit"))matches++;
|
||||
assert(matches==2500);
|
||||
}
|
||||
''')
|
||||
print('PASS: whole-catalog multiword/case-insensitive search, name/path matching, enabled/disabled filters and large result sets')
|
||||
@@ -7,6 +7,7 @@ source=path.read_text()
|
||||
# Dispatch's native calls are doubled; budget behavior is executed separately.
|
||||
code=body(path,'ERR_ConvoyTask Dispatch(')
|
||||
code=re.sub(r'//[^\n]*','',code)
|
||||
code=code.replace('array<ResourceName>', 'Array<string>').replace('ResourceName','string').replace('prefab.IsEmpty()', 'prefab.empty()').replace('unit->Entity.FindComponent(InventoryStorageManagerComponent)', 'unit->Entity->storage').replace('GetGame().ERR_ServerVehicles().Reject', 'Reject')
|
||||
code=code.replace('array<vector>','Array<vector>&')
|
||||
code=code.replace('SCR_AIGroup.Cast(', 'static_cast<SCR_AIGroup*>(').replace('Vehicle.Cast(', 'static_cast<Vehicle*>(')
|
||||
code=code.replace('FactionAffiliationComponent.Cast(unit.Entity.FindComponent(FactionAffiliationComponent))','&unit->Entity->affiliation').replace('AICarMovementComponent.Cast(unit.Entity.FindComponent(AICarMovementComponent))','&unit->Entity->movement')
|
||||
@@ -14,8 +15,9 @@ for cls in ['ERR_ConvoyTask','ERR_ConvoyReceipt','ERR_ForceRoute','ERR_ConvoyVeh
|
||||
code=re.sub(r'\b'+cls+r' ([A-Za-z_]\w*)',cls+r'* \1',code)
|
||||
for obj in ['m_Mode','m_Pool','task','unit','affiliation','movement']:
|
||||
code=re.sub(r'\b'+obj+r'\.',obj+'->',code)
|
||||
for cls in ['ERR_ForceRules','ERR_ConvoyRules','ERR_ConvoyResources','ERR_Actors','ERR_HQ']:
|
||||
for cls in ['ERR_ForceRules','ERR_ConvoyRules','ERR_ConvoyResources','ERR_Actors','ERR_HQ','ERR_VehicleSelection']:
|
||||
code=code.replace(cls+'.',cls+'::')
|
||||
code=code.replace('unit->Entity.FindComponent(InventoryStorageManagerComponent)', 'unit->Entity->storage')
|
||||
code=code.replace('EnemyFaction().','EnemyFaction()->').replace('Group.Get','Group->Get').replace('null','nullptr')
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
@@ -28,7 +30,7 @@ struct IEntity{virtual ~IEntity()=default;};
|
||||
struct FactionAffiliationComponent{void SetAffiliatedFaction(Faction*){}};
|
||||
struct AICarMovementComponent{int speed=0;void SetCruiseSpeed(int v){speed=v;}};
|
||||
struct SCR_AIGroup:IEntity{Faction* GetFaction(){return &enemy;}int GetAgentsCount(){return 0;}};
|
||||
struct Vehicle:IEntity{FactionAffiliationComponent affiliation;AICarMovementComponent movement;};
|
||||
struct Vehicle:IEntity{bool storage=true;FactionAffiliationComponent affiliation;AICarMovementComponent movement;};
|
||||
struct ERR_GameMode{Array<IEntity*>owned;Faction* EnemyFaction(){return &enemy;}void Own(IEntity* e){owned.Insert(e);}};
|
||||
struct ERR_ForceRoute{};
|
||||
struct ERR_ConvoyReceipt{bool used=false;};
|
||||
@@ -40,6 +42,8 @@ namespace ERR_ConvoyRules{bool valid=true,clear=true,empty=true;bool EmptyCrewTe
|
||||
namespace ERR_ConvoyResources{bool available=true;bool Available(){return available;}string GroupPrefab(){return "group";}string VehiclePrefab(int i){return "vehicle"+std::to_string(i);}}
|
||||
namespace ERR_HQ{void Event(string,string){}}
|
||||
namespace ERR_Actors{int calls=0,fail=0;IEntity* Spawn(string prefab,vector,vector=vector{}){calls++;if(calls==fail)return nullptr;if(prefab=="group")return new SCR_AIGroup();return new Vehicle();}}
|
||||
namespace ERR_VehicleSelection{bool empty=false;bool fit=true;string ForRole(int,string preferred){return empty?"":preferred;}bool RoadFit(Vehicle* v){return v&&fit;}}
|
||||
void Reject(string,int){}
|
||||
struct Service{ERR_GameMode* m_Mode;ERR_ConvoyPool* m_Pool;Array<ERR_ConvoyTask*>m_Tasks;bool current=true;int crews=0,failCrew=-1;
|
||||
ERR_ConvoyTask* NewTask(){return new ERR_ConvoyTask();} ERR_ConvoyVehicle* NewVehicle(){return new ERR_ConvoyVehicle();}
|
||||
bool Current(){return current;}bool FillCrew(ERR_ConvoyVehicle* unit){crews++;return unit->Index!=failCrew;}
|
||||
@@ -50,6 +54,7 @@ int main(){
|
||||
ERR_ConvoyRules::valid=false;assert(!service.Dispatch(&receipt,positions,angles,&route,100));assert(!receipt.used&&ERR_Actors::calls==0);ERR_ConvoyRules::valid=true;
|
||||
ERR_ConvoyRules::clear=false;assert(!service.Dispatch(&receipt,positions,angles,&route,100));assert(!receipt.used&&ERR_Actors::calls==0);ERR_ConvoyRules::clear=true;
|
||||
ERR_ConvoyResources::available=false;assert(!service.Dispatch(&receipt,positions,angles,&route,100));assert(!receipt.used&&ERR_Actors::calls==0);ERR_ConvoyResources::available=true;
|
||||
ERR_VehicleSelection::empty=true;assert(!service.Dispatch(&receipt,positions,angles,&route,100)&&!receipt.used&&ERR_Actors::calls==0);ERR_VehicleSelection::empty=false;
|
||||
enemy.key="US"; // A selected non-USSR enemy uses the same finite dispatch transaction.
|
||||
auto task=service.Dispatch(&receipt,positions,angles,&route,100);assert(task&&task->Vehicles.Count()==6&&!task->stopped&&receipt.used&&pool.consumes==1&&ERR_Actors::calls==10&&mode.owned.Count()==10&&service.crews==5);
|
||||
assert(task->Vehicles[2]->Released&&!task->Vehicles[2]->Entity&&!task->Vehicles[2]->Group);
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
from policy_harness import ROOT, body, run
|
||||
import re
|
||||
p=ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_ItemPreferences.c'
|
||||
methods='\n'.join(body(p,n) for n in ('static bool Name(', 'static bool Guid(', 'static bool WeightKey(', 'static bool WeightValue(', 'static bool Valid(', 'protected void Rebuild(', 'bool Enabled(', 'bool Allows(', 'bool Set(', 'int WeightSetting(', 'int Weight(', 'bool SetWeight(', 'bool Preset('))
|
||||
methods=methods.replace('weight.ToString()', 'string(std::to_string(weight))').replace('Math.Max', 'std::max').replace('ERR_CommandArguments.Integer', 'Integer')
|
||||
methods='\n'.join(body(p,n) for n in ('static bool Name(', 'static bool Guid(', 'static bool RoleKey(', 'static bool WeightKey(', 'static bool WeightValue(', 'static bool Valid(', 'protected void Rebuild(', 'bool Enabled(', 'bool ModEnabled(', 'bool VehicleEnabled(', 'bool SetSwitch(', 'bool SetVehicleRole(', 'int VehicleRole(', 'bool AllowsVehicleRole(', 'bool AllowsVehicle(', 'bool Allows(', 'bool Set(', 'int WeightSetting(', 'int Weight(', 'bool SetWeight(', 'bool Preset('))
|
||||
methods=methods.replace('role.ToString()', 'string(std::to_string(role))').replace('weight.ToString()', 'string(std::to_string(weight))').replace('Math.Max', 'std::max').replace('ERR_CommandArguments.Integer', 'Integer')
|
||||
methods=methods.replace('protected ', '').replace('array<string>', 'Array<string>').replace('set<string> keys = new set<string>();','Array<string> keys;')
|
||||
methods=methods.replace('foreach (', 'for (').replace('ResourceName','string').replace('ERR_TerrainCatalog.Key(prefab)', 'prefab').replace('SCR_AddonTool.GetResourceAddons(prefab)', 'sources')
|
||||
methods=methods.replace('Array<string> sources = sources;', 'Array<string> resourceSources = sources;').replace('project : sources','project : resourceSources')
|
||||
methods=methods.replace('"01234".IndexOf', 'string("01234").IndexOf')
|
||||
methods=methods.replace('"0123456789abcdef".IndexOf','string("0123456789abcdef").IndexOf').replace('!records || ', '')
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
@@ -16,7 +17,7 @@ run(r'''
|
||||
#include <map>
|
||||
struct string;
|
||||
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;using std::vector<T>::operator=;int Count(){return this->size();}void Insert(T x){this->push_back(x);}bool Contains(T x){return std::find(this->begin(),this->end(),x)!=this->end();}void Clear(){this->clear();}};
|
||||
struct string:std::string{using std::string::string;string(std::string x):std::string(x){}bool IsEmpty(){return empty();}int Length(){return size();}int IndexOf(string s){auto i=find(s);return i==npos?-1:int(i);}string Substring(int a,int n){return substr(a,n);}int ToInt(){return std::stoi(*this);}void Split(string delim,Array<string>& out,bool){size_t a=0;while(true){auto b=find(delim,a);out.Insert(substr(a,b==npos?b:b-a));if(b==npos)break;a=b+delim.size();}}};
|
||||
struct string:std::string{using std::string::string;string(std::string x):std::string(x){}bool StartsWith(string s){return find(s)==0;}bool IsEmpty(){return empty();}int Length(){return size();}int IndexOf(string s){auto i=find(s);return i==npos?-1:int(i);}string Substring(int a,int n){return substr(a,n);}int ToInt(){return std::stoi(*this);}void Split(string delim,Array<string>& out,bool){size_t a=0;while(true){auto b=find(delim,a);out.Insert(substr(a,b==npos?b:b-a));if(b==npos)break;a=b+delim.size();}}};
|
||||
template<class T>struct Map{std::map<string,T> data;bool Contains(string k){return data.count(k);}T Get(string k){return data[k];}void Set(string k,T v){data[k]=v;}void Clear(){data.clear();}};
|
||||
bool Integer(string s,int& n){try{size_t end;n=std::stoi(s,&end);return end==s.size();}catch(...){return false;}}
|
||||
struct Prefs{Array<string> m_Records={"active"},sources={"base-project","mod-project"};Map<int> m_Rules;Map<string> m_Loaded;int m_Revision=0;bool fail=false;
|
||||
@@ -55,6 +56,8 @@ int main(){
|
||||
auto old=p.m_Records;int revision=p.m_Revision;p.fail=true;
|
||||
assert(!p.Set(b,"*",true)&&p.m_Records==old&&p.m_Revision==revision);
|
||||
assert(!p.Preset("save","failed")&&p.m_Records==old);
|
||||
assert(!p.SetSwitch(a,"mod",false)&&p.m_Records==old&&p.m_Revision==revision);
|
||||
assert(!p.SetSwitch(a,"v"+i,false)&&p.m_Records==old&&p.m_Revision==revision);
|
||||
assert(!p.Preset("delete","custom")&&p.m_Records==old);
|
||||
p.fail=false;
|
||||
assert(p.Preset("save","custom")&&p.Preset("delete","custom")&&!p.Preset("load","custom"));
|
||||
@@ -66,6 +69,28 @@ int main(){
|
||||
assert(!p.Valid({"active","active|1111111111111111|*|2"}));
|
||||
assert(!p.Valid({"active","active|1111111111111111|*|0","active|1111111111111111|*|1"}));
|
||||
assert(!p.Valid({"active","missing|1111111111111111|*|0"}));
|
||||
// Master disable preserves per-item and per-vehicle decisions; all-items does not erase either gate or vehicle rules.
|
||||
p.fail=false; p.m_Loaded.Set("base-project",a);p.m_Loaded.Set("mod-project",b);
|
||||
assert(p.Set(a,"*",true)&&p.Set(b,"*",true));
|
||||
assert(p.Set(a,i,false));assert(p.SetSwitch(a,"v"+j,false));
|
||||
assert(p.SetSwitch(a,"mod",false)&&!p.Allows(j)&&!p.AllowsVehicle(i));
|
||||
assert(p.Set(a,"*",true)&&!p.ModEnabled(a)&&!p.VehicleEnabled(a,j));
|
||||
assert(p.SetSwitch(a,"mod",true)&&p.Allows(j)&&p.AllowsVehicle(i)&&!p.AllowsVehicle(j));
|
||||
assert(p.Set(a,i,false)&&p.SetSwitch(a,"v*",true)&&!p.Allows(i)&&p.AllowsVehicle(j));
|
||||
assert(p.Preset("save","replacement")&&p.SetSwitch(a,"mod",false)&&p.Preset("load","replacement")&&p.ModEnabled(a));
|
||||
assert(!p.SetSwitch(a,"vbad",false)&&!p.SetSwitch("../", "mod",false));
|
||||
// New vehicles are opt-in, role choices independent, explicit source denials win.
|
||||
assert(p.SetVehicleRole(a,j,2,true)&&p.AllowsVehicleRole(j,2,false));
|
||||
assert(!p.AllowsVehicleRole(j,3,false));
|
||||
assert(p.SetVehicleRole(b,j,2,false)&&!p.AllowsVehicleRole(j,2,false));
|
||||
assert(p.SetVehicleRole(b,j,2,true)&&p.AllowsVehicleRole(j,2,false));
|
||||
assert(p.SetVehicleRole(a,"*",3,true)&&p.AllowsVehicleRole(j,3,false));
|
||||
assert(p.SetVehicleRole(a,j,3,false)&&!p.AllowsVehicleRole(j,3,false)&&p.AllowsVehicleRole(j,2,false));
|
||||
assert(p.Preset("save","replacement")&&p.SetVehicleRole(a,j,2,false)&&p.Preset("load","replacement")&&p.AllowsVehicleRole(j,2,false));
|
||||
assert(p.SetSwitch(a,"mod",false)&&!p.AllowsVehicleRole(j,2,true)&&p.SetSwitch(a,"mod",true)&&p.AllowsVehicleRole(j,2,false));
|
||||
int before=p.m_Revision;p.fail=true;assert(!p.SetVehicleRole(a,j,2,false)&&p.m_Revision==before&&p.AllowsVehicleRole(j,2,false));p.fail=false;
|
||||
assert(!p.SetVehicleRole(a,j,5,true)&&!p.SetVehicleRole(a,j,-1,true)&&!p.SetVehicleRole(a,"bad",0,true));
|
||||
assert(!p.Valid({"active","active|1111111111111111|r5*|1"}));
|
||||
assert(!p.Set("../x",i,true)&&!p.Set(a,"unknown",true));
|
||||
}
|
||||
''')
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
"""Exercise per-admin catalog navigation; discovery itself is checked natively."""
|
||||
from policy_harness import ROOT, body, run
|
||||
core=ROOT/'addons/core/Scripts/Game/ERR/00_Core'
|
||||
s=body(core/'ERR_AdminSession.c','protected bool ItemCatalogCommand(')
|
||||
s=body(core/'ERR_AdminSession.c','protected bool ItemCatalogCommand(')+'\n'+body(core/'ERR_AdminSession.c','protected bool VehicleCatalogCommand(')
|
||||
for a,b in [('protected ',''),('array<string> replies','Array<string>& replies'),('array<string> parts','Array<string> parts'),('ERR_ServerItemCatalog catalog','Catalog* catalog'),('GetGame().ERR_ServerItems()','GetCatalog()'),('GetGame().ERR_ItemSelections().','preferences.'),('catalog.','catalog->'),('ERR_CommandArguments.Integer','Integer')]:s=s.replace(a,b)
|
||||
s=s.replace('query.Length()', 'int(query.size())').replace('ERR_ServerVehicleCatalog catalog', 'Catalog* catalog').replace('GetGame().ERR_ServerVehicles()', 'GetCatalog()')
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
@@ -10,12 +11,12 @@ run(r'''
|
||||
#include <sstream>
|
||||
struct string;
|
||||
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;bool IsEmpty(){return this->empty();}int Count(){return this->size();}void Insert(T x){this->push_back(x);}};
|
||||
struct string:std::string{using std::string::string;string(std::string s):std::string(s){}void Split(string,Array<string>& out,bool){std::istringstream in(*this);std::string word;while(in>>word)out.push_back(word);}};
|
||||
struct Catalog{bool Contains(string a,string i){return a=="loaded-a"&&i=="known-item";}void Start(void*){}bool Find(string s){return s=="loaded-a"||s=="loaded-b";}} catalog;
|
||||
struct string:std::string{using std::string::string;string(std::string s):std::string(s){}bool IsEmpty(){return empty();}void Split(string,Array<string>& out,bool){std::istringstream in(*this);std::string word;while(in>>word)out.push_back(word);}};
|
||||
struct Catalog{bool Contains(string a,string i){return a=="loaded-a"&&i=="known-item";}bool Capable(string i,int r){return i=="known-item"&&r!=4;}void Start(void* unused=nullptr){}bool Find(string s){return s=="loaded-a"||s=="loaded-b";}} catalog;
|
||||
Catalog* GetCatalog(){return &catalog;}
|
||||
bool Integer(string s,int& n){try{size_t p;n=std::stoi(s,&p);return p==s.size();}catch(...){return false;}}
|
||||
struct Preferences{int edits=0;bool Preset(string action,string name){edits++;return action=="save"&&name=="test";}bool Set(string,string,bool){edits++;return true;}bool SetWeight(string,string,int){edits++;return true;}} preferences;
|
||||
struct Session{void* m_Mode=nullptr;string m_ItemAddon;int m_ItemPage=0;
|
||||
struct Preferences{int edits=0;bool Preset(string action,string name){edits++;return action=="save"&&name=="test";}bool Set(string,string,bool){edits++;return true;}bool SetVehicleRole(string,string,int,bool){edits++;return true;}bool SetSwitch(string,string,bool){edits++;return true;}bool SetWeight(string,string,int){edits++;return true;}} preferences;
|
||||
struct Session{void* m_Mode=nullptr;string m_ItemAddon;int m_ItemPage=0,m_ItemFilter=0;string m_ItemQuery,m_VehicleAddon,m_VehicleQuery;int m_VehiclePage=0,m_VehicleFilter=0,m_VehicleRole=0;
|
||||
''' + s + r'''
|
||||
};
|
||||
int main(){Session a,b;Array<string> replies;
|
||||
@@ -36,6 +37,21 @@ int main(){Session a,b;Array<string> replies;
|
||||
a.ItemCatalogCommand("items weight loaded-a * 100",replies);assert(preferences.edits==4);
|
||||
a.ItemCatalogCommand("items weight loaded-a known-item 0",replies);assert(preferences.edits==5);
|
||||
assert(a.m_ItemAddon=="loaded-a"&&a.m_ItemPage==0);
|
||||
a.ItemCatalogCommand("items browse loaded-a 2 2 first aid",replies);
|
||||
assert(a.m_ItemPage==2&&a.m_ItemFilter==2&&a.m_ItemQuery=="first aid");
|
||||
a.VehicleCatalogCommand("vehicles browse loaded-b 1 1 2 red",replies);
|
||||
assert(a.m_VehicleAddon=="loaded-b"&&a.m_VehiclePage==1&&a.m_VehicleQuery=="red"&&a.m_ItemAddon=="loaded-a");
|
||||
b.VehicleCatalogCommand("vehicles browse loaded-a 0 0 1",replies);assert(a.m_VehicleAddon=="loaded-b");
|
||||
a.VehicleCatalogCommand("vehicles role unknown 0 * 0",replies);assert(preferences.edits==5);
|
||||
a.VehicleCatalogCommand("vehicles role loaded-a 0 unknown-item 0",replies);assert(preferences.edits==5);
|
||||
a.ItemCatalogCommand("items browse loaded-a 2 3",replies);assert(a.m_ItemFilter==2);
|
||||
a.ItemCatalogCommand("items mod loaded-a 0",replies);assert(preferences.edits==6);
|
||||
a.ItemCatalogCommand("items mod unknown 0",replies);assert(preferences.edits==6);
|
||||
a.VehicleCatalogCommand("vehicles role loaded-a 2 known-item 0",replies);assert(preferences.edits==7);
|
||||
for(string bad:{"vehicles role loaded-a 4 known-item 1","vehicles role loaded-a 5 known-item 1","vehicles role loaded-a -1 * 1","vehicles role loaded-a 2 known-item 2"})a.VehicleCatalogCommand(bad,replies);
|
||||
assert(preferences.edits==7&&a.m_VehicleRole==2&&b.m_VehicleRole==1);
|
||||
a.VehicleCatalogCommand("vehicles browse loaded-a 0 0 5",replies);assert(a.m_VehicleRole==2);
|
||||
a.VehicleCatalogCommand("vehicles role loaded-a 3 * 0",replies);assert(preferences.edits==8);
|
||||
}
|
||||
''')
|
||||
command=body(core/'ERR_AdminSession.c','void Command(')
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
"""Production candidate and patrol crew limits for optional discovered vehicles."""
|
||||
from policy_harness import ROOT, body, run
|
||||
p=ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_ServerVehicleCatalog.c'
|
||||
s=body(p,'static bool Candidate(')+'\n'+body(p,'static bool PatrolSeats(')
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
struct string:std::string{using std::string::string;void ToLower(){std::transform(begin(),end(),begin(),[](unsigned char c){return std::tolower(c);});}bool Contains(string s){return find(s)!=npos;}bool EndsWith(string s){return size()>=s.size()&&compare(size()-s.size(),s.size(),s)==0;}};
|
||||
''' + s + r'''
|
||||
int main(){
|
||||
assert(Candidate("{guid}Prefabs/Vehicles/MyAddon_NewCar.et"));
|
||||
assert(!Candidate("Prefabs/Vehicles/Car_Base.et"));
|
||||
assert(!Candidate("Prefabs/Vehicles/Parts/Engine.et"));
|
||||
assert(!Candidate("Prefabs/Vehicles/Turrets/PKM.et"));
|
||||
assert(!Candidate("Prefabs/Vehicles/Car_wreck.et"));
|
||||
assert(!Candidate("Prefabs/Weapons/Gun.et"));
|
||||
assert(PatrolSeats(1,0,3,4));assert(PatrolSeats(1,1,0,2));assert(PatrolSeats(1,0,14,15));
|
||||
assert(!PatrolSeats(0,1,3,4));assert(!PatrolSeats(2,1,3,6));assert(!PatrolSeats(1,0,0,1));
|
||||
assert(!PatrolSeats(1,0,15,16));assert(!PatrolSeats(1,1,1,5));
|
||||
}
|
||||
''')
|
||||
print('PASS: optional vehicle discovery excludes parts/wrecks/base templates; bounded custom patrol crews accept cars and gun crews without extra bodies')
|
||||
choice=body(p,'ResourceName Choose(int role, ResourceName preferred, bool otherDefaults = true)')
|
||||
choice=choice.replace('ResourceName','string').replace('array<string>','Array<string>').replace('Math.RandomInt','RandomInt')
|
||||
choice=choice.replace('foreach (string key, ERR_VehicleEntry entry : m_Entries)', 'for (auto& [key, entry] : m_Entries)').replace('entry.', 'entry->')
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
struct string:std::string{using std::string::string;bool IsEmpty(){return empty();}};
|
||||
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;bool Contains(T v){return std::find(this->begin(),this->end(),v)!=this->end();}void Insert(T v){this->push_back(v);}bool IsEmpty(){return this->empty();}int Count(){return this->size();}};
|
||||
struct ERR_VehicleEntry{string Prefab;int Defaults;int allowed;};
|
||||
struct Catalog{std::map<string,ERR_VehicleEntry*>m_Entries;void Start(){}bool Allowed(string p,int r){auto it=m_Entries.find(p);return it!=m_Entries.end()&&(it->second->allowed&(1<<r));}int selected=0;int RandomInt(int,int count){return std::min(selected,count-1);}
|
||||
'''+choice+r'''
|
||||
};
|
||||
int main(){
|
||||
ERR_VehicleEntry original{"a",4,4},other{"b",4,4},added{"c",0,0};Catalog c;c.m_Entries={{"a",&original},{"b",&other},{"c",&added}};
|
||||
c.selected=9;assert(c.Choose(2,"a")=="a"); // Existing default distribution is preserved.
|
||||
added.allowed=4;assert(c.Choose(2,"a")=="c"); // Explicitly assigned new content participates.
|
||||
assert(c.Choose(1,"a").IsEmpty()); // Independent uses.
|
||||
original.allowed=0;added.allowed=0;assert(c.Choose(2,"a")=="b");
|
||||
assert(c.Choose(2,"a",false).IsEmpty()); // Garages keep farm defaults out of ordinary parking slots.
|
||||
other.allowed=0;assert(c.Choose(2,"a").IsEmpty()); // No disabled fallback.
|
||||
added.allowed=4;assert(c.Choose(2,"",false)=="c");
|
||||
}
|
||||
''')
|
||||
print('PASS: actual vehicle selection preserves default distribution, admits assigned additions, isolates uses, respects garage default scope and never restores disabled fallbacks')
|
||||
Reference in New Issue
Block a user