Files
EscapistsReintroduced/tests/test_infantry_ammo_access.py
T

75 lines
6.9 KiB
Python

"""Execute actual authored storage-anchor selection and native trace guard."""
import os,re
from pathlib import Path
from policy_harness import ROOT,run
s=Path(os.environ.get('ERR_INFANTRY_ACCESS_SOURCE',ROOT/'addons/ai/Scripts/Game/ERR/ERR_InfantryAmmoAccess.c')).read_text()
s=re.sub(r'//[^\n]*','',s).replace('class ERR_InfantryAmmoAccess','struct ERR_InfantryAmmoAccess').replace('protected ','').replace('notnull ','')
s=s.replace('ActionsManagerComponent.Cast(donor.FindComponent(ActionsManagerComponent))','donor->manager').replace('SCR_OpenVehicleStorageAction.Cast(action)','dynamic_cast<SCR_OpenVehicleStorageAction*>(action)').replace('ChimeraCharacter.Cast(recipient)','dynamic_cast<Character*>(recipient)')
s=s.replace('array<UserActionContext>', 'array<UserActionContext*>').replace('array<BaseUserAction>', 'array<BaseUserAction*>')
s=s.replace('out array<vector> points','Array<vector>& points').replace('array<','Array<').replace('foreach (','for (')
for name in ('ActionsManagerComponent','UserActionContext','BaseUserAction','ChimeraCharacter','TraceParam','ERR_InfantryAmmoAccess'):
s=re.sub(r'\b'+name+r' (\w+)',name+r'* \1',s)
for name in ('manager','context','action','character','trace','access'):
s=s.replace(name+'.',name+'->')
for name in ('ERR_ConvoyRules','ERR_ForceRules','TraceFlags','vector'):
s=s.replace(name+'.',name+'::')
s=s.replace('GetGame().GetWorld()','GetGame()->GetWorld()').replace('GetWorld().','GetWorld()->').replace('null','nullptr')
s=s.replace('TraceMove(trace, Filter)','TraceMove(trace, [this](IEntity e){return Filter(e);})')
s=re.sub(r'\b(bool|IEntity) (\w+);',r'\1 \2{};',s).replace('\n}\n','\n};\n')
run(r'''
#include <cassert>
#include <vector>
#include <cmath>
#include <limits>
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;using std::vector<T>::operator=;int Count(){return this->size();}bool IsEmpty(){return this->empty();}void Insert(T t){this->push_back(t);}};
struct vector{float x=0,y=0,z=0;float& operator[](int n){return n==0?x:n==1?y:z;}static float Distance(vector a,vector b){return std::sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)+(a.z-b.z)*(a.z-b.z));}};
struct Entity;struct Manager;using IEntity=Entity*;using Vehicle=Entity*;
struct Entity{virtual ~Entity(){}Manager*manager=nullptr;IEntity parent=nullptr;};
struct Character:Entity{vector aim={0,1.4,0};vector AimingPosition(){return aim;}};using ChimeraCharacter=Character;
struct BaseUserAction{virtual ~BaseUserAction(){}IEntity owner=nullptr;auto GetOwner(){return owner;}};
struct SCR_OpenVehicleStorageAction:BaseUserAction{};
struct UserActionContext{Array<BaseUserAction*>actions;vector anchor={2,1.4,0};bool valid=true;void GetActionsList(Array<BaseUserAction*>&a){a=actions;}bool GetTransformationWorld(vector (&mat)[4]){mat[3]=anchor;return valid;}};
struct Manager{bool enabled=true;Array<UserActionContext*>contexts;bool IsEnabled(){return enabled;}void GetContextList(Array<UserActionContext*>&a){a=contexts;}};using ActionsManagerComponent=Manager;
struct TraceFlags{static constexpr int WORLD=1,ENTS=2;};static constexpr int TRACE_LAYER_CAMERA=99;
struct TraceParam{vector Start,End;int Flags=0,LayerMask=0;};
struct World{float fraction=1;Array<IEntity>obstacles;int traces=0;vector lastStart,lastEnd;
template<class F>float TraceMove(TraceParam*p,F filter){traces++;lastStart=p->Start;lastEnd=p->End;assert(p->Flags==3&&p->LayerMask==99);for(auto entity:obstacles)if(filter(entity))return .5;return fraction;}};
struct Game{World world;auto GetWorld(){return &world;}} game;Game*GetGame(){return &game;}
struct ERR_ConvoyRules{static bool PartOf(IEntity a,IEntity root){int n=0;while(a&&n++<32){if(a==root)return true;a=a->parent;}return false;}};
struct ERR_ForceRules{static bool FiniteVector(vector v){return std::isfinite(v.x)&&std::isfinite(v.y)&&std::isfinite(v.z);}};
''' + s + r'''
int main(){
Entity truck,truckPart,gear,wall,otherTruck,otherChild;Character actor;truckPart.parent=&truck;gear.parent=&actor;otherChild.parent=&otherTruck;
Manager manager;truck.manager=&manager;UserActionContext context;SCR_OpenVehicleStorageAction storage;storage.owner=&truck;context.actions={&storage};manager.contexts={&context};
Array<vector>points;assert(ERR_InfantryAmmoAccess::Points(&truck,points)&&points.Count()==1);
ERR_InfantryAmmoAccess access;assert(access.Check(&truck,&actor));assert(game.world.lastStart.y==1.4f&&game.world.lastEnd.x==2);
// Own hull/gear cannot falsely block an interior-authored native storage anchor.
game.world.obstacles={&truck,&truckPart,&actor,&gear};assert(access.Check(&truck,&actor));
// A wall or another vehicle still blocks even when owned geometry preceded it.
game.world.obstacles.push_back(&wall);assert(!access.Check(&truck,&actor));
game.world.obstacles={&truck,&otherChild};assert(!access.Check(&truck,&actor));game.world.obstacles.clear();
for(float value:{0.f,.99f,-1.f,2.f,std::numeric_limits<float>::quiet_NaN()}){game.world.fraction=value;assert(!access.Check(&truck,&actor));}game.world.fraction=1;
context.anchor.x=4.01;assert(!access.Check(&truck,&actor));context.anchor.x=0;assert(!access.Check(&truck,&actor));context.anchor.x=2;
actor.aim.y=std::numeric_limits<float>::quiet_NaN();assert(!access.Check(&truck,&actor));actor.aim.y=1.4;
context.valid=false;assert(!access.Check(&truck,&actor));context.valid=true;
context.anchor.z=std::numeric_limits<float>::quiet_NaN();assert(!access.Check(&truck,&actor));context.anchor.z=0;
storage.owner=&otherTruck;assert(!access.Check(&truck,&actor));storage.owner=&truck;
BaseUserAction door;door.owner=&truck;context.actions={&door};assert(!access.Check(&truck,&actor));context.actions={&storage};
manager.enabled=false;assert(!access.Check(&truck,&actor));manager.enabled=true;
// Stock Ural defines 36 root contexts and ammo adds one: a valid storage action near the end must remain discoverable.
for(int count:{37,64}){
std::vector<UserActionContext> stockContexts(count);manager.contexts.clear();
for(auto& authored:stockContexts){authored.actions={&door};manager.contexts.push_back(&authored);}
stockContexts.back().actions={&storage};
assert(ERR_InfantryAmmoAccess::Points(&truck,points)&&points.Count()==1&&access.Check(&truck,&actor));
}
manager.contexts.clear();manager.contexts.resize(65,&context);assert(!access.Check(&truck,&actor));manager.contexts={&context};
context.actions.resize(17,&storage);assert(!access.Check(&truck,&actor));context.actions={&storage};
manager.contexts={nullptr};assert(!access.Check(&truck,&actor));manager.contexts={&context};
assert(!access.Check(&truck,&wall));assert(!access.Check(nullptr,&actor));
// A blocked context never skips an intervening external entity; another independently clear authored context may qualify.
UserActionContext distant;distant.actions={&storage};distant.anchor={9,1.4,0};manager.contexts={&distant,&context};assert(access.Check(&truck,&actor));
}
''')
print('PASS: actual authored storage anchors, native trace flags, own-hierarchy exclusion, external wall/vehicle obstruction, range, unknown geometry and bounded context refusal')