Integrate physical radio calls and finite vehicle maintenance

This commit is contained in:
2026-09-20 06:37:17 +02:00
parent 963cab50c0
commit 915476f709
152 changed files with 26426 additions and 28 deletions
+16 -4
View File
@@ -11,7 +11,7 @@ def adapt(s):
s=re.sub(r'foreach \((\w+) (\w+) : ([^)]+)\)',r'for (\1 \2 : \3)',s)
for n in ('Replication','ERR_Actors','Math','ERR_Resources','ERR_DiscoveryService','SCR_InventoryStorageManagerComponent','SCR_AIGroupUtilityComponent','ERR_MortarSupplyState'):
s=s.replace(n+'.',n+'::')
for n in ('group','member','actor','request','inventory','known','unit','m_Mode','m_Battery','mode','m_Reroll','home','sites','record'):
for n in ('group','member','actor','request','inventory','known','unit','m_Mode','m_Battery','mode','m_Reroll','home','sites','record','m_Mortars'):
s=s.replace(n+'.',n+'->')
s=s.replace('Actor.FindComponent(', 'Actor->FindComponent(').replace('SCR_InventoryStorageManagerComponent inventory', 'SCR_InventoryStorageManagerComponent* inventory').replace('SCR_AIGroupUtilityComponent utility', 'SCR_AIGroupUtilityComponent* utility')
s=s.replace('home->Definition.', 'home->Definition->').replace('Patrol.IsEmpty()', 'Patrol->IsEmpty()').replace('unit->StatusSampler.Bind(', 'unit->StatusSampler.Bind(')
@@ -78,10 +78,12 @@ struct Intel{Mode* m_Mode;Array<ERR_IntelRequest> m_Requests;Set<IEntity> m_Roll
};
struct Clock{float GetWorldTime(){return 10;}Clock* GetWorld(){return this;}}clock_;Clock* GetGame(){return &clock_;}
struct Sampler{bool Bound(){return bindings>0;}int bindings=0,strength=0;SCR_AIGroup group=nullptr;string source;void Bind(SCR_AIGroup g,string id,int n,int faction){assert(faction==1);bindings++;group=g;source=id;strength=n;}};
struct GroupService{int registrations=0;Set<SCR_AIGroup> seen;void RegisterGroup(SCR_AIGroup g){if(!seen.Contains(g)){seen.Insert(g);registrations++;}}};
struct GroupService{int registrations=0;Set<SCR_AIGroup> seen;Array<int> ids;Array<bool> fits;
void RegisterGroup(SCR_AIGroup g,int radioId=0,bool fitRadios=true){ids.Insert(radioId);fits.Insert(fitRadios);if(!seen.Contains(g)){seen.Insert(g);registrations++;}}};
struct Mortars{SCR_AIGroup owned=nullptr;bool FindGroup(SCR_AIGroup group){return group&&group==owned;}};
struct BodyService:GroupService{void RegisterGroup(SCR_AIGroup g,Sampler& s){assert(s.Bound()&&s.group==g);GroupService::RegisterGroup(g);}};
struct Unit{string HomeId,ForceSourceId;bool HomeGuard=false;float NextStatus=0;Sampler StatusSampler;int Id=0;SCR_AIGroup Group=nullptr;struct Utility* Utility=nullptr;float NextReport=0;};using ERR_HQUnit=Unit*;
struct HQ{Mode* m_Mode;GroupService m_Equipment;BodyService m_Bodies;int m_NextId=0;Array<ERR_HQUnit> m_Units;
struct HQ{Mode* m_Mode;GroupService m_Equipment;BodyService m_Bodies;int m_NextId=0;Array<ERR_HQUnit> m_Units;Mortars* m_Mortars=nullptr;
'''+hqbind+hqregister+r'''
};
Intel* intel_;HQ* hq_;
@@ -92,8 +94,10 @@ struct Ready{bool m_Stopped=false,m_Ready=false;Battery* m_Battery;int Finish(Mo
'''+handoff+r'''
}};
int main(){
Mode mode;Intel intel{&mode,{},{} };HQ hq{&mode,{},{},0,{}};intel_=&intel;hq_=&hq;Group group;
Mode mode;Intel intel{&mode,{},{} };HQ hq{&mode,{},{},0,{}};intel_=&intel;hq_=&hq;Group group;Mortars mortars;mortars.owned=&group;hq.m_Mortars=&mortars;
hq.RegisterGroup(&group);assert(hq.m_Units.Count()==1&&mode.publications==0&&Math::rolls==0);
assert(hq.m_Equipment.ids.Count()==1&&hq.m_Equipment.ids[0]==hq.m_Units[0]->Id&&hq.m_Units[0]->Id==1);
assert(!hq.m_Equipment.fits[0]); // Existing mortar readiness owns this group's radio allocation.
assert(!hq.m_Units[0]->StatusSampler.Bound()&&hq.m_Units[0]->ForceSourceId.empty()&&hq.m_Bodies.registrations==0);
Actor actors[4];Agent agents[4];for(int i=0;i<4;i++){agents[i].actor=&actors[i];group.members.Insert(&agents[i]);}
Battery battery;Ready ready{false,false,&battery};battery.shells=26;
@@ -108,6 +112,7 @@ int main(){
for(int n=0;n<5;n++){assert(ready.Finish(&mode,&group)==1);intel.RegisterGroup(&group);hq.RegisterGroup(&group);}
assert(mode.publications==1&&Math::rolls==4&&hq.m_Units.Count()==1);
assert(hq.m_Units[0]->StatusSampler.bindings==1&&hq.m_Equipment.registrations==1&&hq.m_Bodies.registrations==1);
assert(hq.m_Equipment.ids.Count()==1&&hq.m_Equipment.ids[0]==1&&!hq.m_Equipment.fits[0]);
intel.Tick(1);for(Actor& a:actors)assert(a.inventory.requests==1);
for(int n=0;n<5;n++){intel.Tick(2+n);}for(Actor& a:actors)assert(a.inventory.requests==1); // accepted asynchronous spawn never reissued
for(Actor& a:actors){a.inventory.folder=true;}for(auto request:intel.m_Requests){request->Operation->Finished=true;request->Operation->Succeeded=true;}intel.Tick(10);assert(intel.m_Requests.Count()==0);
@@ -118,6 +123,13 @@ int main(){
battery.stopped=true;Ready cancelled{false,false,&battery};assert(cancelled.Finish(&mode,&group)==-1&&mode.publications==1);battery.stopped=false;
Actor killed;Agent killedAgent{&killed};Group deathGroup;deathGroup.members.Insert(&killedAgent);intel.RegisterGroup(&deathGroup);assert(Math::rolls==5);killed.alive=false;intel.Tick(13);assert(killed.inventory.requests==0&&intel.m_Requests.Count()==0);intel.RegisterGroup(&deathGroup);assert(Math::rolls==5);
Mode neverMode;neverMode.settings.chance=0;Intel never{&neverMode,{},{}};never.RegisterGroup(&group);int after=Math::rolls;neverMode.settings.chance=100;never.RegisterGroup(&group);assert(Math::rolls==after&&never.m_Requests.Count()==0); // no reroll of original negative decision
// Ordinary physical groups receive the stable HQ ID and opt in, with or without a mortar service.
Group ordinary,withoutMortars;hq.RegisterGroup(&ordinary);
assert(hq.m_Equipment.ids.Count()==2&&hq.m_Equipment.ids[1]==hq.m_Units[1]->Id&&hq.m_Equipment.ids[1]==2&&hq.m_Equipment.fits[1]);
hq.m_Mortars=nullptr;hq.RegisterGroup(&withoutMortars);
assert(hq.m_Equipment.ids.Count()==3&&hq.m_Equipment.ids[2]==hq.m_Units[2]->Id&&hq.m_Equipment.ids[2]==3&&hq.m_Equipment.fits[2]);
// A subsequent missing mortar registry cannot reroll an already bound group's opt-out.
hq.RegisterGroup(&group);assert(hq.m_Equipment.ids.Count()==3&&!hq.m_Equipment.fits[0]&&hq.m_NextId==3);
}
''')
print('PASS: actual Intel/HQ methods and one-shot qualified readiness handoff deliver late actors once; duplicate readiness, accepted spawn, pickup/drop, death and original negative rolls do not mint assignments')