"""Actual native shoot adapter: void acceptance is not a physical projectile.""" import re from policy_harness import ROOT, body, run p=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_MortarFiring.c' s=body(p,'modded class SCR_AIShootStaticArtillery') s=s.replace('modded class SCR_AIShootStaticArtillery','struct Node').replace('protected ','').replace('override ','') s=s.replace('GetGame().GetWorld().GetWorldTime()','this->now') s=s.replace('SCR_MortarMuzzleComponent.Cast(gun.FindComponent(SCR_MortarMuzzleComponent))','gun->muzzle') s=s.replace('SCR_CharacterControllerComponent.Cast(loader.FindComponent(SCR_CharacterControllerComponent))','loader->controller') for typ in ['ERR_MortarBattery','ERR_MortarSystem','SCR_MortarMuzzleComponent','SCR_CharacterControllerComponent','AIAgent']: s=re.sub(r'\b'+typ+r' (\w+)',typ+r'* \1',s) s=s.replace('super.EOnTaskSimulate(owner, dt)','Native(owner, dt)').replace('ERR_MortarSystem.Get()','ERR_MortarSystem::Get()').replace('ERR_Actors.','ERR_Actors::').replace('ENodeResult.','ENodeResult::').replace('string.Format','string::Format') for n in ['owner','system','battery','gun','loader','muzzle','controller']:s=s.replace(n+'.',n+'->') s=re.sub(r'\bnull\b','nullptr',s) # Enforce initializes locals/members; C++ needs those defaults explicitly. s=re.sub(r'(ERR_MortarBattery\*|SCR_CharacterControllerComponent\*|IEntity) (\w+);',r'\1 \2 = nullptr;',s) s=s.replace('IEntity m_ERRLoader, m_ERRShell;','IEntity m_ERRLoader=nullptr, m_ERRShell=nullptr;') s=s.replace('int m_ERRLoadingOrder, m_ERRLoadingShot, m_ERRLoadAttempts;','int m_ERRLoadingOrder=0, m_ERRLoadingShot=0, m_ERRLoadAttempts=0;').replace('float m_ERRLoadStarted, m_ERRRetryAt;','float m_ERRLoadStarted=0, m_ERRRetryAt=0;') extra=''' static constexpr int PORT_ARTILLERY_ENTITY=1,PORT_AMMO_ENTITY=2; float now=0;int requests=0;bool startLoad=false,synchronous=false; IEntity portGun=nullptr,portShell=nullptr; void GetVariableIn(int p,IEntity& e){e=p==1?portGun:portShell;} ENodeResult Native(AIAgent*,float){requests++;if(startLoad)portGun->muzzle->loading=true;if(synchronous){ERR_MortarSystem::instance->battery->OrderShots++;}return ENodeResult::SUCCESS;} ''' s=s[:s.rfind('}')]+extra+s[s.rfind('}'):]+ ';' run('''#include #include struct string:std::string{using std::string::string;templatestatic string Format(const char* s,T...){return s;}}; enum class ENodeResult{FAIL,RUNNING,SUCCESS}; struct Entity;using IEntity=Entity*; struct SCR_MortarMuzzleComponent{bool loading=false;int ammo=0;bool IsBeingLoaded(){return loading;}int GetAmmoCount(){return ammo;}}; struct SCR_CharacterControllerComponent{IEntity held=nullptr;IEntity GetAttachedGadgetAtLeftHandSlot(){return held;}}; struct Entity{SCR_MortarMuzzleComponent* muzzle=nullptr;SCR_CharacterControllerComponent* controller=nullptr;bool fighting=true,player=false;}; struct AIAgent{IEntity actor=nullptr;IEntity GetControlledEntity(){return actor;}}; struct ERR_Actors{static bool CanFight(IEntity e){return e&&e->fighting;}static bool Player(IEntity e){return e&&e->player;}}; struct ERR_MortarBattery{int OrderId=1,OrderShots=0;bool CommandOpen=true,Released=false;void Trace(string,string,string){}}; struct ERR_MortarSystem{static ERR_MortarSystem* instance;ERR_MortarBattery* battery=nullptr;bool allowed=true;int rejects=0,checks=0;static ERR_MortarSystem* Get(){return instance;}ERR_MortarBattery* FindGun(IEntity){return battery;}bool AllowShot(ERR_MortarBattery*){checks++;return allowed;}void Withhold(ERR_MortarBattery*,string){rejects++;}}; ERR_MortarSystem* ERR_MortarSystem::instance=nullptr; '''+s+''' struct Fixture{ERR_MortarBattery battery;ERR_MortarSystem sys;SCR_MortarMuzzleComponent muzzle;SCR_CharacterControllerComponent controller;Entity gun,actor,shell;AIAgent agent;Node node; Fixture(){sys.battery=&battery;ERR_MortarSystem::instance=&sys;gun.muzzle=&muzzle;actor.controller=&controller;controller.held=&shell;agent.actor=&actor;node.portGun=&gun;node.portShell=&shell;} ENodeResult tick(float t=0){node.now=t;return node.EOnTaskSimulate(&agent,.02f);}}; int main(){ Fixture retry;assert(retry.tick()==ENodeResult::RUNNING&&retry.node.requests==1);assert(retry.tick(999)==ENodeResult::RUNNING&&retry.node.requests==1); retry.node.startLoad=true;assert(retry.tick(1000)==ENodeResult::RUNNING&&retry.node.requests==2);assert(retry.tick(4000)==ENodeResult::RUNNING&&retry.node.requests==2); retry.battery.Released=true;retry.muzzle.loading=false;assert(retry.tick(20000)==ENodeResult::RUNNING&&retry.node.requests==2); retry.battery.OrderShots++;retry.battery.Released=false;retry.node.portShell=nullptr;assert(retry.tick(20001)==ENodeResult::SUCCESS&&retry.node.requests==2&&!retry.node.m_ERRLoadingBattery); Fixture refused;refused.tick();refused.tick(1000);refused.tick(2000);assert(refused.tick(3000)==ENodeResult::FAIL&&refused.node.requests==3&&refused.sys.rejects==1); Fixture interrupted;interrupted.node.startLoad=true;interrupted.tick();interrupted.muzzle.loading=false;assert(interrupted.tick(2000)==ENodeResult::RUNNING&&interrupted.node.requests==2); Fixture stuck;stuck.node.startLoad=true;stuck.tick();assert(stuck.tick(15000)==ENodeResult::FAIL&&stuck.node.requests==1&&stuck.sys.rejects==1); Fixture successor;successor.tick();successor.battery.OrderId++;assert(successor.tick(1000)==ENodeResult::FAIL&&!successor.sys.rejects&&successor.node.requests==1); Fixture closed;closed.tick();closed.battery.CommandOpen=false;assert(closed.tick(1000)==ENodeResult::FAIL&&!closed.sys.rejects); Fixture newOwner;newOwner.tick();newOwner.agent.actor=&newOwner.shell;assert(newOwner.tick(1000)==ENodeResult::FAIL&&!newOwner.sys.rejects); Fixture changed;changed.tick();changed.node.portShell=&changed.actor;changed.controller.held=&changed.actor;assert(changed.tick(1000)==ENodeResult::FAIL&&changed.node.requests==1); Fixture moved;moved.tick();moved.controller.held=nullptr;assert(moved.tick(1000)==ENodeResult::FAIL&&moved.node.requests==1); Fixture unsafe;unsafe.tick();unsafe.sys.allowed=false;assert(unsafe.tick(1000)==ENodeResult::FAIL&&unsafe.node.requests==1); Fixture chambered;chambered.muzzle.ammo=1;assert(chambered.tick()==ENodeResult::FAIL&&!chambered.node.requests); Fixture captured;captured.actor.player=true;assert(captured.tick()==ENodeResult::FAIL&&!captured.node.requests); Fixture dead;dead.actor.fighting=false;assert(dead.tick()==ENodeResult::FAIL&&!dead.node.requests); Fixture instant;instant.node.synchronous=true;assert(instant.tick()==ENodeResult::RUNNING);assert(instant.tick(1)==ENodeResult::SUCCESS&&instant.node.requests==1); Fixture native;native.sys.battery=nullptr;assert(native.tick()==ENodeResult::SUCCESS&&native.node.requests==1); } ''') print('PASS: shoot adapter waits for a physical round; bounded same-shell retries, interrupted/never-started loads, committed no-retry, order/owner changes and normal native delegation')