79 lines
7.1 KiB
Python
79 lines
7.1 KiB
Python
"""Exercise production hunter deployment when a terrain cannot fit the opening."""
|
|
import re
|
|
from policy_harness import ROOT, body, run
|
|
p=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HunterService.c'
|
|
s=body(p,'protected bool StartHunter(').replace('protected ','').replace('foreach (','for (')
|
|
s=s.replace('array<IEntity> players = {};','Array<IEntity*> players;')
|
|
s=s.replace('vector ', 'Vec ').replace('bool occupied;', 'bool occupied=false;').replace('bool openingOccupied;', 'bool openingOccupied=false;').replace('bool nearPlayer;', 'bool nearPlayer=false;')
|
|
for typ in ('ERR_HQ','ERR_ConvoyPlacement','ERR_SiteRuntime','ERR_HunterVehicle','IEntity'):
|
|
s=re.sub(r'\b'+typ+r' (?=\w)',typ+'* ',s)
|
|
for name in ('m_Mode','m_Opening','site','placement','deployed','player','existing'):
|
|
s=s.replace(name+'.',name+'->')
|
|
s=s.replace('Forces().','Forces()->').replace('Sites().','Sites()->').replace('Definition.','Definition->').replace('Group.','Group->').replace('Entity.','Entity->')
|
|
for typ in ('ERR_Actors','ERR_HunterOpeningPlan','ERR_HunterOpeningRules','ERR_LocationKind','ERR_SupplyConvoySystem','Math','ERR_HQ','vector'):
|
|
s=s.replace(typ+'.',('Geometry' if typ=='vector' else typ)+'::')
|
|
s=s.replace('ERR_ConvoyPlacement* placement = new ERR_ConvoyPlacement();','ERR_ConvoyPlacement localPlacement; ERR_ConvoyPlacement* placement=&localPlacement;')
|
|
deployed=body(p,'int Deployed(').replace('foreach (ERR_HunterVehicle unit : m_Hunters)', 'for (ERR_HunterVehicle* unit : m_Hunters)').replace('unit.', 'unit->').replace('int count;', 'int count=0;')
|
|
run(r'''
|
|
#include <cassert>
|
|
#include <cmath>
|
|
#include <vector>
|
|
#include <string>
|
|
using string=std::string;
|
|
struct Vec { float x=0,y=0,z=0; float& operator[](int i){return i==0?x:i==1?y:z;} Vec operator+(Vec b){return {x+b.x,y+b.y,z+b.z};} };
|
|
Vec Vector(float x,float y,float z){return {x,y,z};}
|
|
namespace Geometry{float DistanceXZ(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();}};
|
|
struct IEntity{Vec position;Vec GetOrigin(){return position;}};
|
|
struct Group{int faction=1;int GetFaction(){return faction;}};
|
|
struct Definition{int Kind=1;Vec Position={1000,0,0};string Id="outpost";};
|
|
struct ERR_SiteRuntime{struct Definition* Definition=nullptr;struct Group* Group=nullptr;};
|
|
struct Sites{Array<ERR_SiteRuntime*> sites;int Count(){return sites.Count();}ERR_SiteRuntime* At(int i){return sites[i];}};
|
|
struct Forces{int count=0;int Count(){return count;}};
|
|
struct Mode{struct Forces forces;struct Sites sites;struct Forces* Forces(){return &forces;}struct Sites* Sites(){return &sites;}int EnemyFaction(){return 1;}};
|
|
struct ERR_HunterVehicle{bool Motorbike=false;bool Sarka=false;bool Released=false;IEntity* Entity=nullptr;};
|
|
struct ERR_HQ{static void Event(string,string){}};
|
|
namespace Math{float RandomFloat(float,float){return 0;}}
|
|
namespace ERR_LocationKind{enum{RADIO=1,DEPOT=2,OUTPOST=3};}
|
|
namespace ERR_Actors{Array<IEntity*> current;void Players(Array<IEntity*>& out){out=current;}}
|
|
struct ERR_ConvoyPlacement{static bool clear;bool Clear(Vec,float){return clear;}};bool ERR_ConvoyPlacement::clear=true;
|
|
namespace ERR_HunterOpeningRules{bool InBand(Vec a,Vec b,float radius){auto d=Geometry::DistanceXZ(a,b);return d>=radius-250&&d<=radius+250;}};
|
|
struct ERR_HunterOpeningPlan{bool done=false,ready=false;Vec Center;Array<Vec> Positions={{1200,0,0},{0,0,1200},{-1200,0,0}},Angles={{},{},{}};bool Done(){return done;}bool Ready(){return ready;}static bool AwayFromPlayers(Vec p,Array<IEntity*> players){for(auto* a:players)if(Geometry::DistanceXZ(p,a->GetOrigin())<400)return false;return true;}};
|
|
namespace ERR_SupplyConvoySystem{bool road=true;int calls=0;bool ApproachDirected(Vec seed,Vec& out,Vec& angles){calls++;out=seed;angles={};return road;}}
|
|
struct Service{Mode mode;Mode* m_Mode=&mode;Array<ERR_HunterVehicle*> m_Hunters;int m_Target=3,m_ReserveSpent=0;ERR_HunterVehicle* m_PendingReserve=nullptr;float m_OpeningDistance=1000;ERR_HunterOpeningPlan* m_Opening=nullptr;float m_NextSpawn=0;int spawned=0;Vec chosen;string source;bool SpawnHunter(ERR_HQ*,float,Vec p,Vec,string s){spawned++;chosen=p;source=s;return true;}
|
|
'''+deployed+s+r'''
|
|
};
|
|
int main(){
|
|
Service s;ERR_HQ hq;ERR_HunterOpeningPlan opening;Definition definition;ERR_SiteRuntime site;site.Definition=&definition;s.mode.sites.sites={&site};
|
|
assert(!s.StartHunter(&hq,0)&&s.spawned==0);s.m_Opening=&opening;
|
|
assert(!s.StartHunter(&hq,0)&&ERR_SupplyConvoySystem::calls==0); // unfinished scan cannot be bypassed
|
|
opening.done=true;assert(s.StartHunter(&hq,100)&&s.source=="outpost"&&s.chosen.x==1000); // exhausted opening falls back
|
|
opening.ready=true;assert(s.StartHunter(&hq,100)&&s.source=="prison-opening");
|
|
IEntity player;player.position={1200,0,0};ERR_Actors::current={&player};definition.Position={2000,0,0};
|
|
assert(!s.StartHunter(&hq,100)); // occupied opening cannot escape the 1 km band via a distant objective
|
|
definition.Position={-1000,0,0};assert(s.StartHunter(&hq,100)&&s.source=="outpost");
|
|
player.position={-1000,0,0};opening.ready=false;int kept=s.spawned;
|
|
assert(!s.StartHunter(&hq,100)&&s.spawned==kept&&s.m_NextSpawn==10100); // safe player distance and retry delay
|
|
ERR_Actors::current.clear();definition.Position={599,0,0};assert(!s.StartHunter(&hq,200));
|
|
definition.Position={750,0,0};assert(s.StartHunter(&hq,200)); // exact opening distance boundary
|
|
IEntity parked;parked.position=definition.Position;ERR_HunterVehicle other;other.Entity=&parked;s.m_Hunters={&other};
|
|
assert(!s.StartHunter(&hq,200));other.Released=true;assert(s.StartHunter(&hq,200));
|
|
other.Released=false;parked.position={650,0,0};assert(s.StartHunter(&hq,200)); // 100 m separation accepted
|
|
ERR_ConvoyPlacement::clear=false;assert(!s.StartHunter(&hq,200));ERR_ConvoyPlacement::clear=true;
|
|
ERR_SupplyConvoySystem::road=false;assert(!s.StartHunter(&hq,200));ERR_SupplyConvoySystem::road=true;
|
|
Group foreign;foreign.faction=2;site.Group=&foreign;assert(!s.StartHunter(&hq,200));site.Group=nullptr;
|
|
definition.Kind=99;assert(!s.StartHunter(&hq,200));definition.Kind=1;
|
|
s.mode.forces.count=250;assert(!s.StartHunter(&hq,200));s.mode.forces.count=0;
|
|
s.m_Hunters.resize(3,&other);assert(!s.StartHunter(&hq,200));
|
|
s.m_Target=20;s.m_Hunters.resize(15,&other);assert(!s.StartHunter(&hq,200));
|
|
s.m_Hunters.resize(3,&other);s.m_Target=4;definition.Position={4000,0,0};assert(s.StartHunter(&hq,300)); // later reinforcements retain objective origins
|
|
s.m_Target=3;s.m_PendingReserve=&other;assert(s.StartHunter(&hq,300)); // reserve uses the same safe base roads
|
|
s.mode.forces.count=250;assert(!s.StartHunter(&hq,300));s.mode.forces.count=0;
|
|
s.m_Hunters.resize(15,&other);assert(!s.StartHunter(&hq,300)); // reserve cannot bypass total cap
|
|
s.m_Hunters.resize(3,&other);s.m_PendingReserve=nullptr;s.m_ReserveSpent=1;assert(s.StartHunter(&hq,300));
|
|
s.m_Hunters.clear();assert(!s.StartHunter(&hq,300)); // same 4 km road is forbidden for the opening
|
|
s.mode.sites.sites.clear();assert(!s.StartHunter(&hq,300)&&s.m_NextSpawn==10300);
|
|
}
|
|
''')
|
|
print('PASS: exhausted/blocked opening keeps configured radius; distant objective fallback rejected; bounds, player clearance, separation, force cap and retry delay retained')
|