Files
2026-10-31 15:01:51 +01:00

96 lines
7.3 KiB
Python

"""Execute production fleet ownership, recovery and airborne completion gates."""
from policy_harness import ROOT, body, run
source = ROOT / 'addons/core/Scripts/Game/ERR/00_Core/ERR_ExtractionService.c'
s = '\n'.join(body(source, signature) for signature in (
'int Vehicles(', 'bool IsTransport(', 'protected bool FleetAlive(',
'protected bool FleetLanded(', 'bool CarryingSurvivorsToSafety(', 'void Tick('))
s = s.replace('protected ', '').replace('out array<Vehicle> vehicles', 'Array<Vehicle*>& vehicles')
s = s.replace('array<Vehicle>', 'Array<Vehicle*>').replace('array<ref ERR_PlayerLife>', 'Array<ERR_PlayerLife*>')
s = s.replace('Vehicle aircraft', 'Vehicle* aircraft').replace('ERR_Phase phase', 'int phase')
s = s.replace('foreach (ERR_RescueFlightComponent ', 'for (Flight* ').replace('foreach (ERR_PlayerLife ', 'for (ERR_PlayerLife* ')
for name in ('transport', 'departing', 'life', 'm_Roster'):
s = s.replace(name + '.', name + '->')
s = s.replace('departing->Aircraft().GetOrigin()', 'departing->Aircraft()->GetOrigin()')
s = s.replace('GetGame().GetWorld().GetWorldTime()', 'this->now')
for name in ('ERR_Actors', 'CompartmentAccessComponent', 'Vehicle', 'Replication', 'ERR_MissionRules', 'ERR_RescueRules', 'ERR_ExtractionState', 'ERR_Phase'):
s = s.replace(name + '.', name + '::')
s = s.replace('vector.DistanceXZ', 'Position::DistanceXZ').replace('int aboard;', 'int aboard=0;')
rules = body(source, 'static float AirborneSince(') + body(source, 'static bool FlightComplete(')
run(r'''
#include <algorithm>
#include <cassert>
#include <cmath>
#include <vector>
template<class T> struct Array : std::vector<T> {
using std::vector<T>::vector;
bool IsEmpty(){return this->empty();}int Count(){return this->size();}
bool Contains(T value){return std::find(this->begin(),this->end(),value)!=this->end();}
void Insert(T value){this->push_back(value);}void Clear(){this->clear();}
};
struct Position {float x=0,z=0;static float DistanceXZ(Position a,Position b){return std::hypot(a.x-b.x,a.z-b.z);}};
struct Vehicle {bool alive=true;Position position;Position GetOrigin(){return position;}static Vehicle* Cast(Vehicle* a){return a;}};
struct Actor {bool alive=true;Vehicle* vehicle=nullptr;};
struct ERR_PlayerLife {::Actor* Actor;int AccountId;};
struct Roster {Array<ERR_PlayerLife*> lives;void Participants(Array<ERR_PlayerLife*>& out){out=lives;}};
struct Flight {Vehicle* aircraft;bool lost=false,landed=true,airborne=true;bool AirborneAfterPickup(){return airborne;}bool Lost(){return lost;}bool Landed(){return landed;}Vehicle* Aircraft(){return aircraft;}};
struct ERR_Actors {template<class T>static bool Alive(T* a){return a&&a->alive;}};
struct CompartmentAccessComponent {static Vehicle* GetVehicleIn(Actor* a){return a->vehicle;}};
struct Replication {static inline bool server=true;static bool IsServer(){return server;}};
namespace ERR_Phase {enum {PREPARING,PRISON,ESCAPING,EXTRACTION,VICTORY,DEFEAT};}
namespace ERR_ExtractionState {enum {WAITING,ESCAPED,LOST};}
struct ERR_MissionRules {static bool Terminal(int p){return p>=ERR_Phase::VICTORY;}};
struct ERR_RescueRules {
''' + rules + r'''
};
struct Service {
float now=0,m_AirborneSince=-1;
bool m_Stopped=false,m_Assigned=true,m_Begun=true,m_Mode=false;int m_Stage=5,m_State=ERR_ExtractionState::WAITING;
Flight* m_Transport=nullptr;Array<Flight*> m_Transports;Vehicle* m_Vehicle=nullptr;Roster* m_Roster=nullptr;
Position m_Pickup{500,0},m_Center;float m_Radius=1000;Array<int> m_Escapees;int m_ParticipantCount=0;
void Resolve(int result){m_State=result;}void RescueTick(float now){}
''' + s + r'''
};
int main(){
Vehicle first{true,{1600,0}},second{true,{1800,0}},escort{true,{2000,0}},unrelated{true,{2100,0}};
Actor one{true,&first},two{true,&second},dead{false,nullptr};
ERR_PlayerLife l1{&one,101},l2{&two,202},l3{&dead,303};Roster roster{{&l1,&l2,&l3}};
Flight f1{&first},f2{&second};Service service;
service.m_Transport=&f1;service.m_Transports={&f1,&f2};service.m_Vehicle=&first;service.m_Roster=&roster;
Array<Vehicle*> registered;assert(service.Vehicles(registered)==2); // Legacy primary handle is deduplicated.
assert(service.IsTransport(&first)&&service.IsTransport(&second));
assert(!service.IsTransport(&escort)&&!service.IsTransport(&unrelated)&&!service.IsTransport(nullptr));
assert(service.CarryingSurvivorsToSafety()&&service.FleetLanded()); // Split survivors are valid, including unconscious/disconnected roster actors.
for(Vehicle* invalid : {static_cast<Vehicle*>(nullptr),&escort,&unrelated}){two.vehicle=invalid;assert(!service.CarryingSurvivorsToSafety());}two.vehicle=&second;
for(int stage : {0,1,2,3,4,6}){service.m_Stage=stage;assert(!service.CarryingSurvivorsToSafety());}service.m_Stage=5;
f2.landed=false;assert(!service.FleetLanded());f2.landed=true;
f2.lost=true;assert(!service.CarryingSurvivorsToSafety());service.Tick(ERR_Phase::EXTRACTION);assert(service.m_State==ERR_ExtractionState::LOST);
f2.lost=false;service.m_State=ERR_ExtractionState::WAITING;second.alive=false;assert(!service.CarryingSurvivorsToSafety());service.Tick(ERR_Phase::EXTRACTION);assert(service.m_State==ERR_ExtractionState::LOST);
second.alive=true;service.m_State=ERR_ExtractionState::WAITING;
service.m_Transports.Insert(nullptr);assert(!service.FleetAlive());service.m_Transports.pop_back();
service.m_Stopped=true;assert(!service.CarryingSurvivorsToSafety());service.m_Stopped=false;
service.m_Assigned=false;assert(!service.CarryingSurvivorsToSafety());service.m_Assigned=true;
service.Tick(ERR_Phase::ESCAPING);assert(service.m_AirborneSince==-1);
service.m_Stage=4;service.Tick(ERR_Phase::EXTRACTION);assert(service.m_AirborneSince==-1);service.m_Stage=5;
// One grounded transport prevents the timer, regardless of distance.
f2.airborne=false;service.Tick(ERR_Phase::EXTRACTION);assert(service.m_AirborneSince==-1);
f2.airborne=true;service.now=1000;service.Tick(ERR_Phase::EXTRACTION);assert(service.m_State==ERR_ExtractionState::WAITING);
service.now=30999;service.Tick(ERR_Phase::EXTRACTION);assert(service.m_State==ERR_ExtractionState::WAITING);
// Distance is irrelevant, even right above the pickup.
first.position={500,0};second.position={500,0};
service.now=31000;service.Tick(ERR_Phase::EXTRACTION);assert(service.m_State==ERR_ExtractionState::ESCAPED);
assert(service.m_Escapees.Count()==2&&service.m_Escapees.Contains(101)&&service.m_Escapees.Contains(202)&&!service.m_Escapees.Contains(303));
assert(service.m_ParticipantCount==3);
service.m_State=ERR_ExtractionState::WAITING;service.m_Escapees.Clear();service.m_AirborneSince=-1;
service.now=40000;service.Tick(ERR_Phase::EXTRACTION);
two.vehicle=&first; // An empty second transport must also lift off safely.
f2.airborne=false;service.now=50000;service.Tick(ERR_Phase::EXTRACTION);assert(service.m_AirborneSince==-1);
f2.airborne=true;service.now=60000;service.Tick(ERR_Phase::EXTRACTION);assert(service.m_State==ERR_ExtractionState::WAITING);
service.now=90000;f2.lost=true;service.Tick(ERR_Phase::EXTRACTION);
assert(service.m_State==ERR_ExtractionState::LOST && service.m_Escapees.IsEmpty());
one.alive=false;two.alive=false;assert(!service.CarryingSurvivorsToSafety());roster.lives.clear();assert(!service.CarryingSurvivorsToSafety());
service.m_Transports.Clear();assert(!service.FleetAlive()&&!service.FleetLanded());
}
''')
print('PASS: split-fleet boarding, escort rejection, lost transport failure and both transports airborne for thirty seconds')