Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
66 lines
6.8 KiB
Python
66 lines
6.8 KiB
Python
"""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 <cassert>
|
|
#include <string>
|
|
struct string:std::string{using std::string::string;template<class... T>static 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')
|