Files
EscapistsReintroduced/tests/test_prison_ai.py
T
jakub 6a667d8deb Capture current mission, AI, map and administration development
Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
2026-10-13 22:52:53 +02:00

71 lines
4.8 KiB
Python

"""Run the production prison hold/release callbacks with engine boundaries stubbed."""
from policy_harness import ROOT, body, run
from pathlib import Path
import os
source = Path(os.environ.get('ERR_PRISON_AI_SOURCE', ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_PrisonAI.c'))
def cpp(signature):
s=body(source,signature).replace('protected ','')
for ns in ('Replication','SCR_Global','ERR_GameMode','ERR_Phase','ERR_Actors','SCR_FactionManager'):
s=s.replace(ns+'.',ns+'::')
s=s.replace('ERR_GameMode mode =','ERR_GameMode* mode =').replace('IEntity actor =','Actor* actor =')
for var in ('mode','actor','m_ERRPrisonMode'):
s=s.replace(var+'.',var+'->')
s=s.replace('ERR_Phase previous, ERR_Phase current','int previous, int current')
for callback in ('ERR_PrisonPhaseChanged','ERR_PrisonStopped'):
s=s.replace('Insert('+callback+')','Insert("'+callback+'")').replace('Remove('+callback+')','Remove("'+callback+'")')
s=s.replace('OnPhaseChanged().','OnPhaseChanged()->').replace('OnStop().','OnStop()->')
return s
run(r'''
#include <cassert>
#include <set>
#include <string>
#define null nullptr
namespace Replication {bool server=true;bool IsServer(){return server;}}
namespace SCR_Global {bool edit=false;bool IsEditMode(){return edit;}}
namespace ERR_Phase {constexpr int PRISON=1,RADIO_SEARCH=2;}
struct Actor {bool player=false, fighting=true;int faction=2;};
namespace ERR_Actors {bool Player(Actor* a){return a&&a->player;}bool CanFight(Actor* a){return a&&a->fighting;}}
namespace SCR_FactionManager {int SGetFaction(Actor* a){return a->faction;}}
struct Hook {std::set<std::string> callbacks;int inserted=0;void Insert(std::string c){callbacks.insert(c);inserted++;}void Remove(std::string c){callbacks.erase(c);}};
struct ERR_GameMode {static ERR_GameMode* current;int phase=0,enemy=2;Hook phases,stops;
static ERR_GameMode* Get(){return current;}int Phase(){return phase;}int EnemyFaction(){return enemy;}bool IsMissionEnemy(int f){return enemy&&f>=enemy;}
Hook* OnPhaseChanged(){return &phases;}Hook* OnStop(){return &stops;}};
ERR_GameMode* ERR_GameMode::current=nullptr;
struct Agent {ERR_GameMode* m_ERRPrisonMode=nullptr;Actor* actor=nullptr;bool pending=false,active=true;int starts=0;
Actor* GetControlledEntity(){return actor;}bool IsPlayerPending_S(){return pending;}
void DeactivateAI(){active=false;}void ActivateAI(){active=true;starts++;}
'''+cpp('bool ERR_PrisonHeldForTest(')+cpp('void ERR_HoldForPrison(')+cpp('protected void ERR_PrisonUnsubscribe(')+cpp('protected void ERR_PrisonPhaseChanged(')+cpp('protected void ERR_PrisonStopped(')+r'''
};
int main(){
for(int phase=0;phase<=1;phase++){
ERR_GameMode mode;mode.phase=phase;ERR_GameMode::current=&mode;Actor actor;Agent a;a.actor=&actor;
a.ERR_HoldForPrison();assert(a.ERR_PrisonHeldForTest());assert(!a.active&&mode.phases.callbacks.size()==1&&mode.stops.callbacks.size()==1);
a.active=true;a.ERR_HoldForPrison();assert(!a.active&&mode.phases.inserted==1);
a.ERR_PrisonPhaseChanged(0,1);assert(!a.active&&a.m_ERRPrisonMode==&mode);
mode.phase=2;a.ERR_PrisonPhaseChanged(phase,2);assert(a.active&&a.starts==1&&!a.m_ERRPrisonMode&&mode.phases.callbacks.empty()&&mode.stops.callbacks.empty());
a.ERR_HoldForPrison();assert(a.active&&mode.phases.inserted==1);
assert(!a.ERR_PrisonHeldForTest());a.DeactivateAI();assert(!a.active&&!a.ERR_PrisonHeldForTest());
}
for(int fault=0;fault<8;fault++){
ERR_GameMode mode;ERR_GameMode::current=&mode;Actor actor;Agent a;a.actor=&actor;
if(fault==0)Replication::server=false;if(fault==1)SCR_Global::edit=true;
if(fault==2)ERR_GameMode::current=nullptr;if(fault==3)a.actor=nullptr;
if(fault==4)actor.player=true;if(fault==5)a.pending=true;
if(fault==6)actor.faction=1;if(fault==7)mode.enemy=0;
a.ERR_HoldForPrison();assert(a.active&&!a.m_ERRPrisonMode&&mode.phases.callbacks.empty());
Replication::server=true;SCR_Global::edit=false;
}
// Dead/unconscious, newly possessed, pending-player and removed actors cannot
// be activated by the release callback; all subscriptions are still removed.
for(int fault=0;fault<4;fault++){
ERR_GameMode mode;ERR_GameMode::current=&mode;Actor actor;Agent a;a.actor=&actor;a.ERR_HoldForPrison();
if(fault==0)actor.fighting=false;if(fault==1)actor.player=true;
if(fault==2)a.pending=true;if(fault==3)a.actor=nullptr;
mode.phase=2;a.ERR_PrisonPhaseChanged(1,2);assert(!a.active&&a.starts==0&&mode.phases.callbacks.empty());
}
{ERR_GameMode mode;ERR_GameMode::current=&mode;Actor actor;Agent a;a.actor=&actor;a.ERR_HoldForPrison();
a.ERR_PrisonStopped();assert(!a.active&&a.starts==0&&!a.m_ERRPrisonMode&&mode.phases.callbacks.empty()&&mode.stops.callbacks.empty());}
}
'''.replace(';if(', ';\n if('))
print('PASS: production prison AI hold/release, repeat activation, authority/faction/player exclusion, dead actor and stop cleanup')