Improve AI garrisons, player recovery and mission administration
This commit is contained in:
@@ -1,139 +1,8 @@
|
||||
"""Execute actual scoped skill and finite native-equipment policy with API doubles."""
|
||||
"""Execute actual finite equipment coordinator; individual grant policies have separate tests."""
|
||||
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*>')
|
||||
@@ -157,10 +26,10 @@ template<typename T> struct Array:std::vector<T>{int Count(){return this->size()
|
||||
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 IEntity{SCR_AICombatComponent* combat=nullptr;bool eligible=true,unconscious=false;};
|
||||
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;}}
|
||||
namespace ERR_EnemyEquipmentRules{bool Owned(ERR_GameMode*,SCR_AIGroup*,IEntity* actor){return actor&&actor->eligible;}bool Eligible(ERR_GameMode* m,SCR_AIGroup* g,IEntity* actor){return Owned(m,g,actor)&&!actor->unconscious;}}
|
||||
struct ERR_EnemyOptics{
|
||||
IEntity* actor;SCR_AIGroup* group;int checks=0,stops=0;
|
||||
ERR_EnemyOptics(IEntity* a,SCR_AIGroup* g):actor(a),group(g){}
|
||||
@@ -192,6 +61,12 @@ int main(){
|
||||
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);
|
||||
// Temporary incapacitation preserves the same finite entry and its skill binding.
|
||||
ERR_EnemyEquipment recovery(&mode);SCR_AIGroup recoveringGroup;IEntity recovering;SCR_AICombatComponent recoveredCombat;
|
||||
recovering.combat=&recoveredCombat;AIAgent recoveringAgent;recoveringAgent.actor=&recovering;recoveringGroup.agents.Insert(&recoveringAgent);
|
||||
recovery.RegisterGroup(&recoveringGroup);recovery.Tick(3000);auto retained=recovery.m_Entries[0];
|
||||
recovering.unconscious=true;recovery.Tick(6000);assert(!retained->Released&&retained->stops==0&&retained->checks==1&&recoveredCombat.refreshes==1);
|
||||
recovering.unconscious=false;recovery.Tick(9000);assert(recovery.m_Entries.Count()==1&&recovery.m_Entries[0]==retained&&recoveredCombat.binds==1&&recoveredCombat.refreshes==2&&retained->checks==2);
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user