Add configurable nearby garage vehicles and fix roadblocks and fuel pumps; record Workshop 0.0.131
continuous-integration/drone/push Build is passing
continuous-integration/drone/push Build is passing
This commit is contained in:
@@ -55,7 +55,7 @@ assert 'Apply_planeReconEnabled' not in names # Saved-state toggle pair, not a
|
||||
assert len(names) == len(set(names)), 'Menu widget names must be unique'
|
||||
for key in keys:
|
||||
assert all(name in names for name in (key, 'Apply_' + key, 'Saved_' + key, 'Help_' + key))
|
||||
if key not in ('radioIntelSiteCount','droneReconCount','droneCooldownSeconds','sarkaAngerPerCar','emptyRestartHours','aiDoctrine','aiShadow','motorbikeCount','planeAltitudeMetres','planeReplacementSeconds','planeSectorSeconds','fieldCampCount','fieldHospitalCount','startMode','civilianTrafficCount','roadblockCount','hunterSpawnDistanceMetres','objectGuardCount','objectGuardEvery','objectGuardHQMode','startWeaponCount','startMagazineCount'): assert ('Default_' if key == 'viewDistanceMetres' else 'Random_') + key in names
|
||||
if key not in ('radioIntelSiteCount','droneReconCount','droneCooldownSeconds','sarkaAngerPerCar','emptyRestartHours','aiDoctrine','aiShadow','motorbikeCount','planeAltitudeMetres','planeReplacementSeconds','planeSectorSeconds','fieldCampCount','fieldHospitalCount','startMode','garageVehicleCount','garageChancePercent','garageRadiusMetres','garageSpawnIntervalSeconds','civilianTrafficCount','roadblockCount','hunterSpawnDistanceMetres','objectGuardCount','objectGuardEvery','objectGuardHQMode','startWeaponCount','startMagazineCount'): assert ('Default_' if key == 'viewDistanceMetres' else 'Random_') + key in names
|
||||
else: assert 'Random_startMode' not in names
|
||||
for name in ('Close', 'SettingsPage', 'HQPage', 'JournalPage', 'JournalText', 'JournalContent', 'JournalScroll', 'PauseJournal', 'LatestJournal', 'Status'):
|
||||
assert name in names
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
"""Execute production claim/retirement and per-update scheduling with engine doubles."""
|
||||
import re
|
||||
from policy_harness import ROOT, body, run
|
||||
p=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_GarageVehicles.c'
|
||||
def convert(s):
|
||||
s=s.replace('protected ','').replace('null','nullptr').replace('foreach (','for (')
|
||||
s=s.replace('array<vector>','Array<Vec>').replace('array<IEntity>','Array<IEntity*>').replace('array<BaseCompartmentSlot>','Array<BaseCompartmentSlot*>')
|
||||
s=s.replace('Array<Vec> observers,','Array<Vec>& observers,').replace('Array<IEntity*> actors,','Array<IEntity*>& actors,')
|
||||
for cls in ['ERR_ParkedVehicle','SCR_GarbageSystem','IEntity','BaseCompartmentSlot']:
|
||||
s=re.sub(r'\b'+cls+r' (\w+)',cls+r'* \1',s)
|
||||
for cls in ['ERR_CivilianTrafficRules','ERR_ConvoyRules','ERR_Actors','Replication','SCR_GarbageSystem']:
|
||||
s=s.replace(cls+'.',cls+'::')
|
||||
for name in ['parked','seat','m_Mode','garbage','Site','Entity']:
|
||||
s=s.replace(name+'.',name+'->')
|
||||
s=s.replace('vector.Distance','Geometry::Distance')
|
||||
s=s.replace('m_Parked[i].','m_Parked[i]->')
|
||||
return s
|
||||
methods=convert('\n'.join(body(p,x) for x in ['protected bool InUse(', 'protected void Claim(', 'void PlayerEntered(', 'protected void Maintain(', 'protected void Step(']))
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
struct Vec{float x=0,z=0;};
|
||||
namespace Geometry{float Distance(Vec a,Vec b){return std::hypot(a.x-b.x,a.z-b.z);}}
|
||||
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;int Count(){return this->size();}bool IsEmpty(){return this->empty();}void Remove(int i){this->erase(this->begin()+i);}void Clear(){this->clear();}};
|
||||
struct IEntity{Vec origin;bool player=false,hidden=true,deleted=false,seatsKnown=true;IEntity* occupant=nullptr;Vec GetOrigin(){return origin;}Vec GetYawPitchRoll(){return {};}};
|
||||
struct BaseCompartmentSlot{IEntity* occupant=nullptr;IEntity* GetOccupant(){return occupant;}};
|
||||
namespace Replication{bool authority=true;bool IsServer(){return authority;}}
|
||||
namespace ERR_Actors{bool Player(IEntity* e){return e&&e->player;}void DeleteSpawned(IEntity* e){assert(!e->occupant);e->deleted=true;}}
|
||||
namespace ERR_ConvoyRules{
|
||||
bool PartOf(IEntity* e,IEntity* root){return e==root;}
|
||||
bool CollectSeats(IEntity* e,Array<BaseCompartmentSlot*>& a){static BaseCompartmentSlot seat;seat.occupant=e->occupant;a.push_back(&seat);return e->seatsKnown;}
|
||||
}
|
||||
namespace ERR_CivilianTrafficRules{
|
||||
bool Far(Vec p,Array<Vec>& o,float distance){for(auto q:o)if(Geometry::Distance(p,q)<distance)return false;return true;}
|
||||
bool CleanupDue(float now,float since){return since>=0&&now-since>=60000;}
|
||||
}
|
||||
struct SCR_GarbageSystem{static SCR_GarbageSystem* GetByEntityWorld(IEntity*){static SCR_GarbageSystem g;return &g;}void UpdateBlacklist(IEntity*,bool){}};
|
||||
struct SiteData{bool Consumed=false;};
|
||||
struct ERR_ParkedVehicle{IEntity* Entity=nullptr;SiteData* Site=nullptr;Vec Origin;float HiddenSince=-1;};
|
||||
struct Mode{int owned=0;void Own(IEntity*){owned++;}};
|
||||
struct Service{
|
||||
Mode mode;Mode* m_Mode=&mode;bool m_Stopped=false,running=true,known=true;
|
||||
int m_Claimed=0,m_MaintainCursor=0,m_Work=0,m_Pending=0,m_CleanupRadius=1400;
|
||||
float clock=0,m_NextStep=0;int discovers=0,plans=0,pending=0,hiddenCalls=0;
|
||||
Array<ERR_ParkedVehicle*> m_Parked;Array<Vec> views;Array<IEntity*> actors;
|
||||
bool Running(){return running;}float Now(){return clock;}
|
||||
bool Observers(Array<Vec>& o,Array<IEntity*>& a){o=views;a=actors;return known;}
|
||||
bool Hidden(Vec,Vec,Array<IEntity*>& a,IEntity* e){hiddenCalls++;return e->hidden;}
|
||||
void Discover(Array<Vec>&,float){discovers++;}void Plan(Array<Vec>&,Array<IEntity*>&,float){plans++;}
|
||||
void Pending(Array<Vec>&,Array<IEntity*>&){pending++;}
|
||||
''' + methods.replace('m_Pending = nullptr','m_Pending = 0') + r'''
|
||||
};
|
||||
int main(){
|
||||
Array<Vec> far={{2000,0}},near={{100,0}};Array<IEntity*> actors;
|
||||
IEntity car,player;player.player=true;SiteData site;ERR_ParkedVehicle record;record.Entity=&car;record.Site=&site;
|
||||
Service s;s.m_Parked={&record};
|
||||
s.Maintain(far,actors,100,true);assert(record.HiddenSince==100&&!car.deleted);
|
||||
s.Maintain(far,actors,60099,true);assert(!car.deleted);
|
||||
s.Maintain(near,actors,60100,true);assert(record.HiddenSince<0&&!car.deleted);
|
||||
s.Maintain(far,actors,70000,true);car.hidden=false;s.Maintain(far,actors,140000,true);assert(!car.deleted&&record.HiddenSince<0);
|
||||
car.hidden=true;s.Maintain(far,actors,150000,true);s.Maintain(far,actors,220000,false);assert(!car.deleted&&record.HiddenSince<0);
|
||||
s.Maintain(far,actors,230000,true);s.Maintain(far,actors,290000,true);assert(car.deleted&&s.m_Parked.empty()&&!site.Consumed);
|
||||
car.deleted=false;s.m_Parked={&record};s.PlayerEntered(&player,&car);assert(site.Consumed&&s.m_Claimed==1&&s.m_Parked.empty()&&s.mode.owned==1);
|
||||
s.PlayerEntered(&player,&car);s.Maintain(far,actors,500000,true);assert(s.m_Claimed==1&&!car.deleted);
|
||||
SiteData used;record.Site=&used;s.m_Parked={&record};car.origin={4,0};s.Maintain(far,actors,600000,true);assert(used.Consumed&&!car.deleted&&s.m_Parked.empty());
|
||||
SiteData occupied;record.Site=&occupied;car.origin={0,0};car.occupant=&player;s.m_Parked={&record};s.Maintain(far,actors,700000,true);assert(occupied.Consumed&&!car.deleted);car.occupant=nullptr;
|
||||
Service tick;tick.Step();assert(tick.discovers==1&&tick.plans==0);tick.Step();assert(tick.discovers==1); // no same-frame catch-up
|
||||
tick.clock=250;tick.Step();assert(tick.plans==1);tick.clock=500;tick.Step();assert(tick.discovers==1&&tick.plans==1);
|
||||
tick.m_Pending=1;tick.clock=750;tick.Step();assert(tick.pending==1&&tick.plans==1&&tick.discovers==1);
|
||||
tick.known=false;tick.clock=1000;record.HiddenSince=100;tick.m_Parked={&record};tick.Step();assert(tick.m_Pending==0&&record.HiddenSince<0&&tick.pending==1);
|
||||
tick.m_Stopped=true;tick.clock=1250;tick.Step();assert(tick.plans==1&&tick.discovers==1);
|
||||
Service client;Replication::authority=false;client.Step();assert(client.discovers==0&&client.plans==0);
|
||||
}
|
||||
''')
|
||||
print('PASS: permanent entry/movement/occupancy claims; far hidden grace cleanup; proximity/visibility/unknown-view resets; one staged operation per update, no same-frame catch-up, stop and client guards')
|
||||
|
||||
# The current round takes one snapshot of the saved menu values. No polling
|
||||
# should silently apply next-round changes or restart disabled/client services.
|
||||
start=body(p,'void Start(').replace('Replication.', 'Replication::').replace('Math.Max', 'std::max')
|
||||
start=start.replace('m_Mode.Settings().Get(', 'settings.Get(').replace('m_Bikes.Find()', 'bikeLookups++')
|
||||
start=start.replace('GetGame().GetCallqueue().CallLater(Step, 250, true)', 'queueCalls++')
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
namespace Replication{bool authority=true;bool IsServer(){return authority;}}
|
||||
struct Settings{std::map<std::string,int> values={{"garageVehicleCount",16},{"garageChancePercent",70},{"garageRadiusMetres",1000},{"garageSpawnIntervalSeconds",5}};int Get(std::string k){return values.at(k);}};
|
||||
struct Service{
|
||||
Settings settings;bool m_Started=false,m_Stopped=false;
|
||||
int m_Requested=0,m_GarageChance=0,m_SpawnRadius=0,m_SpawnInterval=0,m_CleanupRadius=0,bikeLookups=0,queueCalls=0;
|
||||
void Stop(){m_Stopped=true;}
|
||||
''' + start + r'''
|
||||
};
|
||||
int main(){
|
||||
Service normal;normal.Start();assert(normal.m_Requested==16&&normal.m_GarageChance==70&&normal.m_SpawnRadius==1000&&normal.m_SpawnInterval==5000&&normal.m_CleanupRadius==1400&&normal.queueCalls==1);
|
||||
normal.settings.values["garageRadiusMetres"]=1500;normal.settings.values["garageSpawnIntervalSeconds"]=60;normal.Start();assert(normal.m_SpawnRadius==1000&&normal.m_SpawnInterval==5000&&normal.queueCalls==1);
|
||||
Service custom;custom.settings.values={{"garageVehicleCount",24},{"garageChancePercent",100},{"garageRadiusMetres",1500},{"garageSpawnIntervalSeconds",60}};custom.Start();assert(custom.m_Requested==24&&custom.m_GarageChance==100&&custom.m_SpawnRadius==1500&&custom.m_SpawnInterval==60000&&custom.m_CleanupRadius==1900);
|
||||
for(auto key:{"garageVehicleCount","garageChancePercent"}){Service disabled;disabled.settings.values[key]=0;disabled.Start();assert(disabled.m_Stopped&&disabled.queueCalls==0&&disabled.bikeLookups==0);}
|
||||
Service small;small.settings.values["garageRadiusMetres"]=250;small.Start();assert(small.m_CleanupRadius==1400);
|
||||
Service stopped;stopped.m_Stopped=true;stopped.Start();assert(stopped.queueCalls==0);
|
||||
Service client;Replication::authority=false;client.Start();assert(!client.m_Started&&client.queueCalls==0&&client.bikeLookups==0);
|
||||
}
|
||||
''')
|
||||
print('PASS: saved garage menu settings configure the round once; unit conversion and cleanup radius scale correctly; count/chance zero disable startup; clients and stopped rounds do no work')
|
||||
@@ -0,0 +1,61 @@
|
||||
"""Exercise production placement vetoes for players, spectators and editor cameras."""
|
||||
import re
|
||||
from policy_harness import ROOT, body, run
|
||||
p=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_GarageVehicles.c'
|
||||
s=body(p,'protected bool Observers(').replace('protected ','').replace('array<vector>', 'Array<Vec>').replace('array<IEntity>', 'Array<IEntity*>').replace('array<int> ids = {};','std::vector<int> ids;').replace('foreach (int id : ids)','for (int id : ids)').replace('null','nullptr')
|
||||
s=s.replace('Array<Vec> positions','Array<Vec>& positions').replace('Array<IEntity*> actors','Array<IEntity*>& actors')
|
||||
s=s.replace('ERR_ForceRules.', 'ERR_ForceRules::').replace('ERR_ObserverReason.NONE','ERR_ObserverReason::NONE')
|
||||
for cls in ['SCR_PlayerController','SCR_EditorManagerEntity','SCR_EditorManagerCore','IEntity']:
|
||||
s=s.replace(cls+'.',cls+'::')
|
||||
s=re.sub(r'\b'+cls+r' (\w+)',cls+r'* \1',s)
|
||||
s=s.replace('SCR_EditorManagerCore::GetInstance(SCR_EditorManagerCore)','SCR_EditorManagerCore::GetInstance()')
|
||||
s=s.replace('SCR_EditorManagerEntity* editor;', 'SCR_EditorManagerEntity* editor = nullptr;')
|
||||
for prefix in ['GetGame()','GetPlayerManager()','controller','body','watched','editors','editor']:
|
||||
s=s.replace(prefix+'.',prefix+'->')
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
struct Vec{float x=0,z=0;};
|
||||
template<class T>struct Array:std::vector<T>{void Clear(){this->clear();}void Insert(T t){this->push_back(t);}};
|
||||
namespace ERR_ForceRules{bool FiniteVector(Vec v){return std::isfinite(v.x)&&std::isfinite(v.z);}}
|
||||
namespace Geometry{float DistanceXZ(Vec a, Vec b){return std::hypot(a.x-b.x,a.z-b.z);}}
|
||||
namespace ERR_ObserverReason{const int NONE=0;}
|
||||
struct IEntity{Vec p;Vec GetOrigin(){return p;}};
|
||||
struct SCR_PlayerController{
|
||||
IEntity* body=nullptr;IEntity* watched=nullptr;int state=0;
|
||||
IEntity* GetControlledEntity(){return body;}IEntity* ERR_ObservedEntity(){return watched;}int ERR_ObserverState(){return state;}
|
||||
static SCR_PlayerController* Cast(SCR_PlayerController* p){return p;}
|
||||
};
|
||||
struct SCR_EditorManagerEntity{bool opened=false;bool IsOpened(){return opened;}};
|
||||
SCR_EditorManagerEntity editor;
|
||||
struct SCR_EditorManagerCore{
|
||||
static SCR_EditorManagerCore* GetInstance(){static SCR_EditorManagerCore c;return &c;}
|
||||
static SCR_EditorManagerCore* Cast(SCR_EditorManagerCore* p){return p;}
|
||||
SCR_EditorManagerEntity* GetEditorManager(int){return &editor;}
|
||||
};
|
||||
struct Manager{
|
||||
std::vector<SCR_PlayerController*> clients;
|
||||
void GetPlayers(std::vector<int>& ids){for(unsigned i=0;i<clients.size();i++)ids.push_back(i);}
|
||||
SCR_PlayerController* GetPlayerController(int id){return clients[id];}
|
||||
};
|
||||
struct Game{Manager players;Manager* GetPlayerManager(){return &players;}} game;
|
||||
Game* GetGame(){return &game;}
|
||||
''' + s + r'''
|
||||
int main(){
|
||||
Array<Vec> positions;Array<IEntity*> actors;
|
||||
assert(Observers(positions,actors)&&positions.empty());
|
||||
game.players.clients={nullptr};assert(!Observers(positions,actors));
|
||||
SCR_PlayerController c;game.players.clients={&c};assert(!Observers(positions,actors));
|
||||
IEntity player{{10,0}}, watched{{500,0}};c.body=&player;
|
||||
assert(Observers(positions,actors)&&positions.size()==1&&actors[0]==&player);
|
||||
editor.opened=true;assert(!Observers(positions,actors));editor.opened=false;
|
||||
c.state=1;assert(!Observers(positions,actors));c.watched=&watched;
|
||||
assert(Observers(positions,actors)&&positions.size()==2&&actors[1]==&watched);
|
||||
c.body=nullptr;assert(Observers(positions,actors)&&positions.size()==1&&actors[0]==&watched);
|
||||
watched.p={NAN,0};assert(!Observers(positions,actors));watched.p={500,0};
|
||||
c.state=0;assert(!Observers(positions,actors));
|
||||
|
||||
}
|
||||
''')
|
||||
print('PASS: bodies and spectator targets are collected; unknown, nonfinite and editor views veto spawning/cleanup; no connected players yields an empty known view')
|
||||
@@ -0,0 +1,38 @@
|
||||
"""Run production garage filtering and vehicle-fit rules against unsafe inputs."""
|
||||
from policy_harness import ROOT, body, run
|
||||
p = ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_GarageVehicles.c'
|
||||
rules = '\n'.join(body(p, s) for s in ('static bool Garage(', 'static bool Envelope('))
|
||||
rules = rules.replace('vector ', 'Vec ').replace('ERR_ForceRules.', 'ERR_ForceRules::')
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cctype>
|
||||
#include <limits>
|
||||
#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 EndsWith(const std::string& suffix){return size()>=suffix.size() && compare(size()-suffix.size(),suffix.size(),suffix)==0;}
|
||||
};
|
||||
struct Vec {float v[3];float operator[](int i)const{return v[i];}};
|
||||
namespace ERR_ForceRules {bool FiniteVector(Vec v){return std::isfinite(v[0])&&std::isfinite(v[1])&&std::isfinite(v[2]);}}
|
||||
''' + rules + r'''
|
||||
int main(){
|
||||
assert(Garage("{GUID}Prefabs/Structures/Houses/Garage/Garage_E_01/Garage_E_01.et"));
|
||||
assert(Garage("Prefabs/Structures/Houses/Village/HouseAddon_Garage_E_01/HouseAddon_Garage_E_01.et"));
|
||||
assert(!Garage("Prefabs/Structures/Houses/Garage/Garage_E_01/Dst/Garage_E_01_Ruin.et"));
|
||||
assert(!Garage("Prefabs/Structures/Houses/Garage/Garage_E_01/Furniture/Garage_E_01_furniture_01.et"));
|
||||
assert(!Garage("Prefabs/Structures/BuildingParts/Doors/Door_Garage_E_01_A_LEFT_EXT_COV_B.et"));
|
||||
assert(!Garage("Prefabs/Structures/Military/Houses/GarageMilitary_E_01/GarageMilitary_E_01.et"));
|
||||
assert(Envelope({{-1,-0.2,-2.3}},{{1,1.8,2.3}}));
|
||||
assert(!Envelope({{-1.06f,0,-2}},{{1,1.8,2}}));
|
||||
assert(!Envelope({{-1,0,-2.5}},{{1,1.8,2}}));
|
||||
assert(!Envelope({{-1,0,-2}},{{1,2.1,2}}));
|
||||
assert(!Envelope({{-1,-0.4,-2}},{{1,1.8,2}}));
|
||||
assert(!Envelope({{1,0,2}},{{-1,1.8,-2}}));
|
||||
assert(!Envelope({{NAN,0,-2}},{{1,1.8,2}}));
|
||||
assert(!Envelope({{-1,0,-2}},{{1,INFINITY,2}}));
|
||||
}
|
||||
''')
|
||||
print('PASS: native garage families only; rubble/furniture/military garages rejected; oversized and invalid vehicle bounds rejected')
|
||||
@@ -14,7 +14,7 @@ def tr(s):
|
||||
s=re.sub(r'\b'+t+r' (\w+)',t+r'* \1',s)
|
||||
for n in ('m_Mode','mode','hq','unit','Group','agent','record','Recovery','Tactics','CloseContact','entity'):
|
||||
s=re.sub(r'\b'+n+r'\.',n+'->',s)
|
||||
s=s.replace('Forces().','Forces()->').replace('Players().','Players()->').replace('Command().','Command()->').replace('Hunters().','Hunters()->').replace('CivilianTraffic().','CivilianTraffic()->')
|
||||
s=s.replace('Forces().','Forces()->').replace('Players().','Players()->').replace('Command().','Command()->').replace('Hunters().','Hunters()->').replace('CivilianTraffic().','CivilianTraffic()->').replace('GarageVehicles().','GarageVehicles()->')
|
||||
for n in ('Replication','ERR_GameMode','ERR_ConvoyRules','SCR_EntityHelper'):s=s.replace(n+'.',n+'::')
|
||||
s=s.replace('ERR_HQ* hq;','ERR_HQ* hq=nullptr;').replace('int depth;','int depth=0;')
|
||||
return s
|
||||
@@ -34,8 +34,8 @@ struct BaseCompartmentManagerComponent{};
|
||||
struct Service;struct ERR_HQ;
|
||||
struct Roster{IEntity*current=nullptr;void*CurrentActor(IEntity*a){return a&&a==current?this:nullptr;}};
|
||||
struct ERR_ForceRecord{bool Pinned=true;};struct Ledger{ERR_ForceRecord r;ERR_ForceRecord*FindGroup(GroupType*){return &r;}};
|
||||
struct Traffic{void PlayerEntered(IEntity*,IEntity*){}};
|
||||
struct ERR_GameMode{Traffic* CivilianTraffic(){return nullptr;}inline static ERR_GameMode*current=nullptr;bool active=true;ERR_HQ*command=nullptr;Roster roster;Ledger ledger;static ERR_GameMode*Get(){return current;}bool Active(){return active;}ERR_HQ*Command(){return command;}Roster*Players(){return &roster;}Ledger*Forces(){return &ledger;}};
|
||||
struct Traffic{int calls=0;IEntity* actor=nullptr;IEntity* target=nullptr;void PlayerEntered(IEntity* a,IEntity* t){calls++;actor=a;target=t;}};
|
||||
struct ERR_GameMode{Traffic garage;Traffic* GarageVehicles(){return &garage;}Traffic* CivilianTraffic(){return nullptr;}inline static ERR_GameMode*current=nullptr;bool active=true;ERR_HQ*command=nullptr;Roster roster;Ledger ledger;static ERR_GameMode*Get(){return current;}bool Active(){return active;}ERR_HQ*Command(){return command;}Roster*Players(){return &roster;}Ledger*Forces(){return &ledger;}};
|
||||
namespace Replication{bool server=true;bool IsServer(){return server;}}
|
||||
namespace ERR_ConvoyRules{bool occupied=false;bool Captured(IEntity*,GroupType*){return occupied;}
|
||||
'''+part+r'''
|
||||
@@ -65,6 +65,7 @@ int main(){
|
||||
// Native occupancy is not visible yet; super removes current identity to model
|
||||
// disconnect/body loss before next one-second poll. Event must win that gap.
|
||||
c.Enter();assert(c.unit.PlayerCaptureObserved&&c.unit.Released);assert(c.access.nativeCalls==1);
|
||||
assert(c.mode.garage.calls==1&&c.mode.garage.actor==&c.player&&c.mode.garage.target==&c.turret);
|
||||
assert(c.hq.unregistered==1&&c.hq.retained==0&&c.hq.recoveries==0);
|
||||
assert(c.close.stops==1&&c.recovery.stops==1&&c.unit.Downlink==nullptr&&c.unit.orders==0&&c.unit.clears==1);
|
||||
assert(!c.mode.ledger.r.Pinned&&c.unit.Entity==&c.vehicle&&c.unit.Group==&c.group);
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
"""Execute the shipped checkpoint layout across road widths and rotations."""
|
||||
from policy_harness import ROOT, body, run
|
||||
import re
|
||||
p=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_Roadblocks.c'
|
||||
s='\n'.join(body(p,x) for x in ('protected void Layout(', 'protected void AddProp('))
|
||||
s='\n'.join(body(p,x) for x in ('protected void Layout(', 'protected void AddProp(', 'protected vector GuardPosition('))
|
||||
s=s.replace('protected ', '').replace('out ', '').replace('array<ref ERR_RoadblockProp> covers', 'Array<Prop*>& covers')
|
||||
s=s.replace('ResourceName ', 'string ').replace('vector ', 'Vec ').replace('ERR_Actors.Ground','Ground').replace('new ERR_RoadblockProp','new Prop')
|
||||
shoulder = re.search(r'm_Shoulder = ([^;]+);', body(p, 'protected bool Candidate('))[1]
|
||||
shoulder = shoulder.replace('road.GetWidth()', 'width').replace('Math.Max', 'std::max')
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
@@ -27,24 +30,28 @@ int main(){
|
||||
for(double width:{4.,6.,10.,16.})for(int degrees=0;degrees<360;degrees+=15){
|
||||
Checkpoint l;double angle=degrees*3.141592653589793/180;
|
||||
l.m_Forward={std::sin(angle),0,std::cos(angle)};l.m_Right={std::cos(angle),0,-std::sin(angle)};
|
||||
l.m_Shoulder=std::max(width*.5+1.5,4.);Array<Prop*>props;l.Layout(props);assert(props.size()==18);
|
||||
l.m_Shoulder='''+shoulder+r''';Array<Prop*>props;l.Layout(props);assert(props.size()==8);
|
||||
std::map<string,int>counts;std::vector<double>barriers;
|
||||
for(auto*p:props){counts[p->Prefab]++;
|
||||
Vec delta=p->Position-l.m_Centre;double across=dot(delta,l.m_Right),along=dot(delta,l.m_Forward);
|
||||
// Whole footprints (not just origins) preserve a four-metre straight passage.
|
||||
double reach=std::abs(dot(p->Right,l.m_Right))*p->HalfWidth+std::abs(dot(p->Forward,l.m_Right))*p->HalfDepth;
|
||||
assert(std::abs(across)-reach>=2.-1e-8);
|
||||
// Bound the compact footprint, including both road shoulders.
|
||||
assert(std::abs(across)+reach <= std::max(width*.5+1.2,4.5)+1e-6);
|
||||
double depth=std::abs(dot(p->Right,l.m_Forward))*p->HalfWidth+std::abs(dot(p->Forward,l.m_Forward))*p->HalfDepth;
|
||||
assert(std::abs(along)+depth <= 8.9+1e-6);
|
||||
if(p->Prefab=="barrier")barriers.push_back(along);
|
||||
if(p->Prefab=="sign")assert(dot(p->Forward,l.m_Forward)*along>0); // both approaches face outward
|
||||
// Guard starting volumes cannot intersect any prop in the planned post.
|
||||
for(int side:{-1,1})for(int row:{-1,1}){
|
||||
Vec guard=l.m_Centre+l.m_Right*(side*(l.m_Shoulder+2.5))+l.m_Forward*(side*(row-1.5));
|
||||
Vec guard=l.GuardPosition(side,row);
|
||||
Vec rel=guard-p->Position;
|
||||
assert(std::abs(dot(rel,p->Right))>p->HalfWidth+.6 || std::abs(dot(rel,p->Forward))>p->HalfDepth+.6);
|
||||
}
|
||||
}
|
||||
assert(counts["cover"]==8&&counts["barrier"]==4&&counts["obstacle"]==2&&counts["sign"]==2&&counts["table"]==1&&counts["shelter"]==1);
|
||||
assert(barriers.size()==4&&std::abs(barriers[0]-barriers[2])>15.9);
|
||||
assert(counts["cover"]==4&&counts["barrier"]==2&&counts["sign"]==2&&counts.size()==3);
|
||||
assert(barriers.size()==2&&std::abs(barriers[0]-barriers[1])>11.9);
|
||||
// No physical prop footprint intersects another (separating axes).
|
||||
for(size_t i=0;i<props.size();i++)for(size_t j=i+1;j<props.size();j++){
|
||||
auto*a=props[i];auto*b=props[j];bool separate=false;
|
||||
|
||||
@@ -5,7 +5,7 @@ from policy_harness import ROOT,body,run
|
||||
spec=importlib.util.spec_from_file_location('import_settings',BASE/'tools/import_settings.py')
|
||||
m=importlib.util.module_from_spec(spec);spec.loader.exec_module(m)
|
||||
result,deferred=m.merge({'configVersion':1,'reportIntervalSeconds':20.0,'supportSquadLimit':2,'responseRadiusMetres':1500,'convoyCount':3},{'aiIntelChancePercent':0,'intelRevealChancePercent':100})
|
||||
assert result==dict(telemetryEnabled=1,droneReconCount=0,droneCooldownSeconds=300,aiDoctrine=0,aiShadow=0,planeAltitudeMetres=250,planeReplacementSeconds=60,planeSectorSeconds=120,planeReconEnabled=1,roadblockCount=4,civilianTrafficCount=3,startMode=0,keepWeaponOnUnconscious=1,selfEpinephrine=0,restartOnEmpty=1,emptyRestartHours=5,reportIntervalSeconds=20,supportSquadLimit=2,responseRadiusMetres=1500,aiIntelChancePercent=0,intelRevealChancePercent=100,radioIntelSiteCount=5,showAllSites=0,radioDefenceSeconds=180,startTimeMinutes=840,timeMultiplier=1,startDateCode=0,weatherProfile=0,viewDistanceMetres=0,automaticEquipmentChancePercent=25,antiAirChancePercent=7,antiPersonnelMineChancePercent=60,automaticConvoyEnabled=1,convoyCount=3,heliTimerMinutes=15)
|
||||
assert result==dict(telemetryEnabled=1,droneReconCount=0,droneCooldownSeconds=300,aiDoctrine=0,aiShadow=0,planeAltitudeMetres=250,planeReplacementSeconds=60,planeSectorSeconds=120,planeReconEnabled=1,roadblockCount=4,civilianTrafficCount=3,garageVehicleCount=16,garageChancePercent=70,garageRadiusMetres=1000,garageSpawnIntervalSeconds=5,startMode=0,keepWeaponOnUnconscious=1,selfEpinephrine=0,restartOnEmpty=1,emptyRestartHours=5,reportIntervalSeconds=20,supportSquadLimit=2,responseRadiusMetres=1500,aiIntelChancePercent=0,intelRevealChancePercent=100,radioIntelSiteCount=5,showAllSites=0,radioDefenceSeconds=180,startTimeMinutes=840,timeMultiplier=1,startDateCode=0,weatherProfile=0,viewDistanceMetres=0,automaticEquipmentChancePercent=25,antiAirChancePercent=7,antiPersonnelMineChancePercent=60,automaticConvoyEnabled=1,convoyCount=3,heliTimerMinutes=15)
|
||||
assert deferred==[]
|
||||
for count in (0,1,3,20):
|
||||
values,notes=m.merge(ai={'convoyCount':count})
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
"""Execute station handover with native manager/world boundaries doubled."""
|
||||
from policy_harness import ROOT, body, run
|
||||
p=ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainSupportStations.c'
|
||||
def cpp(signature):
|
||||
s=body(p,signature)
|
||||
for a,b in [('int restored;', 'int restored=0;'),('protected ',''),('override ',''),('SCR_SupportStationManagerComponent manager','Manager* manager'),('SCR_SupportStationManagerComponent original','Manager* original'),('array<SCR_BaseSupportStationComponent> stations','Array<Station*>& stations'),('array<SCR_BaseSupportStationComponent> registered','Array<Station*>* registered'),('foreach (SCR_BaseSupportStationComponent station : registered)','for (Station* station : *registered)'),('foreach (SCR_BaseSupportStationComponent station : m_ERRTerrainSupportStations)','for (Station* station : m_ERRTerrainSupportStations)'),('IEntity owner','Owner* owner'),('station.', 'station->'),('manager.', 'manager->'),('original.', 'original->'),('owner.', 'owner->'),('GetGame().', 'GetGame()->'),('EDamageState.DESTROYED','2'),('super.', 'Base::')]: s=s.replace(a,b)
|
||||
return s
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
struct Station;struct Manager;struct Game;
|
||||
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;void Clear(){this->clear();}void Insert(T v){this->push_back(v);}bool Contains(T v){return std::find(this->begin(),this->end(),v)!=this->end();}};
|
||||
struct World{} world,otherWorld;
|
||||
struct Owner{World* world=&::world;World* GetWorld(){return world;}};
|
||||
struct Base{int cleaned=0;void OnBeforeWorldCleanup(){cleaned++;}void ERR_TerrainStationRestored(Manager*){}};
|
||||
template<class...T>void PrintFormat(T...){}
|
||||
struct Game:Base{Array<Station*> m_ERRTerrainSupportStations;World* GetWorld(){return &world;}
|
||||
void ERR_CaptureTerrainSupportStations(Manager*);
|
||||
void ERR_RestoreTerrainSupportStations(Manager*);
|
||||
void OnBeforeWorldCleanup();
|
||||
} game;
|
||||
Game* current=&game;Game* GetGame(){return current;}
|
||||
struct Station:Base{Owner own;Owner* owner=&own;int m_eCurrentDamageState=0;bool ranged=true;
|
||||
bool m_ERRItemStationInitialized=true;Manager* m_ERRItemStationManager=nullptr;
|
||||
Owner* GetOwner(){return owner;}bool UsesRange(){return ranged;}
|
||||
'''+cpp('bool ERR_CanRestoreTerrainStation(')+cpp('override void ERR_TerrainStationRestored(')+r'''
|
||||
};
|
||||
struct Map{std::vector<Array<Station*>*> lists;int Count(){return lists.size();}Array<Station*>* GetElement(int i){return lists[i];}};
|
||||
struct Manager{Map m_mSupportStations;Array<Station*> accepted;void AddSupportStation(Station* s){if(!accepted.Contains(s))accepted.Insert(s);}
|
||||
'''+cpp('void ERR_CollectTerrainStations(')+r'''
|
||||
};
|
||||
'''+cpp('void ERR_CaptureTerrainSupportStations(').replace('void ERR_', 'void Game::ERR_',1)+cpp('void ERR_RestoreTerrainSupportStations(').replace('void ERR_', 'void Game::ERR_',1)+cpp('override protected void OnBeforeWorldCleanup(').replace('void OnBefore','void Game::OnBefore',1)+r'''
|
||||
int main(){
|
||||
Station pump,item,destroyed,deleted,offWorld,nonRanged;
|
||||
Manager old,replacement;
|
||||
destroyed.m_eCurrentDamageState=2;deleted.owner=nullptr;offWorld.own.world=&otherWorld;nonRanged.ranged=false;
|
||||
pump.m_ERRItemStationManager=&old;item.m_ERRItemStationManager=&old;
|
||||
Array<Station*> fuel{&pump,&destroyed,nullptr,&deleted,&offWorld};
|
||||
Array<Station*> repair{&item,&pump,&nonRanged};old.m_mSupportStations.lists={&fuel,&repair,nullptr};
|
||||
game.ERR_CaptureTerrainSupportStations(&old);assert(game.m_ERRTerrainSupportStations.size()==6);
|
||||
// The removed registry can disappear; the captured list is independent.
|
||||
fuel.Clear();repair.Clear();
|
||||
replacement.AddSupportStation(&item); // initialization races must not duplicate entries
|
||||
game.ERR_RestoreTerrainSupportStations(&replacement);
|
||||
assert(replacement.accepted.size()==2&&replacement.accepted.Contains(&pump)&&replacement.accepted.Contains(&item));
|
||||
assert(pump.m_ERRItemStationManager==&replacement&&item.m_ERRItemStationManager==&replacement);
|
||||
assert(game.m_ERRTerrainSupportStations.empty());
|
||||
game.ERR_RestoreTerrainSupportStations(&replacement);assert(replacement.accepted.size()==2);
|
||||
game.m_ERRTerrainSupportStations.Insert(&pump);game.ERR_RestoreTerrainSupportStations(nullptr);assert(game.m_ERRTerrainSupportStations.size()==1);
|
||||
game.OnBeforeWorldCleanup();assert(game.m_ERRTerrainSupportStations.empty()&&game.cleaned==1);
|
||||
game.m_ERRTerrainSupportStations.Insert(&pump);game.ERR_CaptureTerrainSupportStations(nullptr);assert(game.m_ERRTerrainSupportStations.empty());
|
||||
current=nullptr;assert(!pump.ERR_CanRestoreTerrainStation());current=&game;
|
||||
}
|
||||
''')
|
||||
core=p.parent
|
||||
server=body(core/'ERR_TerrainTakeover.c','protected void ERR_StopOriginalTerrainMode(') if 'protected void ERR_StopOriginalTerrainMode(' in (core/'ERR_TerrainTakeover.c').read_text() else (core/'ERR_TerrainTakeover.c').read_text()
|
||||
assert server.index('ERR_CaptureTerrainSupportStations(')<server.index('m_ERRTerrainOriginal.OnGameEnd()')
|
||||
client=body(core/'ERR_TerrainTakeoverClient.c','void ERR_BeginClientTerrainTakeover(')
|
||||
assert client.index('ERR_CaptureTerrainSupportStations(')<client.index('mode.OnGameEnd()')
|
||||
init=body(p,'override void OnPostInit(')
|
||||
assert 'ERR_TerrainTakeoverMode.Cast(owner) && GetInstance() == this' in init
|
||||
print('PASS: support registrations survive takeover on both peers, deduplicate, skip invalid stations, update item cleanup and clear on world teardown')
|
||||
Reference in New Issue
Block a user