Integrate faction and random settings menus, generated objectives, scouts, convoys, air support, mortar stations and body-intel recovery. Add identified-body aggression, finite hunter UAZ deployment, dedicated objective defence and maximum AI skill with separate reduced aim accuracy. Include policy tests, native probes, evidence and documentation.
203 lines
17 KiB
Python
203 lines
17 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/core/Scripts/Game/ERR/10_AI/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','ERR_EnemyGunnerSkill','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;}bool IsMissionEnemy(int f){return f==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;}
|
|
};
|
|
namespace ERR_EnemyGunnerSkill {
|
|
bool CapturedGroup(ERR_GameMode*,SCR_AIGroup*){return false;}
|
|
bool OriginalGunner(ERR_GameMode*,SCR_AIGroup*,IEntity*){return false;}
|
|
EAISkill Profile(EAISkill requested,bool){return EAISkill::CYLON;}
|
|
}
|
|
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::CYLON);
|
|
skill.combat.SetAISkill(EAISkill::ROOKIE);assert(skill.combat.GetAISkill()==EAISkill::CYLON);
|
|
skill.combat.ResetAISkill();assert(skill.combat.GetAISkill()==EAISkill::CYLON);
|
|
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_EnemyRadio','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').replace('Radio.Check','Radio->Check').replace('Radio.Stop','Radio->Stop').replace('Radio.Register','Radio->Register')
|
|
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,refreshes=0;void ERR_BindEnemySkill(ERR_GameMode*,SCR_AIGroup*){binds++;}void ERR_RefreshEnemySkill(){refreshes++;}};
|
|
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_EnemyRadio{ERR_EnemyRadio(SCR_AIGroup*,int){}void Register(IEntity*){}void Check(ERR_GameMode*){}void Stop(){}};
|
|
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&&combat.refreshes==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&&combat.refreshes==1); // Stinger is already complete; optics has its own startup completion.
|
|
service.Tick(3001);assert(entry->checks==1&&entry->Optics->checks==1&&combat.refreshes==1);
|
|
service.Tick(6000);assert(entry->checks==2&&entry->Optics->checks==2&&combat.binds==1&&combat.refreshes==2);
|
|
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&&combat.refreshes==2);
|
|
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<270;i++){bounded.RegisterGroup(new SCR_AIGroup());}assert(bounded.m_Groups.Count()==256);
|
|
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 256-group bound')
|