Files
EscapistsReintroduced/tests/test_prison_ai.py
T
jakub cab998fb64 Combine Workshop addon and fix prison startup
Merge owned systems into one addon while preserving resource identities. Gate player admission on the replicated prison scene, restore the original framed door, and hold enemy AI until escape. Correct scenario branding and author metadata, include publishing assets, and retain regression and native verification evidence.
2026-09-20 16:42:52 +02:00

68 lines
4.5 KiB
Python

"""Run the production prison hold/release callbacks with engine boundaries stubbed."""
from policy_harness import ROOT, body, run
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;}
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('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.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(1,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);
}
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')