Files
EscapistsReintroduced/tests/test_enemy_equipment.py
T

197 lines
16 KiB
Python

"""Execute actual scoped skill and finite native-equipment policy with API doubles."""
import os,re
from pathlib import Path
from policy_harness import ROOT,run
source=Path(os.environ.get('ERR_EQUIPMENT_SOURCE',str(ROOT/'addons/ai/Scripts/Game/ERR/ERR_EnemyEquipment.c'))).read_text()
code=source[:source.index('class ERR_EnemyEquipmentGroup')]
code=re.sub(r'//[^\n]*','',code)
code=code.replace('modded class SCR_AICombatComponent','struct SCR_AICombatComponent : NativeCombat')
code=code.replace('class ERR_EnemyEquipmentCallback : ScriptedInventoryOperationCallback','struct ERR_EnemyEquipmentCallback')
code=code.replace('class ERR_EnemyEquipmentRules','struct ERR_EnemyEquipmentRules').replace('class ERR_EnemyEquipmentEntry','struct ERR_EnemyEquipmentEntry')
code=code.replace('protected ','').replace('override ','').replace('ref ','')
code=code.replace('static const ResourceName','static constexpr const char*').replace('static const int','static constexpr int')
code=code.replace('string result','std::string result').replace('array<IEntity>','std::vector<IEntity*>')
code=code.replace('BaseWeaponManagerComponent.Cast(Actor.FindComponent(BaseWeaponManagerComponent))','Actor->weapons')
code=code.replace('InventoryStorageManagerComponent.Cast(Actor.FindComponent(InventoryStorageManagerComponent))','Actor->inventory')
for cls in ('ERR_GameMode','SCR_AIGroup','SCR_AIGroupUtilityComponent','IEntity','SCR_AICombatComponent','ERR_EnemyEquipmentCallback','ERR_EnemyOptics','BaseWeaponManagerComponent','InventoryStorageManagerComponent'):
code=re.sub(r'\b'+cls+r' ([a-zA-Z_]\w*)',cls+r'* \1',code)
for obj in ('mode','group','utility','Actor','Group','Combat','Optics','Operation','item','weapons','inventory'):
code=code.replace(obj+'.',obj+'->')
for cls in ('Replication','SCR_Global','ERR_GameMode','ERR_Actors','ERR_EnemyEquipmentRules','EAISkill','ERR_HQ','Math','EStoragePurpose'):
code=code.replace(cls+'.',cls+'::')
code=code.replace('GetPrefabData().','GetPrefabData()->').replace('Settings().','Settings()->').replace('foreach (','for (').replace('null','nullptr').replace('super.','NativeCombat::')
code=code.replace('new ERR_EnemyEquipmentCallback()','NewOperation()')
code=re.sub(r'\b(bool|int|EAISkill|[A-Za-z_]\w*\*) ([A-Za-z_]\w*);',r'\1 \2 {};',code)
code=code.replace('\n}\n','\n};\n')
# Forward factory declaration permits bounded managed operation storage below.
code=code.replace('struct ERR_EnemyEquipmentEntry','ERR_EnemyEquipmentCallback* NewOperation();\nstruct ERR_EnemyEquipmentEntry')
run(r'''
#include <cassert>
#include <string>
#include <vector>
#include <memory>
enum class EAISkill {ROOKIE,REGULAR,EXPERT,CYLON};
namespace Replication {bool server=true;bool IsServer(){return server;}}
namespace SCR_Global {bool editor=false;bool IsEditMode(){return editor;}}
namespace EStoragePurpose {constexpr int PURPOSE_WEAPON_PROXY=7;}
namespace ERR_HQ {void Event(std::string,std::string){}}
struct SCR_AIGroupUtilityComponent {bool military=true;bool IsMilitary(){return military;}};
struct SCR_AIGroup {
SCR_AIGroupUtilityComponent utility;int players=0,faction=1;bool ready=true;
SCR_AIGroupUtilityComponent* GetGroupUtilityComponent(){return ready?&utility:nullptr;}
int GetPlayerCount(bool){return players;}int GetFaction(){return faction;}
};
struct Settings {int oneIn=15;int Get(std::string key){assert(key=="stingerChanceOneIn");return oneIn;}};
struct ERR_GameMode {
static inline ERR_GameMode* current=nullptr;bool active=true;struct Settings values;
static ERR_GameMode* Get(){return current;}bool Active(){return active;}int EnemyFaction(){return 1;}
struct Settings* Settings(){return &values;}
};
namespace Math {int draw=0,calls=0;int RandomInt(int lo,int hi){assert(lo==0&&hi>0);calls++;return draw;}}
struct Prefab {std::string name;std::string GetPrefabName(){return name;}};
struct ERR_EnemyEquipmentCallback;
struct InventoryStorageManagerComponent {
bool accept=true;int requests=0;ERR_EnemyEquipmentCallback* callback=nullptr;
bool TrySpawnPrefabToStorage(const char* prefab,void* target,int slot,int purpose,ERR_EnemyEquipmentCallback* op){assert(std::string(prefab).find("3DF204EEE0534EF2")!=std::string::npos);assert(!target&&slot==-1&&purpose==7);requests++;callback=op;return accept;}
};
struct IEntity;
struct BaseWeaponManagerComponent {std::vector<IEntity*> items;void GetWeaponsList(std::vector<IEntity*>& out){out=items;}};
struct IEntity {
bool player=false,alive=true;SCR_AIGroup* group=nullptr;Prefab* prefab=nullptr;
BaseWeaponManagerComponent* weapons=nullptr;InventoryStorageManagerComponent* inventory=nullptr;
Prefab* GetPrefabData(){return prefab;}
};
namespace ERR_Actors {bool CanFight(IEntity* actor){return actor->alive;}bool Player(IEntity* actor){return actor->player;}SCR_AIGroup* Group(IEntity* actor){return actor->group;}}
struct NativeCombat {
IEntity* owner=nullptr;EAISkill skill=EAISkill::REGULAR;
IEntity* GetOwner(){return owner;}EAISkill GetAISkill(){return skill;}
virtual void SetAISkill(EAISkill value){skill=value;}virtual void ResetAISkill(){skill=EAISkill::ROOKIE;}
};
struct ERR_EnemyOptics{int stops=0;void Stop(){stops++;}};
''' + code + r'''
std::vector<std::unique_ptr<ERR_EnemyEquipmentCallback>> operations;
ERR_EnemyEquipmentCallback* NewOperation(){operations.emplace_back(new ERR_EnemyEquipmentCallback());return operations.back().get();}
struct Soldier {
SCR_AIGroup group;BaseWeaponManagerComponent weapons;InventoryStorageManagerComponent inventory;IEntity actor;SCR_AICombatComponent combat;ERR_EnemyOptics optics;
Soldier(){actor.group=&group;actor.weapons=&weapons;actor.inventory=&inventory;combat.owner=&actor;}
ERR_EnemyEquipmentEntry Entry(){ERR_EnemyEquipmentEntry e;e.Actor=&actor;e.Group=&group;e.Combat=&combat;e.Optics=&optics;return e;}
};
int main(){
ERR_GameMode mode;ERR_GameMode::current=&mode;
// One draw per exact actor; accepted insertion cannot be retried, even after timeout.
for(int draw=0;draw<15;draw++){
Math::draw=draw;Math::calls=0;Soldier soldier;auto job=soldier.Entry();
for(int n=0;n<20;n++){job.Check(&mode);}
assert(job.Complete&&Math::calls==1&&soldier.inventory.requests==(draw==0?1:0));
}
Math::draw=0;mode.values.oneIn=0;Soldier off;auto disabled=off.Entry();Math::calls=0;
assert(disabled.Check(&mode)&&Math::calls==0&&off.inventory.requests==0);
mode.values.oneIn=1;
Soldier success;auto finished=success.Entry();assert(!finished.Check(&mode));
success.inventory.callback->OnComplete();success.inventory.callback->OnAllFailed();
assert(finished.Check(&mode)&&success.inventory.requests==1&&success.inventory.callback->Succeeded);
// A completed then removed launcher is never replenished.
for(int n=0;n<10;n++){finished.Check(&mode);}assert(success.inventory.requests==1);
Soldier rejected;rejected.inventory.accept=false;auto retries=rejected.Entry();
for(int n=0;n<10;n++){retries.Check(&mode);}assert(rejected.inventory.requests==4&&retries.Complete);
Soldier failed;auto again=failed.Entry();again.Check(&mode);failed.inventory.callback->OnFailed();again.Check(&mode);assert(failed.inventory.requests==2);
for(int n=0;n<10;n++){again.Check(&mode);}assert(failed.inventory.requests==2);
Soldier changed;auto captured=changed.Entry();captured.Check(&mode);changed.actor.player=true;assert(captured.Check(&mode));changed.actor.player=false;
for(int n=0;n<10;n++){captured.Check(&mode);}assert(changed.inventory.requests==1);
Soldier absent;absent.actor.inventory=nullptr;auto missing=absent.Entry();for(int n=0;n<8;n++){missing.Check(&mode);}assert(missing.Complete&&absent.inventory.requests==0);
Soldier equipped;Prefab prefab;prefab.name=ERR_EnemyEquipmentEntry::LAUNCHER;IEntity item;item.prefab=&prefab;equipped.weapons.items.push_back(&item);auto existing=equipped.Entry();assert(existing.Check(&mode)&&equipped.inventory.requests==0);
// The exact same eligibility policy protects skill binding and item submission.
for(int variant=0;variant<9;variant++){
Soldier soldier;ERR_GameMode other;auto job=soldier.Entry();Math::calls=0;
if(variant==0)Replication::server=false;
if(variant==1)SCR_Global::editor=true;
if(variant==2)mode.active=false;
if(variant==3)ERR_GameMode::current=&other;
if(variant==4)soldier.actor.player=true;
if(variant==5)soldier.actor.alive=false;
if(variant==6)soldier.group.players=1;
if(variant==7)soldier.group.faction=2;
if(variant==8)soldier.group.utility.military=false;
assert(job.Check(&mode)&&Math::calls==0&&soldier.inventory.requests==0);
soldier.combat.ERR_BindEnemySkill(&mode,&soldier.group);assert(soldier.combat.GetAISkill()==EAISkill::REGULAR);
Replication::server=true;SCR_Global::editor=false;mode.active=true;ERR_GameMode::current=&mode;
}
Soldier skill;skill.combat.ERR_BindEnemySkill(&mode,&skill.group);assert(skill.combat.GetAISkill()==EAISkill::EXPERT);
skill.combat.SetAISkill(EAISkill::ROOKIE);assert(skill.combat.GetAISkill()==EAISkill::EXPERT);
skill.combat.ResetAISkill();assert(skill.combat.GetAISkill()==EAISkill::EXPERT);
skill.combat.SetAISkill(EAISkill::CYLON);assert(skill.combat.GetAISkill()==EAISkill::CYLON);
skill.combat.ERR_BindEnemySkill(&mode,&skill.group);skill.combat.ERR_ReleaseEnemySkill(&mode);assert(skill.combat.GetAISkill()==EAISkill::REGULAR);
skill.combat.SetAISkill(EAISkill::ROOKIE);assert(skill.combat.GetAISkill()==EAISkill::ROOKIE);
Soldier moved;SCR_AIGroup replacement;moved.combat.ERR_BindEnemySkill(&mode,&moved.group);moved.actor.group=&replacement;
moved.combat.SetAISkill(EAISkill::ROOKIE);assert(moved.combat.GetAISkill()==EAISkill::ROOKIE);
Soldier stopped;auto terminal=stopped.Entry();terminal.Check(&mode);terminal.Stop(&mode);assert(stopped.optics.stops==1);stopped.inventory.callback->OnComplete();
for(int n=0;n<10;n++){terminal.Check(&mode);}assert(stopped.inventory.requests==1&&terminal.Complete);
}
''')
print('PASS: actual enemy eligibility, scoped native skill floor/reset/restoration, one-roll inventory issuance, completion/failure, timeout quarantine, actor capture and terminal stop; native inventory runtime unproven')
coordinator=source[source.index('class ERR_EnemyEquipmentGroup'):]
coordinator=re.sub(r'//[^\n]*','',coordinator).replace('protected ','').replace('ref ','').replace('class ','struct ')
coordinator=coordinator.replace('array<ERR_EnemyEquipmentGroup>','Array<ERR_EnemyEquipmentGroup*>').replace('array<ERR_EnemyEquipmentEntry>','Array<ERR_EnemyEquipmentEntry*>').replace('array<AIAgent>','Array<AIAgent*>')
coordinator=coordinator.replace('SCR_AICombatComponent.Cast(actor.FindComponent(SCR_AICombatComponent))','actor->combat').replace('SCR_AICombatComponent.Cast(entry.Actor.FindComponent(SCR_AICombatComponent))','entry->Actor->combat')
for cls in ('ERR_GameMode','SCR_AIGroup','ERR_EnemyEquipmentGroup','ERR_EnemyEquipmentEntry','IEntity','AIAgent'):
coordinator=re.sub(r'\b'+cls+r' ([a-zA-Z_]\w*)',cls+r'* \1',coordinator)
for obj in ('m_Mode','existing','state','group','agent','entry','actor'):
coordinator=coordinator.replace(obj+'.',obj+'->')
coordinator=coordinator.replace('Group.GetAgents','Group->GetAgents').replace('Combat.ERR_','Combat->ERR_').replace('Optics.Check','Optics->Check')
for cls in ('Replication','ERR_GameMode','ERR_EnemyEquipmentRules'):coordinator=coordinator.replace(cls+'.',cls+'::')
coordinator=coordinator.replace('void ERR_EnemyEquipment(', 'ERR_EnemyEquipment(')
coordinator=coordinator.replace('GetGame().GetWorld().GetWorldTime()','worldTime').replace('foreach (','for (').replace('null','nullptr')
coordinator=re.sub(r'\b(bool|int|float|[A-Za-z_]\w*\*) ([A-Za-z_]\w*);',r'\1 \2 {};',coordinator).replace('\n}\n','\n};\n')
run(r'''
#include <cassert>
#include <vector>
#include <algorithm>
float worldTime=0;
namespace Replication {bool IsServer(){return true;}}
template<typename T> struct Array:std::vector<T>{int Count(){return this->size();}void Insert(T value){this->push_back(value);}void Clear(){this->clear();}};
struct SCR_AIGroup;
struct ERR_GameMode{static inline ERR_GameMode* current=nullptr;bool active=true;bool Active(){return active;}static ERR_GameMode* Get(){return current;}};
struct SCR_AICombatComponent{int binds=0;void ERR_BindEnemySkill(ERR_GameMode*,SCR_AIGroup*){binds++;}};
struct IEntity{SCR_AICombatComponent* combat=nullptr;bool eligible=true;};
struct AIAgent{IEntity* actor=nullptr;IEntity* GetControlledEntity(){return actor;}};
struct SCR_AIGroup{Array<AIAgent*> agents;void GetAgents(Array<AIAgent*>& out){out=agents;}};
namespace ERR_EnemyEquipmentRules{bool Eligible(ERR_GameMode*,SCR_AIGroup*,IEntity* actor){return actor&&actor->eligible;}}
struct ERR_EnemyOptics{
IEntity* actor;SCR_AIGroup* group;int checks=0,stops=0;
ERR_EnemyOptics(IEntity* a,SCR_AIGroup* g):actor(a),group(g){}
void Check(ERR_GameMode*){checks++;}void Stop(){stops++;}
};
struct ERR_EnemyEquipmentEntry{
ERR_EnemyOptics* Optics=nullptr;bool Complete=false;
IEntity* Actor=nullptr;SCR_AIGroup* Group=nullptr;SCR_AICombatComponent* Combat=nullptr;bool Released=false;int SkillChecks=0,checks=0,stops=0;
void Check(ERR_GameMode*){checks++;Complete=true;}void Stop(ERR_GameMode*){stops++;Released=true;if(Optics)Optics->Stop();Combat=nullptr;}
};
''' + coordinator + r'''
int main(){
ERR_GameMode mode;ERR_GameMode::current=&mode;ERR_EnemyEquipment service(&mode);
SCR_AIGroup group;IEntity soldier;SCR_AICombatComponent combat;soldier.combat=&combat;AIAgent agent;agent.actor=&soldier;group.agents.Insert(&agent);
service.RegisterGroup(&group);service.RegisterGroup(&group);assert(service.m_Groups.Count()==1);
service.Tick(0);assert(service.m_Entries.Count()==0);service.Tick(3000);assert(service.m_Entries.Count()==1&&combat.binds==1);
auto entry=service.m_Entries[0];assert(entry->Optics&&entry->Optics->actor==&soldier&&entry->Optics->group==&group);
assert(entry->Complete&&entry->Optics->checks==1); // Stinger is already complete; optics has its own startup completion.
service.Tick(3001);assert(entry->checks==1&&entry->Optics->checks==1);
service.Tick(6000);assert(entry->checks==2&&entry->Optics->checks==2&&combat.binds==1);
service.UnregisterGroup(&group);service.Tick(9000);assert(service.m_Entries[0]->Released&&service.m_Entries[0]->checks==2&&entry->Optics->checks==2&&entry->Optics->stops==1);
service.RegisterGroup(&group);service.Tick(12000);assert(service.m_Entries.Count()==1&&combat.binds==1);
service.Stop();service.RegisterGroup(&group);service.Tick(50000);assert(service.m_Groups.Count()==0&&service.m_Stopped);
ERR_EnemyEquipment delayed(&mode);SCR_AIGroup late;delayed.RegisterGroup(&late);delayed.Tick(3000);late.agents.Insert(&agent);delayed.Tick(6000);assert(delayed.m_Entries.Count()==1);
ERR_EnemyEquipment expired(&mode);SCR_AIGroup empty;expired.RegisterGroup(&empty);for(int i=1;i<=6;i++){expired.Tick(i*3000);}empty.agents.Insert(&agent);expired.Tick(21000);assert(expired.m_Entries.Count()==0);
ERR_EnemyEquipment bounded(&mode);for(int i=0;i<140;i++){bounded.RegisterGroup(new SCR_AIGroup());}assert(bounded.m_Groups.Count()==128);
ERR_EnemyEquipment incomplete(&mode);SCR_AIGroup unresolved;IEntity body;AIAgent bodyAgent;bodyAgent.actor=&body;unresolved.agents.Insert(&bodyAgent);incomplete.RegisterGroup(&unresolved);
for(int i=1;i<=12;i++){incomplete.Tick(i*3000);}assert(incomplete.m_Entries.Count()==1&&incomplete.m_Entries[0]->SkillChecks==5);
auto incompleteOptics=incomplete.m_Entries[0]->Optics;int checksBeforeStop=incompleteOptics->checks;
incomplete.Stop();incomplete.Tick(60000);assert(incompleteOptics->stops==1&&incompleteOptics->checks==checksBeforeStop);
ERR_EnemyEquipment capture(&mode);SCR_AIGroup capturedGroup;IEntity capturedBody;AIAgent capturedAgent;capturedAgent.actor=&capturedBody;
capturedGroup.agents.Insert(&capturedAgent);capture.RegisterGroup(&capturedGroup);capture.Tick(3000);
auto capturedOptics=capture.m_Entries[0]->Optics;capturedBody.eligible=false;capture.Tick(6000);
assert(capturedOptics->checks==1&&capturedOptics->stops==1&&capture.m_Entries[0]->Released);
capturedBody.eligible=true;capture.Tick(9000);assert(capturedOptics->checks==1);
}
''')
print('PASS: actual equipment coordinator registration dedupe, startup delay, five member scans, finite component retries, unregister/stop terminal behavior and 128-group bound')