Integrate faction and random settings menus, generated objectives, scouts, convoys, air support, mortar stations and body-intel recovery. Add identified-body aggression, finite hunter UAZ deployment, dedicated objective defence and maximum AI skill with separate reduced aim accuracy. Include policy tests, native probes, evidence and documentation.
252 lines
21 KiB
Python
252 lines
21 KiB
Python
"""Execute production supply-circuit and combat timing policies with native boundaries doubled."""
|
|
from policy_harness import ROOT, body, run
|
|
import re
|
|
D=ROOT/'addons/core/Scripts/Game/ERR/10_AI'
|
|
|
|
def cpp(s):
|
|
s=re.sub(r'//[^\n]*','',s).replace('protected ','').replace('override ','').replace('foreach (','for (').replace('null','nullptr')
|
|
s=s.replace('m_Task.', 'm_Task->').replace('m_Mode.', 'm_Mode->').replace('m_Service.', 'm_Service->')
|
|
for typ in ('ERR_SupplyConvoyTask','ERR_ConvoyVehicle','ERR_SupplyTeam','ERR_ForceRoute','IEntity'):
|
|
s=re.sub(r'\b'+typ+r' (?=\w)',typ+'* ',s)
|
|
for obj in ('task','vehicle','departure','team','lost','boarding','stranded','leg','infantryTeam','member','delayed'):
|
|
s=s.replace(obj+'.',obj+'->')
|
|
for cls in ('Math','ERR_SupplyConvoyRules','ERR_HQ','ERR_ForceRules','ERR_Actors','vector','string'):
|
|
s=s.replace(cls+'.',cls+'::')
|
|
s=s.replace('bool surviving, pending;','bool surviving=false, pending=false;')
|
|
s=s.replace('float nearest = 400; bool found;', 'float nearest = 400; bool found=false;')
|
|
s=s.replace('m_Support[released].', 'm_Support[released]->').replace('Vehicles[released].', 'Vehicles[released]->').replace('infantryTeam->Group.', 'infantryTeam->Group->')
|
|
s=s.replace('->Receipt.', '->Receipt->').replace('Vehicles[2].', 'Vehicles[2]->')
|
|
s=s.replace('.Entity.', '.Entity->').replace('->Entity.', '->Entity->')
|
|
return s
|
|
common=r'''
|
|
#include <cassert>
|
|
#include <cmath>
|
|
#include <limits>
|
|
#include <vector>
|
|
#include <string>
|
|
struct string:std::string{using std::string::string;string(std::string s):std::string(s){}template<class...T>static string Format(T...){return "event";}};
|
|
struct vector{float x=0;static float Distance(vector a,vector b){return std::abs(a.x-b.x);}static float DistanceXZ(vector a,vector b){return Distance(a,b);}};
|
|
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;int Count(){return this->size();}};
|
|
namespace Math{int RandomInt(int low,int){return low;}float RandomFloat(float low,float){return low;}}
|
|
namespace ERR_HQ{void Event(string,string){}}
|
|
namespace ERR_ForceRules{bool Finite(float v){return std::isfinite(v);}bool FiniteVector(vector v){return Finite(v.x);}}
|
|
'''
|
|
next_base=cpp(body(D/'ERR_SupplyConvoySystem.c','static int NextBase('))
|
|
run(common+'struct ERR_SupplyConvoyRules{'+next_base+r'''};
|
|
struct Entity{vector position;vector GetOrigin(){return position;}};
|
|
struct ERR_ForceRoute{Array<vector> points;void Add(vector p){points.push_back(p);}void Seal(){}};
|
|
struct Receipt{string SourceId="test";};
|
|
struct ERR_ConvoyVehicle{bool Released=false,Arrived=false;int cleared=0,RouteIndex=3;struct Entity entity;struct Entity* Entity=&entity;
|
|
void ClearOrder(){cleared++;}void Tick(void*,ERR_ForceRoute*,float){}};
|
|
struct ERR_SupplyConvoyTask{Array<ERR_ConvoyVehicle*> Vehicles;float TravelDeadline=1000,Departure=0;bool Waiting=false,stopped=false;int LastBase=-1,Legs=0;
|
|
Array<vector> Bases={{0},{1000},{2000}};ERR_ForceRoute* Route=nullptr;struct Receipt receipt;struct Receipt* Receipt=&receipt;
|
|
void Stop(void*,string){stopped=true;}};
|
|
struct Service{void* m_Mode=nullptr;
|
|
'''+cpp(body(D/'ERR_SupplyConvoyService.c','protected void Circuit('))+r'''};
|
|
int main(){
|
|
for(int last=0;last<5;last++)for(int roll=0;roll<4;roll++){int next=ERR_SupplyConvoyRules::NextBase(5,last,roll);assert(next>=0&&next<5&&next!=last);}
|
|
assert(ERR_SupplyConvoyRules::NextBase(1,0,0)==-1);assert(ERR_SupplyConvoyRules::NextBase(3,3,0)==-1);
|
|
Service s;ERR_SupplyConvoyTask t;ERR_ConvoyVehicle a,b;t.Vehicles={&a,&b};
|
|
s.Circuit(&t,999);assert(!t.Waiting&&!a.Arrived);s.Circuit(&t,1000);assert(a.Arrived&&b.Arrived&&t.Waiting&&t.Departure==46000);
|
|
s.Circuit(&t,45999);assert(t.Legs==0);s.Circuit(&t,46000);assert(t.Legs==1&&!t.Waiting&&!a.Arrived&&!b.Arrived&&t.LastBase==0&&t.TravelDeadline==646000);
|
|
a.entity.position={250};b.entity.position={251};s.Circuit(&t,47000);assert(a.Arrived&&!b.Arrived&&!t.Waiting);
|
|
b.entity.position={249};s.Circuit(&t,48000);assert(t.Waiting);s.Circuit(&t,93000);assert(t.LastBase==1&&t.Legs==2);
|
|
a.Released=true;b.Released=true;s.Circuit(&t,94000);assert(t.stopped); // no replacements
|
|
}
|
|
''')
|
|
run(common+r'''
|
|
struct Group{int GetFaction(){return 1;}};struct IEntity{};struct Mode{int EnemyFaction(){return 1;}};
|
|
namespace ERR_Actors{bool Alive(IEntity*){return true;}struct Group* Group(IEntity*){return nullptr;}}
|
|
struct ERR_ConvoyVehicle{bool Released=false;void* Group=nullptr;void* Entity=nullptr;};
|
|
struct ERR_SupplyTeam{bool Released=false,aboard=true,living=true;struct Group nativeGroup;struct Group* Group=&nativeGroup;Array<IEntity*> Members;int orders=0;void Clear(){}
|
|
bool Aboard(void*,bool required){return aboard==required;}bool Living(){return living;}void Release(void*){Released=true;}void Add(int){orders++;}};
|
|
struct Receipt{string SourceId="test";};
|
|
struct ERR_SupplyConvoyTask{Array<ERR_ConvoyVehicle*> Vehicles;Array<ERR_SupplyTeam*> Teams;struct Receipt receipt;struct Receipt* Receipt=&receipt;};
|
|
struct Tactics{ERR_SupplyConvoyTask* m_Task;Mode mode;Mode* m_Mode=&mode;Array<ERR_SupplyTeam*>m_Support;bool m_Stopped=false,m_TruckWithdrawn=false,seeing=false,truck=true;
|
|
int m_State=0,starts=0,reorders=0,withdraws=0,finishes=0,m_DismountAttempts=0,flanks=0;float m_NextDismount=0;void Flank(int,bool){flanks++;}float m_LastContact=-1,m_NextOrders=0,m_BoardDeadline=0;vector m_Contact;
|
|
bool Observe(void*,vector& p,float&){p={100};return seeing;}void Start(vector p,float){m_Contact=p;m_State=1;m_TruckWithdrawn=false;starts++;}
|
|
void Orders(bool,float){reorders++;}bool InfantryAboard(bool required){for(auto t:m_Task->Teams)if(!t->Released&&!t->Aboard(nullptr,required))return false;return true;}
|
|
void Support(int i,bool hold){if(i==2&&!hold)withdraws++;}bool Usable(int){return truck;}void Clear(){}int Board(void*,bool){return 1;}
|
|
void Finish(float){m_State=0;finishes++;}
|
|
'''+cpp(body(D/'ERR_SupplyConvoyTactics.c','void Report('))+cpp(body(D/'ERR_SupplyConvoyTactics.c','bool Tick('))+r'''};
|
|
int main(){
|
|
ERR_SupplyConvoyTask task;ERR_ConvoyVehicle driver;task.Vehicles={&driver,&driver,&driver};ERR_SupplyTeam left,right;task.Teams={&left,&right};Tactics t{&task};
|
|
t.Report({100},std::numeric_limits<float>::quiet_NaN(),0);assert(t.starts==0);
|
|
t.Report({100},-1,0);t.Report({100},10,0);t.Report({100},0,60001);assert(t.starts==0);
|
|
t.Report({100},1000,1000);assert(t.starts==1&&t.m_State==1);t.Report({0},999,1001);assert(t.m_LastContact==1000);
|
|
assert(t.Tick(60999)&&t.m_State==1&&t.withdraws==0);left.aboard=right.aboard=false;
|
|
assert(t.Tick(61000)&&t.m_State==2&&t.withdraws==1&&left.orders==1&&right.orders==1&&t.m_BoardDeadline==181000);
|
|
assert(t.Tick(180999));left.aboard=true;assert(!t.Tick(181000)&&t.finishes==1&&!left.Released&&right.Released);
|
|
right.Released=false;t.Report({100},200000,200000);t.Tick(260000);assert(t.m_State==2);
|
|
t.seeing=true;t.Tick(260001);assert(t.m_State==1&&t.starts==3); // fresh contact interrupts reboarding
|
|
t.seeing=false;t.truck=false;t.Tick(320001);assert(t.m_State==0&&left.Released&&right.Released); // truck lost: release surviving infantry
|
|
t.m_Stopped=true;t.Report({100},400000,400000);assert(t.starts==3&&!t.Tick(400000));
|
|
left.Released=right.Released=false;left.aboard=right.aboard=true;Tactics retry{&task};retry.Report({100},0,0);
|
|
for(int i=1;i<=9;i++){retry.Report({100},10000*i,10000*i);retry.Tick(10000*i);}
|
|
assert(retry.m_DismountAttempts==6&&retry.flanks==12); // six attempts per still-mounted team, no infinite retry
|
|
|
|
}
|
|
''')
|
|
startup=cpp(body(D/'ERR_SupplyConvoySystem.c','void Tick('))
|
|
run(common+r'''
|
|
struct Service{int count=0;int Count(){return count;}};
|
|
struct Startup{Service* m_Service;bool m_Stopped=false,m_Automatic=true,create=false;int m_Target=3,m_Attempts=0,calls=0;
|
|
float m_Began=-1,m_Next=0;string m_Reason;
|
|
bool StartOne(){calls++;if(create)m_Service->count++;return create;}
|
|
'''+startup+r'''};
|
|
int main(){
|
|
Service service;Startup s{&service};s.Tick(1000,true);s.Tick(30999,true);assert(!s.calls);s.Tick(31000,false);assert(!s.calls);s.Tick(36000,true);assert(s.calls==1);
|
|
s.create=true;s.Tick(41000,true);s.Tick(46000,true);s.Tick(51000,true);assert(service.count==3&&s.calls==4);s.Tick(56000,true);assert(s.calls==4);
|
|
Service noRoad;Startup fail{&noRoad};fail.Tick(0,true);for(int i=0;i<200;i++)fail.Tick(30000+5000*i,true);assert(fail.calls==120&&fail.m_Attempts==120);
|
|
Startup off{&noRoad};off.m_Target=0;off.Tick(0,true);off.Tick(30000,true);assert(!off.calls);
|
|
off.m_Target=3;off.m_Automatic=false;off.Tick(90000,true);assert(!off.calls);
|
|
}
|
|
''')
|
|
retain=body(D/'ERR_SupplyConvoyTactics.c','static bool Retain(')
|
|
retain=retain.replace('IEntity actor','IEntity* actor').replace('IEntity vehicle','IEntity* vehicle')
|
|
retain=retain.replace('SCR_VehicleDamageManagerComponent damage = SCR_VehicleDamageManagerComponent.Cast(vehicle.FindComponent(SCR_VehicleDamageManagerComponent));','SCR_VehicleDamageManagerComponent* damage = vehicle->damage;')
|
|
for name in ('ERR_Actors','CompartmentAccessComponent','EDamageState'):retain=retain.replace(name+'.',name+'::')
|
|
retain=retain.replace('damage.', 'damage->')
|
|
run(r"""
|
|
#include <cassert>
|
|
#include <map>
|
|
enum class EDamageState{UNDAMAGED,DAMAGED,DESTROYED};
|
|
struct SCR_VehicleDamageManagerComponent{float health=1;bool engine=true,gear=true;EDamageState state=EDamageState::UNDAMAGED;float GetHealthScaled(){return health;}bool GetEngineFunctional(){return engine;}bool GetGearboxFunctional(){return gear;}EDamageState GetState(){return state;}};
|
|
struct IEntity{bool player=false,fit=true;IEntity* vehicle=nullptr;SCR_VehicleDamageManagerComponent* damage=nullptr;};
|
|
namespace ERR_Actors{bool Player(IEntity* a){return a->player;}bool CanFight(IEntity* a){return a->fit;}}
|
|
namespace CompartmentAccessComponent{IEntity* GetVehicleIn(IEntity* a){return a->vehicle;}}
|
|
struct Map:std::map<IEntity*,IEntity*>{bool Contains(IEntity* a){return count(a);}IEntity* Get(IEntity* a){return at(a);}}s_Crews;
|
|
"""+retain+r"""
|
|
int main(){
|
|
IEntity driver,truck,other;SCR_VehicleDamageManagerComponent health;truck.damage=&health;driver.vehicle=&truck;
|
|
assert(!Retain(&driver,&truck));s_Crews[&driver]=&truck;assert(Retain(&driver,&truck));assert(!Retain(&driver,&other));
|
|
driver.player=true;assert(!Retain(&driver,&truck));driver.player=false;driver.fit=false;assert(!Retain(&driver,&truck));driver.fit=true;
|
|
health.state=EDamageState::DAMAGED;health.health=.9;assert(Retain(&driver,&truck));health.health=.25;assert(!Retain(&driver,&truck));health.health=1;
|
|
health.engine=false;assert(!Retain(&driver,&truck));health.engine=true;health.gear=false;assert(!Retain(&driver,&truck));health.gear=true;
|
|
health.state=EDamageState::DESTROYED;assert(!Retain(&driver,&truck));health.state=EDamageState::UNDAMAGED;driver.vehicle=nullptr;assert(!Retain(&driver,&truck));
|
|
}
|
|
""")
|
|
boarding=body(D/'ERR_SupplyConvoyTactics.c','static AIWaypoint Board(')
|
|
boarding=boarding.replace('AIWaypoint Board','AIWaypoint* Board').replace('Vehicle truck','Vehicle* truck').replace('truck.', 'truck->').replace('waypoint.', 'waypoint->').replace('specific.', 'specific->').replace('null','nullptr')
|
|
boarding=boarding.replace('SCR_BoardingWaypoint waypoint','SCR_BoardingWaypoint* waypoint').replace('SCR_BoardingEntityWaypoint specific','SCR_BoardingEntityWaypoint* specific')
|
|
boarding=boarding.replace('SCR_BoardingWaypoint.Cast(', 'dynamic_cast<SCR_BoardingWaypoint*>(').replace('SCR_BoardingEntityWaypoint.Cast(', 'dynamic_cast<SCR_BoardingEntityWaypoint*>(').replace('ERR_Actors.Spawn','ERR_Actors::Spawn').replace('EAIWaypointCompletionType.All','EAIWaypointCompletionType::All')
|
|
run(r"""
|
|
#include <cassert>
|
|
#include <string>
|
|
using ResourceName=std::string;
|
|
enum class EAIWaypointCompletionType{Leader,All};
|
|
struct Vehicle{int GetOrigin(){return 7;}};
|
|
struct AIWaypoint{virtual ~AIWaypoint(){}int radius=9;EAIWaypointCompletionType completion=EAIWaypointCompletionType::Leader;void SetCompletionRadius(int n){radius=n;}void SetCompletionType(EAIWaypointCompletionType type){completion=type;}};
|
|
struct SCR_BoardingWaypoint:AIWaypoint{bool driver=true,gunner=true,cargo=false;void SetAllowance(bool d,bool g,bool c){driver=d;gunner=g;cargo=c;}};
|
|
struct SCR_BoardingEntityWaypoint:SCR_BoardingWaypoint{Vehicle* vehicle=nullptr;void SetEntity(Vehicle* v){vehicle=v;}};
|
|
namespace ERR_Actors{bool fail=false;AIWaypoint* Spawn(ResourceName resource,int){if(fail)return nullptr;if(resource.find("GetIn.et")!=std::string::npos)return new SCR_BoardingEntityWaypoint();return new SCR_BoardingWaypoint();}}
|
|
"""+boarding+r"""
|
|
int main(){
|
|
Vehicle truck;assert(!Board(nullptr,false));
|
|
auto out=dynamic_cast<SCR_BoardingWaypoint*>(Board(&truck,false));assert(out&&out->completion==EAIWaypointCompletionType::All&&out->radius==100&&!out->driver&&!out->gunner&&out->cargo);
|
|
auto in=dynamic_cast<SCR_BoardingEntityWaypoint*>(Board(&truck,true));assert(in&&in->radius==100&&in->completion==EAIWaypointCompletionType::All&&in->vehicle==&truck&&!in->driver&&!in->gunner&&in->cargo);
|
|
delete out;delete in;ERR_Actors::fail=true;assert(!Board(&truck,true));
|
|
}
|
|
""")
|
|
delivery=body(D/'ERR_SupplyConvoyService.c','override bool ApplyCombatOrder(')
|
|
delivery=delivery.replace('override ','').replace('ERR_SupplyConvoyTask.Cast(task)','dynamic_cast<ERR_SupplyConvoyTask*>(task)')
|
|
for typ in ('ERR_ConvoyTask','ERR_SupplyConvoyTask','ERR_ConvoyCombatOrder'):delivery=re.sub(r'\b'+typ+r' (?=\w)',typ+'* ',delivery)
|
|
for name in ('supply','order'):delivery=delivery.replace(name+'.',name+'->')
|
|
delivery=delivery.replace('Tactics.','Tactics->').replace('super.ApplyCombatOrder','Base::ApplyCombatOrder')
|
|
run(r"""
|
|
#include <cassert>
|
|
struct ERR_ConvoyTask{virtual ~ERR_ConvoyTask(){}};
|
|
struct ERR_ConvoyCombatOrder{bool Hold=true;int Position=3;float ObservedAt=100;};
|
|
struct Tactics{int state=0,reports=0;int State(){return state;}void Report(int,float,float){reports++;}};
|
|
struct ERR_SupplyConvoyTask:ERR_ConvoyTask{struct Tactics tactics;struct Tactics* Tactics=&tactics;};
|
|
struct Base{bool accept=true;int applied=0;bool ApplyCombatOrder(ERR_ConvoyTask*,ERR_ConvoyCombatOrder*,float){applied++;return accept;}};
|
|
struct Service:Base{
|
|
"""+delivery+r"""
|
|
};
|
|
int main(){
|
|
Service service;ERR_SupplyConvoyTask task;ERR_ConvoyCombatOrder order;
|
|
for(int state=0;state<3;state++){task.tactics.state=state;assert(service.ApplyCombatOrder(&task,&order,100));}
|
|
assert(task.tactics.reports==3);order.Hold=false;assert(!service.ApplyCombatOrder(&task,&order,100));assert(service.applied==3);
|
|
task.tactics.state=0;assert(service.ApplyCombatOrder(&task,&order,100));assert(task.tactics.reports==3);
|
|
service.accept=false;order.Hold=true;assert(!service.ApplyCombatOrder(&task,&order,100)&&task.tactics.reports==3);
|
|
assert(!service.ApplyCombatOrder(nullptr,&order,100));assert(!service.ApplyCombatOrder(&task,nullptr,100));
|
|
}
|
|
""")
|
|
print('PASS: actual supply route cycling/arrival/stops, finite startup bounds, contact freshness, dismount hold, quiet interval, interrupted boarding, timeout and lost-truck release')
|
|
|
|
# Mounted groups retain their native spawn origin; sighting distance must follow vehicles.
|
|
distance=cpp(body(D/'ERR_SupplyConvoyTactics.c','protected float DistanceToConvoy(')).replace('Math.Min','Math::Min')
|
|
observe=cpp(body(D/'ERR_SupplyConvoyTactics.c','protected bool Observe('))
|
|
for typ in ('SCR_AIGroup','SCR_AIGroupPerception','PerceptionManager','SCR_AITargetInfo'):
|
|
observe=re.sub(r'\b'+typ+r' (?=\w)',typ+'* ',observe)
|
|
for obj in ('group','perception','manager','target'):observe=observe.replace(obj+'.',obj+'->')
|
|
observe=observe.replace('out vector position','vector& position').replace('inout float nearest','float& nearest').replace('bool found;','bool found=false;')
|
|
observe=observe.replace('GetGroupUtilityComponent().','GetGroupUtilityComponent()->').replace('GetFaction().','GetFaction()->').replace('GetGame().','GetGame()->').replace('EAITargetInfoCategory.','EAITargetInfoCategory::')
|
|
run(common+r"""
|
|
namespace Math{float Min(float a,float b){return a<b?a:b;}}
|
|
struct Entity{vector position;vector GetOrigin(){return position;}};
|
|
struct ERR_ConvoyVehicle{bool Released=false;struct Entity* Entity=nullptr;};
|
|
struct ERR_SupplyConvoyTask{Array<ERR_ConvoyVehicle*> Vehicles;};
|
|
struct Faction{bool hostile=true;bool IsFactionEnemy(Faction* other){return other->hostile;}};
|
|
enum class EAITargetInfoCategory{IDENTIFIED,SUSPECTED};
|
|
struct SCR_AITargetInfo{void* m_Entity=this;Faction* m_Faction=nullptr;float m_fTimestamp=10;EAITargetInfoCategory m_eCategory=EAITargetInfoCategory::IDENTIFIED;vector m_vWorldPos={5100};};
|
|
struct SCR_AIGroupPerception{Array<SCR_AITargetInfo*> m_aTargets;};
|
|
struct Utility{SCR_AIGroupPerception* m_Perception=nullptr;bool allowed=true;bool IsPositionAllowed(vector){return allowed;}};
|
|
struct SCR_AIGroup{Faction faction;Utility utility;Utility* GetGroupUtilityComponent(){return &utility;}Faction* GetFaction(){return &faction;}vector GetOrigin(){return {0};}};
|
|
struct PerceptionManager{float now=10;float GetTime(){return now;}};
|
|
struct Game{PerceptionManager manager;PerceptionManager* GetPerceptionManager(){return &manager;}}game;
|
|
Game* GetGame(){return &game;}
|
|
struct Observer{ERR_SupplyConvoyTask* m_Task;
|
|
"""+distance+observe+r"""
|
|
};
|
|
int main(){
|
|
Entity car{{5000}},lost{{0}};ERR_ConvoyVehicle live{false,&car},released{true,&lost};ERR_SupplyConvoyTask task{{&live,&released}};Observer observer{&task};
|
|
Faction enemy;SCR_AITargetInfo target;target.m_Faction=&enemy;SCR_AIGroupPerception perception{{&target}};SCR_AIGroup group;group.utility.m_Perception=&perception;
|
|
vector position;float nearest=400;assert(observer.Observe(&group,position,nearest)&&position.x==5100&&nearest==100);
|
|
target.m_vWorldPos={100};nearest=400;assert(!observer.Observe(&group,position,nearest)); // stale group/spawn origin is irrelevant
|
|
target.m_vWorldPos={5401};assert(!observer.Observe(&group,position,nearest));target.m_vWorldPos={5100};
|
|
target.m_eCategory=EAITargetInfoCategory::SUSPECTED;assert(!observer.Observe(&group,position,nearest));target.m_eCategory=EAITargetInfoCategory::IDENTIFIED;
|
|
target.m_fTimestamp=4;assert(!observer.Observe(&group,position,nearest));target.m_fTimestamp=11;assert(!observer.Observe(&group,position,nearest));target.m_fTimestamp=5;
|
|
assert(observer.Observe(&group,position,nearest));nearest=400;enemy.hostile=false;assert(!observer.Observe(&group,position,nearest));enemy.hostile=true;
|
|
group.utility.allowed=false;assert(!observer.Observe(&group,position,nearest));group.utility.allowed=true;
|
|
live.Released=true;assert(!observer.Observe(&group,position,nearest));assert(!observer.Observe(nullptr,position,nearest));
|
|
}
|
|
""")
|
|
retain_group=body(D/'ERR_SupplyConvoyTactics.c','static bool RetainGroup(')
|
|
retain_group=retain_group.replace('SCR_AIGroup group','SCR_AIGroup* group').replace('IEntity vehicle','IEntity* vehicle').replace('IEntity leader','IEntity* leader').replace('group.','group->').replace('CompartmentAccessComponent.','CompartmentAccessComponent::')
|
|
source=(D/'ERR_SupplyConvoyTactics.c').read_text()
|
|
overrides=[]
|
|
for cls in ('SCR_AIGetOutVehicle','SCR_AIGetOutActivity'):
|
|
section=source[source.index('modded class '+cls):];section=section[:section.index('\nclass ') if '\nclass ' in section else len(section)]
|
|
for method in ('CustomEvaluate','EvaluatePriorityLevel'):
|
|
# Each method's exact production body, selecting only this class's first occurrence.
|
|
a=section.index('override float '+method+'(');b=section.index('\n }',a)+3
|
|
method_source=section[a:b].replace('override ','').replace('m_Utility.','m_Utility->').replace('ERR_ConvoyMountedCrew.','ERR_ConvoyMountedCrew::').replace('super.','Base::')
|
|
overrides.append(method_source)
|
|
run(r"""
|
|
#include <cassert>
|
|
struct IEntity{bool held=false;IEntity* vehicle=nullptr;};
|
|
struct SCR_AIGroup{int players=0;IEntity* leader=nullptr;int GetPlayerCount(bool){return players;}IEntity* GetLeaderEntity(){return leader;}};
|
|
namespace CompartmentAccessComponent{IEntity* GetVehicleIn(IEntity* actor){return actor->vehicle;}}
|
|
struct ERR_ConvoyMountedCrew{static bool Retain(IEntity* actor,IEntity* vehicle){return actor&&actor->held&&vehicle&&actor->vehicle==vehicle;}
|
|
"""+retain_group+r"""
|
|
};
|
|
struct Utility{IEntity* m_OwnerEntity;SCR_AIGroup* m_Owner;};
|
|
struct Base{float CustomEvaluate(){return 1162;}float EvaluatePriorityLevel(){return 1000;}};
|
|
struct Individual:Base{Utility* m_Utility=nullptr;struct{IEntity* m_Value=nullptr;}m_Vehicle;
|
|
"""+''.join(overrides[:2])+r"""
|
|
};
|
|
struct Activity:Base{Utility* m_Utility=nullptr;struct{IEntity* m_Value=nullptr;}m_Vehicle;
|
|
"""+''.join(overrides[2:])+r"""
|
|
};
|
|
int main(){
|
|
IEntity truck,driver{true,&truck};SCR_AIGroup group{0,&driver};Utility utility{&driver,&group};Individual individual;Activity activity;
|
|
individual.m_Utility=&utility;individual.m_Vehicle.m_Value=&truck;activity.m_Utility=&utility;activity.m_Vehicle.m_Value=&truck;
|
|
assert(individual.CustomEvaluate()==0&&individual.EvaluatePriorityLevel()==0);assert(activity.CustomEvaluate()==0&&activity.EvaluatePriorityLevel()==0);
|
|
assert(ERR_ConvoyMountedCrew::RetainGroup(&group,nullptr));group.players=1;assert(activity.CustomEvaluate()==1162&&activity.EvaluatePriorityLevel()==1000);group.players=0;
|
|
driver.held=false;assert(individual.CustomEvaluate()==1162&&individual.EvaluatePriorityLevel()==1000);assert(activity.CustomEvaluate()==1162&&activity.EvaluatePriorityLevel()==1000);
|
|
group.leader=nullptr;assert(!ERR_ConvoyMountedCrew::RetainGroup(&group,&truck));assert(!ERR_ConvoyMountedCrew::RetainGroup(nullptr,&truck));
|
|
}
|
|
""")
|
|
print('PASS: moving-convoy perception boundaries and scoped individual/group abandonment priorities')
|