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.
117 lines
11 KiB
Python
117 lines
11 KiB
Python
"""Execute actual startup optics service through native attachment API doubles; no native proof."""
|
|
from pathlib import Path
|
|
import os,re
|
|
from policy_harness import ROOT,run
|
|
P=Path(os.environ.get('ERR_OPTICS_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_EnemyOptics.c'))
|
|
s=P.read_text()
|
|
assert 'GetGame(' not in s and 'CallLater' not in s and 'DeleteEntity' not in s
|
|
callback=(ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_EnemyEquipment.c').read_text().split('class ERR_EnemyEquipmentCallback',1)[1].split('class ERR_EnemyEquipmentEntry',1)[0]
|
|
code='class ERR_EnemyEquipmentCallback'+callback+s
|
|
code=re.sub(r'//[^\n]*','',code).replace('protected ','').replace('override ','').replace('class ','struct ')
|
|
for src,dst in {
|
|
'BaseWeaponManagerComponent.Cast(m_Actor.FindComponent(BaseWeaponManagerComponent))':'m_Actor->manager',
|
|
'InventoryStorageManagerComponent.Cast(m_Actor.FindComponent(InventoryStorageManagerComponent))':'m_Actor->inventory',
|
|
'BaseWeaponComponent.Cast(job.Rifle.FindComponent(BaseWeaponComponent))':'job->Rifle->weapon',
|
|
'BaseWeaponComponent.Cast(rifle.FindComponent(BaseWeaponComponent))':'rifle->weapon',
|
|
'WeaponAttachmentsStorageComponent.Cast(job.Rifle.FindComponent(WeaponAttachmentsStorageComponent))':'job->Rifle->storage',
|
|
'entity.FindComponent(SCR_2DOpticsComponent)':'entity->scope2D',
|
|
'entity.FindComponent(SCR_2DPIPSightsComponent)':'entity->scope2DPip',
|
|
'entity.FindComponent(SCR_PIPSightsComponent)':'entity->scopePip',
|
|
}.items():code=code.replace(src,dst)
|
|
code=code.replace('ref array<ref ERR_EnemyOpticsJob>','Array<ERR_EnemyOpticsJob*>').replace('ref array<','Array<').replace('array<','Array<')
|
|
code=code.replace('out Array<ResourceName> result','Array<ResourceName>& result')
|
|
for name in ('ERR_EnemyEquipmentCallback','ERR_EnemyOpticsJob','ERR_EnemyOpticsInspection'):
|
|
code=re.sub(r'\b(?:ref )?'+name+r' (?=\w)',name+'* ',code)
|
|
code=code.replace('void ERR_EnemyOptics(','ERR_EnemyOptics(')
|
|
code=code.replace('EWeaponType type','int type').replace('foreach (','for (').replace('null','nullptr')
|
|
for name in ('ERR_EnemyEquipmentRules','ERR_HQ','Math','EStoragePurpose','EWeaponType'):code=code.replace(name+'.',name+'::')
|
|
for name in ('entity','child','weapon','sights','slot','job','manager','inventory','storage','inspection'):
|
|
code=re.sub(r'\b'+name+r'\.',name+'->',code)
|
|
code=code.replace('mode.Content().Allows(prefab)', 'mode->Content()->Allows(prefab)').replace('mode.Content()', 'mode->Content()')
|
|
code=code.replace('job->Operation.','job->Operation->')
|
|
code=re.sub(r'\b(bool|int|IEntity|SCR_AIGroup) (\w+);',r'\1 \2{};',code)
|
|
code=re.sub(r'\b(ERR_EnemyEquipmentCallback\*) (\w+);',r'\1 \2{};',code)
|
|
code=code.replace('\n}\n','\n};\n')
|
|
preamble=r'''
|
|
#include <cassert>
|
|
#include <algorithm>
|
|
#include <string>
|
|
#include <vector>
|
|
using string=std::string;using ResourceName=std::string;
|
|
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;int Count(){return this->size();}bool IsEmpty(){return this->empty();}void Insert(T t){this->push_back(t);}void Remove(int index){this->erase(this->begin()+index);}bool Contains(T t){return std::find(this->begin(),this->end(),t)!=this->end();}};
|
|
struct Entity;struct Group;struct Weapon;struct Manager;struct Inventory;struct Storage;struct Slot;struct Sights;struct ContentPolicy{bool rhs=true;bool Allows(string p){return rhs||p.find("{C850A33226B8F9C1}")!=string::npos||p.find("{D2018EDB1BBF4C88}")!=string::npos||p.find("{BD496EE1B40DC510}")!=string::npos;}};struct ERR_GameMode{ContentPolicy content;ContentPolicy* Content(){return &content;}};
|
|
using IEntity=Entity*;using SCR_AIGroup=Group*;using BaseWeaponComponent=Weapon*;using BaseWeaponManagerComponent=Manager*;
|
|
using InventoryStorageManagerComponent=Inventory*;using WeaponAttachmentsStorageComponent=Storage*;using InventoryStorageSlot=Slot*;using AttachmentSlotComponent=Slot*;using BaseSightsComponent=Sights*;
|
|
struct ScriptedInventoryOperationCallback{virtual void OnComplete(){}virtual void OnFailed(){}virtual void OnAllComplete(){}virtual void OnAllFailed(){}virtual ~ScriptedInventoryOperationCallback()=default;};
|
|
struct EWeaponType{static const int WT_SNIPERRIFLE=1,WT_RIFLE=2,WT_MACHINEGUN=3,WT_HANDGUN=4;};
|
|
struct EStoragePurpose{static const int PURPOSE_ANY=0;};struct Math{static int RandomInt(int,int){return 0;}};
|
|
struct Group{};
|
|
struct Slot{IEntity attached=nullptr;IEntity GetAttachedEntity(){return attached;}};
|
|
struct Storage{Array<Slot*>slots;int GetSlotsCount(){return slots.Count();}Slot* GetSlot(int n){return slots[n];}};
|
|
struct Sights{IEntity owner=nullptr;IEntity GetOwner(){return owner;}};
|
|
struct Weapon{IEntity owner=nullptr;int kind=EWeaponType::WT_RIFLE;Sights*sights=nullptr;Array<Slot*>slots;
|
|
IEntity GetOwner(){return owner;}int GetWeaponType(){return kind;}Sights* GetAttachedSights(){return sights;}void GetAttachments(Array<Slot*>&out){out=slots;}};
|
|
struct Manager{Array<IEntity>weapons;void GetWeaponsList(Array<IEntity>&out){out=weapons;}};
|
|
struct Entity{Manager*manager=nullptr;Inventory*inventory=nullptr;Weapon*weapon=nullptr;Storage*storage=nullptr;
|
|
Entity*child=nullptr,*sibling=nullptr;Group*group=nullptr;bool eligible=true,scope2D=false,scope2DPip=false,scopePip=false;
|
|
IEntity GetChildren(){return child;}IEntity GetSibling(){return sibling;}};
|
|
struct Inventory{int submits=0,checks=0;bool accept=true,fit=true;int callback=0;ScriptedInventoryOperationCallback*last=nullptr;Array<int>requested;
|
|
bool CanInsertResourceInStorage(ResourceName,Storage*,int){checks++;return fit;}
|
|
bool TrySpawnPrefabToStorage(ResourceName,Storage*,int slot,int,ScriptedInventoryOperationCallback*cb){submits++;requested.Insert(slot);last=cb;
|
|
if(callback==1){cb->OnComplete();}if(callback==2){cb->OnFailed();}return accept;}};
|
|
struct ERR_EnemyEquipmentRules{static bool Eligible(ERR_GameMode* mode,Group*group,IEntity actor){return mode&&group&&actor&&actor->eligible&&actor->group==group;}};
|
|
struct ERR_HQ{static void Event(string,string){}};
|
|
'''
|
|
# Enforce class arguments are reference types.
|
|
code=re.sub(r'\bERR_GameMode (?=\w)','ERR_GameMode* ',code)
|
|
main=r'''
|
|
struct Fixture{ERR_GameMode mode;Group group,other;Entity actor,rifle;Weapon weapon;Manager manager;Inventory inv;Storage storage;Slot slot;
|
|
Fixture(){actor.group=&group;actor.manager=&manager;actor.inventory=&inv;manager.weapons={&rifle};rifle.weapon=&weapon;rifle.storage=&storage;weapon.owner=&rifle;storage.slots={&slot};}
|
|
};
|
|
int main(){
|
|
{Fixture f;f.mode.content.rhs=false;f.inv.fit=false;ERR_EnemyOptics s(&f.actor,&f.group);for(int i=0;i<5;i++)s.Check(&f.mode);assert(f.inv.submits==0&&f.inv.checks==12);}
|
|
|
|
// Existing mounted/nested/sights magnified optics are preserved. Occupied red dots aren't overwritten.
|
|
for(int style=0;style<3;style++){Fixture f;Entity optic,adapter;Sights sights;Slot attach;
|
|
optic.scope2D=style==0;optic.scope2DPip=style==1;optic.scopePip=style==2;if(style==0){f.rifle.child=&adapter;adapter.child=&optic;}
|
|
if(style==1){sights.owner=&optic;f.weapon.sights=&sights;}
|
|
if(style==2){attach.attached=&optic;f.weapon.slots={&attach};}
|
|
ERR_EnemyOptics s(&f.actor,&f.group);assert(s.Check(&f.mode));assert(f.inv.submits==0&&s.m_Jobs[0]->Confirmed);}
|
|
{Fixture f;Entity reddot;f.slot.attached=&reddot;ERR_EnemyOptics s(&f.actor,&f.group);for(int i=0;i<6;i++)s.Check(&f.mode);assert(f.inv.submits==0);}
|
|
// Positive grant targets one concrete native free compatible slot; accepted unknown is never repeated.
|
|
{Fixture f;ERR_EnemyOptics s(&f.actor,&f.group);assert(!s.Check(&f.mode));assert(f.inv.submits==1&&f.inv.requested[0]==0);
|
|
for(int i=0;i<20;i++){s.Check(&f.mode);}assert(f.inv.submits==1&&s.m_Checks==5&&s.m_Jobs[0]->Terminal);
|
|
f.inv.last->OnFailed();s.Check(&f.mode);assert(f.inv.submits==1);}
|
|
// Both sync/async success terminate even if the optic disappears before physical observation.
|
|
for(int sync=0;sync<2;sync++){Fixture f;f.inv.callback=sync;ERR_EnemyOptics s(&f.actor,&f.group);s.Check(&f.mode);
|
|
if(!sync){f.inv.last->OnAllComplete();}assert(s.Check(&f.mode));assert(f.inv.submits==1&&!s.m_Jobs[0]->Confirmed);}
|
|
// Callback first terminal result wins; never turn a success into another grant.
|
|
{Fixture f;ERR_EnemyOptics s(&f.actor,&f.group);s.Check(&f.mode);f.inv.last->OnComplete();f.inv.last->OnFailed();s.Check(&f.mode);assert(f.inv.submits==1);}
|
|
// Explicit failures permit a different prefab/mount, but only one request per check and never fifth.
|
|
for(int kind=0;kind<3;kind++){Fixture f;f.inv.accept=kind!=0;f.inv.callback=kind==1?2:0;ERR_EnemyOptics s(&f.actor,&f.group);
|
|
for(int i=0;i<5;i++){s.Check(&f.mode);if(kind==2&&f.inv.last)f.inv.last->OnAllFailed();}
|
|
assert(f.inv.submits==4&&s.m_Jobs[0]->Rejected.Count()>=3);assert(s.m_Jobs[0]->Attempt==48);}
|
|
{Fixture f;f.inv.accept=false;f.inv.callback=1;ERR_EnemyOptics s(&f.actor,&f.group);s.Check(&f.mode);s.Check(&f.mode);assert(f.inv.submits==1);}
|
|
// Delayed inventory is bounded; fifth check can observe a starting rifle but cannot issue it.
|
|
{Fixture f;f.actor.inventory=nullptr;ERR_EnemyOptics s(&f.actor,&f.group);for(int i=0;i<4;i++)assert(!s.Check(&f.mode));f.actor.inventory=&f.inv;assert(s.Check(&f.mode));assert(f.inv.submits==0);}
|
|
{Fixture f;f.inv.fit=false;ERR_EnemyOptics s(&f.actor,&f.group);for(int i=0;i<5;i++)s.Check(&f.mode);assert(f.inv.submits==0&&f.inv.checks==28);}
|
|
// Exact original actor and original physical weapon identity are terminal; no picked-up replacements.
|
|
{Fixture f;f.inv.fit=false;ERR_EnemyOptics s(&f.actor,&f.group);s.Check(&f.mode);Entity replacement;replacement.weapon=&f.weapon;replacement.storage=&f.storage;
|
|
f.manager.weapons={&replacement};f.inv.fit=true;assert(s.Check(&f.mode));f.manager.weapons={&f.rifle};s.Check(&f.mode);assert(f.inv.submits==0&&s.m_Jobs.Count()==1);}
|
|
{Fixture f;ERR_EnemyOptics s(&f.actor,&f.group);s.Check(&f.mode);f.actor.group=&f.other;assert(s.Check(&f.mode));f.actor.group=&f.group;f.inv.last->OnFailed();s.Check(&f.mode);assert(f.inv.submits==1);}
|
|
{Fixture f;ERR_EnemyOptics s(&f.actor,&f.group);s.Check(&f.mode);s.Stop();f.inv.last->OnFailed();s.Check(&f.mode);assert(f.inv.submits==1);}
|
|
{Fixture f;f.actor.eligible=false;ERR_EnemyOptics s(&f.actor,&f.group);assert(s.Check(&f.mode));assert(f.inv.submits==0);}
|
|
// Duplicate native weapon roster entries don't create duplicate requests; nonrifles are ignored.
|
|
{Fixture f;f.manager.weapons={&f.rifle,&f.rifle};ERR_EnemyOptics s(&f.actor,&f.group);s.Check(&f.mode);assert(f.inv.submits==1&&s.m_Jobs.Count()==1);}
|
|
{Fixture f;f.weapon.kind=EWeaponType::WT_HANDGUN;ERR_EnemyOptics s(&f.actor,&f.group);for(int i=0;i<5;i++)s.Check(&f.mode);assert(f.inv.submits==0);}
|
|
// Malformed/oversized hierarchies fail closed, including cycles and too-deep adapters.
|
|
{Fixture f;f.rifle.child=&f.rifle;ERR_EnemyOptics s(&f.actor,&f.group);s.Check(&f.mode);assert(f.inv.submits==0);}
|
|
{Fixture f;Entity child;f.rifle.child=&child;child.sibling=&child;ERR_EnemyOptics s(&f.actor,&f.group);s.Check(&f.mode);assert(f.inv.submits==0);}
|
|
{Fixture f;for(int i=0;i<17;i++)f.weapon.slots.Insert(&f.slot);ERR_EnemyOptics s(&f.actor,&f.group);s.Check(&f.mode);assert(f.inv.submits==0);}
|
|
{Fixture f;for(int i=0;i<17;i++)f.storage.slots.Insert(&f.slot);ERR_EnemyOptics s(&f.actor,&f.group);s.Check(&f.mode);assert(f.inv.submits==0);}
|
|
{Fixture f;for(int i=0;i<9;i++)f.manager.weapons.Insert(&f.rifle);ERR_EnemyOptics s(&f.actor,&f.group);assert(s.Check(&f.mode));assert(f.inv.submits==0);}
|
|
}
|
|
'''
|
|
run(preamble+code+main)
|
|
print('Enemy optics actual-service ownership, preservation, callback, deadline and bounded-inspection policies passed')
|