197 lines
16 KiB
Python
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')
|