Files

84 lines
7.2 KiB
Python

"""Execute production stuck-route timing and original-crew dismount selection."""
from policy_harness import ROOT, body, run
p=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HunterService.c'
s=body(p,'protected void ObserveMovement(')
s=s.replace('protected ', '').replace('ERR_HunterVehicle unit','Unit* unit').replace('unit.', 'unit->')
for name in ('Entity','Waypoint','Group'):
s=s.replace(name+'.',name+'->')
s=s.replace('Physics physics','Physics* physics').replace('physics.','physics->').replace('GetPhysics().','GetPhysics()->')
s=s.replace('vector.DistanceXZ','Vec::DistanceXZ').replace('vector ', 'Vec ').replace('ERR_HunterCombat.Active','Active').replace('ERR_HQ.Event','Event').replace('string.Format','Format')
s=s.replace('AIWaypoint->Cast(ERR_Actors.Spawn(ERR_Resources.MoveWaypoint(), retryDestination))','Spawn(retryDestination)').replace('unit->Repaths.ToString()', 'std::to_string(unit->Repaths)')
run(r'''
#include <cassert>
#include <string>
#include <cmath>
struct Vec{float x=0;float Length(){return std::abs(x);}static float DistanceXZ(Vec a,Vec b){return std::abs(a.x-b.x);}};
struct Physics{float speed=0;Vec GetVelocity(){return {speed};}};
struct Entity{Vec position;Physics physics;bool hasPhysics=true;Vec GetOrigin(){return position;}Physics* GetPhysics(){return hasPhysics?&physics:nullptr;}};
struct Group{bool contact=false;int orders=0;void AddWaypoint(void*){orders++;}};
struct Waypoint{Vec position;bool arrived=false;Vec GetOrigin(){return position;}bool IsWithinCompletionRadius(Group*){return arrived;}void SetCompletionRadius(int value){assert(value==50);}};
struct Unit{Entity* Entity=nullptr;Group* Group=nullptr;Waypoint* Waypoint=nullptr;float ProgressAt=0,NextMotionLog=0;Vec ProgressPosition,Destination{9999};int Repaths=0,clears=0;bool Released=false;std::string Source="bike";void ClearOrder(){clears++;Waypoint=nullptr;}};
bool Active(Group* g){return g->contact;}void Event(std::string,std::string){}template<class...T>std::string Format(T...){return {};}
Waypoint replacement;int spawned=0;Waypoint* Spawn(Vec dest){spawned++;replacement.position=dest;return &replacement;}
int abandoned=0;void AbandonStuckVehicle(Unit* unit){abandoned++;unit->Released=true;}
'''.replace('Entity* Entity','struct Entity* Entity').replace('Group* Group','struct Group* Group').replace('Waypoint* Waypoint','struct Waypoint* Waypoint')+s+r'''
int main(){
Entity vehicle;Group group;Waypoint waypoint;waypoint.position={1234};Unit unit;unit.Entity=&vehicle;unit.Group=&group;unit.Waypoint=&waypoint;
ObserveMovement(&unit,1000);ObserveMovement(&unit,60999);assert(spawned==0&&abandoned==0);
for(int i=1;i<=3;i++){ObserveMovement(&unit,1000+i*60000);assert(unit.Repaths==i&&spawned==i&&abandoned==0&&replacement.position.x==1234);}
ObserveMovement(&unit,240999);assert(!abandoned);
vehicle.physics.speed=4;ObserveMovement(&unit,241000);assert(!abandoned);
vehicle.physics.speed=0;vehicle.hasPhysics=false;ObserveMovement(&unit,241001);assert(!abandoned);
vehicle.hasPhysics=true;group.contact=true;ObserveMovement(&unit,241002);assert(!abandoned);
group.contact=false;ObserveMovement(&unit,301001);assert(!abandoned);
ObserveMovement(&unit,301002);assert(abandoned==1&&unit.Released&&spawned==3);
// Actual movement restores retry allowance; arrival is never a stuck patrol.
unit.Released=false;vehicle.position={20};ObserveMovement(&unit,400000);assert(unit.Repaths==0&&!unit.Released);
replacement.arrived=true;ObserveMovement(&unit,500000);assert(!unit.Released&&abandoned==1&&spawned==3);
}
''')
print('PASS: actual movement recovery retries delivered orders three times, respects combat/speed/arrival/progress, then abandons the stalled vehicle')
s=body(p,'protected void AbandonStuckVehicle(').replace('protected ', '').replace('ERR_HunterVehicle unit','Unit* unit').replace('unit.', 'unit->')
s=s.replace('string reason', 'std::string reason')
s=s.replace('SCR_AIGroupUtilityComponent utility = unit->Group.GetGroupUtilityComponent();','Utility* utility = unit->Group->GetGroupUtilityComponent();')
s=s.replace('SCR_AIVehicleUsageComponent usage = SCR_AIVehicleUsageComponent.Cast(unit->Entity.FindComponent(SCR_AIVehicleUsageComponent));','void* usage = unit->Entity;')
s=s.replace('foreach (IEntity member : unit->Members)','for (Actor* member : unit->Members)')
s=s.replace('SCR_ChimeraAIAgent agent = SCR_ChimeraAIAgent.Cast(SCR_AIUtils.GetAIAgent(member));','Agent* agent = member->agent;')
s=s.replace('agent.', 'agent->').replace('m_UtilityComponent.', 'm_UtilityComponent->').replace('utility.', 'utility->').replace('null','nullptr').replace('priority: ', '')
for ns in ('ERR_ConvoyRules','ERR_Actors','ERR_ConvoyMountedCrew','CompartmentAccessComponent','ERR_HQ'):s=s.replace(ns+'.',ns+'::')
run(r'''
#include <cassert>
#include <vector>
#include <string>
struct Utility{int detached=0;void RemoveUsableVehicle(void*){detached++;}};
struct Group{Utility utility;Utility* GetGroupUtilityComponent(){return &utility;}};
struct Action{};
struct AgentUtility{bool m_AIInfo=true;int exits=0;void AddAction(Action* action){exits++;delete action;}};
struct SCR_AIGetOutVehicle:Action{SCR_AIGetOutVehicle(AgentUtility*,void*,void*,int priority){assert(priority==1000);}};
struct Agent{AgentUtility* m_UtilityComponent=nullptr;};
struct Actor{bool fit=true,player=false;Group* group=nullptr;void* vehicle=nullptr;Agent* agent=nullptr;int releases=0;};
struct Unit{bool Released=false,PlayerCaptureObserved=false;struct Group* Group=nullptr;void* Entity=nullptr;std::vector<Actor*> Members;std::string Source="original bike";int releases=0;void Release(void*){releases++;Released=true;}};
void* m_Mode=nullptr;
namespace ERR_ConvoyRules{bool captured=false;bool Captured(void*,Group*){return captured;}}
namespace ERR_Actors{bool CanFight(Actor*a){return a&&a->fit;}bool Player(Actor*a){return a->player;}struct Group* Group(Actor*a){return a->group;}}
namespace ERR_ConvoyMountedCrew{void Release(Actor*a){a->releases++;}}
namespace CompartmentAccessComponent{void* GetVehicleIn(Actor*a){return a->vehicle;}}
namespace ERR_HQ{void Event(std::string,std::string){}}
'''+s+r'''
int main(){Group group,other;Unit unit;unit.Group=&group;unit.Entity=&unit;
AgentUtility utility;Agent agent{&utility};Actor driver,passenger,player,unfit,transferred,foot;
for(auto*a:{&driver,&passenger,&player,&unfit,&transferred,&foot}){a->group=&group;a->vehicle=&unit;a->agent=&agent;unit.Members.push_back(a);}
player.player=true;unfit.fit=false;transferred.group=&other;foot.vehicle=nullptr;
unit.PlayerCaptureObserved=true;AbandonStuckVehicle(&unit);assert(unit.releases==0);
unit.PlayerCaptureObserved=false;ERR_ConvoyRules::captured=true;AbandonStuckVehicle(&unit);assert(unit.releases==0);
ERR_ConvoyRules::captured=false;AbandonStuckVehicle(&unit);
assert(unit.releases==1&&group.utility.detached==1&&utility.exits==2);
assert(driver.releases==1&&passenger.releases==1&&foot.releases==1);
assert(player.releases==0&&unfit.releases==0&&transferred.releases==0);
AbandonStuckVehicle(&unit);assert(unit.releases==1&&utility.exits==2);
}
''')
assert 'ObserveMovement(unit, now);\n if (unit.Released) continue;' in p.read_text()
print('PASS: actual abandonment releases once, detaches transport, requests native exits only for fit original AI aboard, and leaves players/captured crews alone')