81 lines
8.1 KiB
Python
81 lines
8.1 KiB
Python
"""Actual native convoy ownership/waypoint policy with small engine API doubles."""
|
|
import os,re
|
|
from pathlib import Path
|
|
from policy_harness import ROOT,run
|
|
path=Path(os.environ.get('ERR_CONVOY_SERVICE',str(ROOT/'addons/ai/Scripts/Game/ERR/ERR_ConvoyService.c')))
|
|
source=path.read_text();code=source[source.index('class ERR_ConvoyVehicle'):source.index('class ERR_ConvoyCargoCallback')]
|
|
code=re.sub(r'//[^\n]*','',code).replace('class ERR_ConvoyVehicle','struct ERR_ConvoyVehicle').replace('ref ','')
|
|
code=code.replace('array<IEntity>','Array<IEntity*>').replace('array<BaseCompartmentSlot>','Array<BaseCompartmentSlot*>').replace('array<AIAgent>','Array<AIAgent*>')
|
|
for typ in ['Vehicle','SCR_AIGroup','IEntity','BaseCompartmentSlot','AIWaypoint','ERR_GameMode','ERR_ForceRoute','ERR_ForceRecord','AIAgent','ERR_ConvoyCombatState']:
|
|
code=re.sub(r'\b'+typ+r' ([A-Za-z_]\w*)',typ+r'* \1',code)
|
|
for obj in ['Waypoint','Group','Entity','mode','record','agent','member','route','Combat']:
|
|
code=re.sub(r'\b'+obj+r'\.',obj+'->',code)
|
|
for cls in ['SCR_EntityHelper','ERR_Actors','ERR_ConvoyRules','ERR_Resources','CompartmentAccessComponent','vector']:
|
|
code=code.replace(cls+'.',cls+'::')
|
|
code=code.replace('AIWaypoint.Cast(', 'static_cast<AIWaypoint*>(').replace('Forces().','Forces()->').replace('Command().','Command()->').replace('foreach (','for (').replace('null','nullptr').replace('\n}\n','\n};\n')
|
|
code=re.sub(r'\b(bool|int|float|[A-Za-z_]\w*\*) ([A-Za-z_]\w*);',r'\1 \2 {};',code)
|
|
run(r'''
|
|
#include <cassert>
|
|
#include <vector>
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
struct vector{float x=0,y=0,z=0;static float DistanceXZ(vector a,vector 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;void Insert(T v){this->push_back(v);}void Clear(){this->clear();}bool Contains(T v){return std::find(this->begin(),this->end(),v)!=this->end();}};
|
|
struct SCR_AIGroup;struct Vehicle;struct ERR_ConvoyCombatState{bool Held=false;};
|
|
struct IEntity{bool alive=true,player=false,awake=true;Vehicle* vehicle=nullptr;SCR_AIGroup* group=nullptr;IEntity* parent=nullptr;IEntity* GetParent(){return parent;}};
|
|
struct AIAgent{int lod=0;void SetPermanentLOD(int value){lod=value;}};
|
|
struct AIWaypoint:IEntity{float radius=0;void SetCompletionRadius(float value){radius=value;}bool IsWithinCompletionRadius(SCR_AIGroup* group);};
|
|
struct SCR_AIGroup:IEntity{bool within=false;AIWaypoint* current=nullptr;int lod=0,faction=1,removes=0,adds=0;Array<AIAgent*> agents;int GetFaction(){return faction;}void SetPermanentLOD(int value){lod=value;}void GetAgents(Array<AIAgent*>& out){out=agents;}void RemoveWaypoint(AIWaypoint*){removes++;}void AddWaypoint(AIWaypoint*){adds++;}};
|
|
bool AIWaypoint::IsWithinCompletionRadius(SCR_AIGroup* group){return group->within;}
|
|
struct Vehicle:IEntity{vector position;vector GetOrigin(){return position;}};
|
|
struct BaseCompartmentSlot{};
|
|
struct ERR_ForceRecord{bool Pinned=true;};
|
|
struct Ledger{ERR_ForceRecord record;ERR_ForceRecord* FindGroup(SCR_AIGroup*){return &record;}};
|
|
struct Command{int unregistrations=0;void UnregisterGroup(SCR_AIGroup*){unregistrations++;}};
|
|
struct ERR_GameMode{struct Ledger ledger;struct Command command;int EnemyFaction(){return 1;}struct Ledger* Forces(){return &ledger;}struct Command* Command(){return &command;}};
|
|
struct ERR_ForceRoute{Array<vector> points;int Count(){return points.size();}vector At(int i){assert(i>=0&&i<Count());return points[i];}};
|
|
namespace ERR_Actors{int spawns=0;bool fail=false;bool Alive(IEntity* a){return a&&a->alive;}bool CanFight(IEntity* a){return Alive(a)&&a->awake;}bool Player(IEntity* a){return a&&a->player;}SCR_AIGroup* Group(IEntity* a){return a?a->group:nullptr;}IEntity* Spawn(int,vector){spawns++;return fail?nullptr:new AIWaypoint();}}
|
|
namespace ERR_Resources{int MoveWaypoint(){return 1;}}
|
|
namespace CompartmentAccessComponent{IEntity* GetVehicleIn(IEntity* a){return a?a->vehicle:nullptr;}}
|
|
namespace ERR_ConvoyRules{bool PartOf(IEntity* e,IEntity* root){int depth=0;while(e&&depth++<32){if(e==root)return true;e=e->GetParent();}return false;}}
|
|
namespace SCR_EntityHelper{int deletes=0;void DeleteEntityAndChildren(AIWaypoint* p){deletes++;delete p;}}
|
|
''' + code + r'''
|
|
struct Case{
|
|
ERR_GameMode mode;Vehicle vehicle;SCR_AIGroup group;IEntity driver;AIAgent agent;ERR_ConvoyVehicle unit;ERR_ForceRoute route;
|
|
Case(){unit.Entity=&vehicle;unit.Group=&group;unit.Driver=&driver;unit.Members.Insert(&driver);driver.vehicle=&vehicle;driver.group=&group;group.agents.Insert(&agent);route.points={{100,0,0},{300,0,0}};}
|
|
};
|
|
int main(){
|
|
Case travel;travel.unit.Tick(&travel.mode,&travel.route,1000);assert(travel.group.adds==1&&travel.unit.Waypoint&&travel.unit.LegDeadline==601000);
|
|
travel.unit.Tick(&travel.mode,&travel.route,2000);assert(travel.group.adds==1&&travel.unit.LegDeadline==601000);
|
|
travel.vehicle.position={100,0,0};travel.group.within=true;travel.unit.Tick(&travel.mode,&travel.route,3000);assert(travel.unit.RouteIndex==1&&travel.group.adds==2&&travel.group.removes==1);
|
|
travel.vehicle.position={300,0,0};travel.unit.Tick(&travel.mode,&travel.route,4000);assert(travel.unit.Arrived&&!travel.unit.Waypoint&&travel.group.removes==2);
|
|
travel.unit.Tick(&travel.mode,&travel.route,5000);assert(travel.group.adds==2);
|
|
// Native completion may use seated actors ahead of the vehicle origin; native
|
|
// current WP can already be removed while the controller still owns its handle.
|
|
Case offset;offset.unit.Tick(&offset.mode,&offset.route,1000);offset.vehicle.position={64.5,0,0};offset.group.within=true;offset.group.current=nullptr;
|
|
assert(vector::DistanceXZ(offset.vehicle.position,offset.route.At(0))>35);
|
|
offset.unit.Tick(&offset.mode,&offset.route,2000);assert(offset.unit.RouteIndex==1&&offset.group.adds==2&&offset.group.removes==1&&offset.unit.Waypoint);
|
|
offset.group.within=false;offset.unit.Tick(&offset.mode,&offset.route,3000);offset.unit.Tick(&offset.mode,&offset.route,4000);assert(offset.unit.RouteIndex==1&&offset.group.adds==2&&offset.group.removes==1);
|
|
// A close vehicle origin alone is not the native seated-group arrival predicate.
|
|
Case notNative;notNative.vehicle.position={99,0,0};notNative.unit.Tick(¬Native.mode,¬Native.route,1000);notNative.unit.Tick(¬Native.mode,¬Native.route,2000);assert(notNative.unit.RouteIndex==0&¬Native.group.adds==1);
|
|
Case held;held.unit.Tick(&held.mode,&held.route,1000);held.unit.Combat->Held=true;held.unit.ClearOrder();held.group.within=true;assert(!held.unit.Order(&held.route,2000)&&held.group.adds==1);
|
|
held.unit.Tick(&held.mode,&held.route,700000);assert(held.unit.RouteIndex==0&&held.group.adds==1&&!held.unit.Released&&!held.unit.Waypoint);
|
|
held.driver.alive=false;held.unit.Tick(&held.mode,&held.route,700001);assert(held.unit.Released&&!held.unit.Combat->Held);
|
|
Case timeout;timeout.unit.Tick(&timeout.mode,&timeout.route,1000);timeout.unit.Tick(&timeout.mode,&timeout.route,601000);assert(timeout.unit.Released&&!timeout.unit.Waypoint&&timeout.mode.command.unregistrations==1&&!timeout.mode.ledger.record.Pinned);
|
|
timeout.unit.Release(&timeout.mode);timeout.unit.Tick(&timeout.mode,&timeout.route,999999);assert(timeout.mode.command.unregistrations==1&&timeout.group.adds==1&&timeout.group.lod==-1&&timeout.agent.lod==-1);
|
|
for(int reason=0;reason<6;reason++){
|
|
Case lost;lost.unit.Tick(&lost.mode,&lost.route,1000);
|
|
if(reason==0)lost.driver.alive=false;
|
|
if(reason==1)lost.driver.player=true;
|
|
if(reason==2)lost.driver.vehicle=nullptr;
|
|
if(reason==3)lost.group.faction=2;
|
|
if(reason==4)lost.vehicle.alive=false;
|
|
if(reason==5)lost.driver.group=nullptr;
|
|
lost.unit.Tick(&lost.mode,&lost.route,2000);assert(lost.unit.Released&&!lost.unit.Waypoint&&lost.mode.command.unregistrations==1);
|
|
assert(lost.unit.Entity==&lost.vehicle&&lost.unit.Group==&lost.group&&lost.unit.Members[0]==&lost.driver);
|
|
}
|
|
Case failed;ERR_Actors::fail=true;failed.unit.Tick(&failed.mode,&failed.route,1000);assert(failed.unit.Released&&failed.group.adds==0);ERR_Actors::fail=false;
|
|
}
|
|
''')
|
|
print('PASS: actual native seated-group arrival outside35m, native waypoint removal, idempotent advance, and convoy waypoint idempotence/arrival/deadline, missing/dead/captured/reparented crew refusal, failed native waypoint release, original vehicle/member preservation and no replacement')
|