107 lines
8.5 KiB
Python
107 lines
8.5 KiB
Python
"""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')
|