Merge owned systems into one addon while preserving resource identities. Gate player admission on the replicated prison scene, restore the original framed door, and hold enemy AI until escape. Correct scenario branding and author metadata, include publishing assets, and retain regression and native verification evidence.
116 lines
9.4 KiB
Python
116 lines
9.4 KiB
Python
"""Actual scoped binding and original turret identity predicate, with native doubles."""
|
|
import os,re
|
|
from pathlib import Path
|
|
from policy_harness import ROOT,run
|
|
helper=Path(os.environ.get('ERR_GUNNER_SOURCE', str(ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_EnemyGunnerSkill.c'))).read_text()
|
|
equipment=Path(os.environ.get('ERR_EQUIPMENT_SOURCE', str(ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_EnemyEquipment.c'))).read_text()
|
|
def convert(s):
|
|
s=re.sub(r'//[^\n]*','',s).replace('protected ','').replace('override ','')
|
|
s=s.replace('ERR_HQ.Cast(mode.Command())','mode->Command()').replace('TurretCompartmentSlot.Cast(slot)','dynamic_cast<TurretCompartmentSlot*>(slot)')
|
|
for cls in ('ERR_GameMode','SCR_AIGroupUtilityComponent','SCR_AIGroup','IEntity','ERR_HQ','ERR_ConvoyTask','ERR_ConvoyVehicle','ERR_ForceRecord','BaseCompartmentSlot'):
|
|
s=re.sub(r'\b'+cls+r' ([a-zA-Z_]\w*)',cls+r'* \1',s)
|
|
for obj in ('mode','group','actor','utility','hq','task','unit','record','slot'):
|
|
s=s.replace(obj+'.',obj+'->')
|
|
for field in ('Receipt','Entity'):
|
|
s=s.replace(field+'.',field+'->')
|
|
s=s.replace('Forces().','Forces()->')
|
|
for cls in ('ERR_EnemyEquipmentRules','ERR_EnemyGunnerSkill','ERR_ConvoyRules','ERR_Actors','Replication','SCR_Global','ERR_GameMode','EAISkill','ERR_ForceKind','ECompartmentType','CompartmentAccessComponent'):
|
|
s=s.replace(cls+'.',cls+'::')
|
|
s=s.replace('super.','NativeCombat::').replace('null','nullptr').replace('foreach (','for (')
|
|
s=re.sub(r'\b(bool|int|EAISkill|[A-Za-z_]\w*\*) ([A-Za-z_]\w*);',r'\1 \2 {};',s)
|
|
return s
|
|
rules=equipment[equipment.index('class ERR_EnemyEquipmentRules'):equipment.index('class ERR_EnemyEquipmentCallback')]
|
|
rules=convert(rules.replace('class ERR_EnemyEquipmentRules','struct ERR_EnemyEquipmentRules').replace('modded class SCR_AICombatComponent','struct SCR_AICombatComponent : NativeCombat')).replace('\n}\n','\n};\n')
|
|
# Declare helper before rules, then define its real method bodies after rules.
|
|
helper=helper[helper.index(' static bool CapturedGroup'):helper.rfind('}')]
|
|
helper=convert(helper).replace('static bool CapturedGroup','bool ERR_EnemyGunnerSkill::CapturedGroup').replace('static bool OriginalGunner','bool ERR_EnemyGunnerSkill::OriginalGunner').replace('static EAISkill Profile','EAISkill ERR_EnemyGunnerSkill::Profile')
|
|
run(r'''
|
|
#include <cassert>
|
|
#include <vector>
|
|
enum class EAISkill{ROOKIE=20,REGULAR=50,EXPERT=80,CYLON=100};
|
|
enum class ERR_ForceKind{CONVOY,GUARD};
|
|
enum class ECompartmentType{TURRET,CARGO};
|
|
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;using std::vector<T>::operator=;int Count(){return this->size();}};
|
|
namespace Replication{bool server=true;bool IsServer(){return server;}}
|
|
namespace SCR_Global{bool edit=false;bool IsEditMode(){return edit;}}
|
|
struct SCR_AIGroupUtilityComponent{bool military=true;bool IsMilitary(){return military;}};
|
|
struct SCR_AIGroup{SCR_AIGroupUtilityComponent utility;int players=0,faction=1;auto GetGroupUtilityComponent(){return &utility;}int GetPlayerCount(bool){return players;}int GetFaction(){return faction;}};
|
|
struct IEntity{virtual ~IEntity(){}bool alive=true,player=false;SCR_AIGroup* group=nullptr;IEntity* vehicle=nullptr;};
|
|
using Vehicle=IEntity;
|
|
struct BaseCompartmentSlot{virtual ~BaseCompartmentSlot(){}IEntity* occupant=nullptr;IEntity* vehicle=nullptr;ECompartmentType type=ECompartmentType::TURRET;auto GetType(){return type;}auto GetOccupant(){return occupant;}auto GetVehicle(){return vehicle;}};
|
|
struct TurretCompartmentSlot:BaseCompartmentSlot{};
|
|
struct ERR_ForceRecord{int Id=8;bool Physical=true,Eliminated=false;ERR_ForceKind Kind=ERR_ForceKind::CONVOY;};
|
|
struct Ledger{ERR_ForceRecord* record=nullptr;auto FindGroup(SCR_AIGroup*){return record;}};
|
|
struct ERR_ConvoyReceipt{bool Consumed=true;Array<int> Records={8};};
|
|
struct ERR_ConvoyVehicle{SCR_AIGroup* Group=nullptr;Vehicle* Entity=nullptr;bool Registered=true,Released=false;int Index=0;Array<IEntity*> Members;Array<BaseCompartmentSlot*> Seats;};
|
|
struct ERR_ConvoyTask{bool Ready=true,Stopped=false;ERR_ConvoyReceipt* Receipt=nullptr;Array<ERR_ConvoyVehicle*> Vehicles;};
|
|
struct ERR_HQ{ERR_ConvoyTask* task=nullptr;auto ConvoyTask(){return task;}};
|
|
struct ERR_GameMode{static inline ERR_GameMode* current=nullptr;bool active=true;ERR_HQ* hq=nullptr;Ledger* ledger=nullptr;static auto Get(){return current;}bool Active(){return active;}int EnemyFaction(){return 1;}auto Command(){return hq;}auto Forces(){return ledger;}};
|
|
namespace ERR_Actors{bool CanFight(IEntity* a){return a->alive;}bool Alive(IEntity* a){return a->alive;}bool Player(IEntity* a){return a->player;}auto Group(IEntity* a){return a->group;}}
|
|
namespace ERR_ConvoyRules{bool capture=false;bool Captured(Vehicle*,SCR_AIGroup* group){return capture||group->players>0;}bool PartOf(IEntity* a,IEntity* b){return a==b;}}
|
|
namespace CompartmentAccessComponent{auto GetVehicleIn(IEntity* actor){return actor->vehicle;}}
|
|
struct ERR_EnemyGunnerSkill{static bool CapturedGroup(ERR_GameMode*,SCR_AIGroup*);static bool OriginalGunner(ERR_GameMode*,SCR_AIGroup*,IEntity*);static EAISkill Profile(EAISkill,bool);};
|
|
struct NativeCombat{IEntity* owner=nullptr;EAISkill skill=EAISkill::REGULAR;int writes=0;auto GetOwner(){return owner;}auto GetAISkill(){return skill;}virtual void SetAISkill(EAISkill value){writes++;skill=value;}virtual void ResetAISkill(){skill=EAISkill::ROOKIE;}};
|
|
''' + rules + helper + r'''
|
|
int main(){
|
|
ERR_GameMode mode;ERR_GameMode::current=&mode;SCR_AIGroup group;IEntity actor,vehicle,other;actor.group=&group;actor.vehicle=&vehicle;
|
|
TurretCompartmentSlot slot;slot.occupant=&actor;slot.vehicle=&vehicle;BaseCompartmentSlot falseTurret;falseTurret.occupant=&actor;falseTurret.vehicle=&vehicle;
|
|
ERR_ConvoyVehicle unit;unit.Group=&group;unit.Entity=&vehicle;unit.Members={&actor};unit.Seats={&slot};
|
|
ERR_ConvoyReceipt receipt;ERR_ConvoyTask task;task.Receipt=&receipt;task.Vehicles={&unit};ERR_HQ hq;hq.task=&task;mode.hq=&hq;
|
|
ERR_ForceRecord record;Ledger ledger;ledger.record=&record;mode.ledger=&ledger;
|
|
assert(ERR_EnemyGunnerSkill::OriginalGunner(&mode,&group,&actor));
|
|
// Every native/canonical ownership fact independently matters.
|
|
for(int i=0;i<25;i++){
|
|
auto savedActor=actor;auto savedVehicle=vehicle;auto savedGroup=group;auto savedUnit=unit;auto savedTask=task;auto savedReceipt=receipt;auto savedRecord=record;auto savedSlot=slot;
|
|
if(i==0)Replication::server=false;
|
|
if(i==1)SCR_Global::edit=true;
|
|
if(i==2)mode.active=false;
|
|
if(i==3)actor.player=true;
|
|
if(i==4)actor.alive=false;
|
|
if(i==5)actor.group=nullptr;
|
|
if(i==6)group.players=1;
|
|
if(i==7)group.faction=2;
|
|
if(i==8)group.utility.military=false;
|
|
if(i==9)task.Stopped=true;
|
|
if(i==10)task.Ready=false;
|
|
if(i==11)receipt.Consumed=false;
|
|
if(i==12)unit.Registered=false;
|
|
if(i==13)unit.Released=true;
|
|
if(i==14)vehicle.alive=false;
|
|
if(i==15)record.Physical=false;
|
|
if(i==16)record.Eliminated=true;
|
|
if(i==17)record.Kind=ERR_ForceKind::GUARD;
|
|
if(i==18)record.Id=9;
|
|
if(i==19)unit.Members[0]=&other;
|
|
if(i==20)slot.occupant=&other;
|
|
if(i==21)unit.Seats[0]=&falseTurret;
|
|
if(i==22)slot.type=ECompartmentType::CARGO;
|
|
if(i==23)slot.vehicle=&other;
|
|
if(i==24)actor.vehicle=&other;
|
|
assert(!ERR_EnemyGunnerSkill::OriginalGunner(&mode,&group,&actor));
|
|
actor=savedActor;vehicle=savedVehicle;group=savedGroup;unit=savedUnit;task=savedTask;receipt=savedReceipt;record=savedRecord;slot=savedSlot;
|
|
Replication::server=true;SCR_Global::edit=false;mode.active=true;
|
|
}
|
|
ERR_ConvoyRules::capture=true;assert(!ERR_EnemyGunnerSkill::OriginalGunner(&mode,&group,&actor));ERR_ConvoyRules::capture=false;
|
|
// Current turret specialization must not overwrite the underlying requested skill.
|
|
SCR_AICombatComponent combat;combat.owner=&actor;combat.ERR_BindEnemySkill(&mode,&group);assert(combat.skill==EAISkill::CYLON);
|
|
combat.ResetAISkill();assert(combat.skill==EAISkill::CYLON);
|
|
combat.SetAISkill(static_cast<EAISkill>(90));assert(combat.skill==EAISkill::CYLON);
|
|
actor.vehicle=nullptr;slot.occupant=nullptr;combat.ERR_RefreshEnemySkill();assert(static_cast<int>(combat.skill)==90);
|
|
actor.vehicle=&vehicle;slot.occupant=&actor;combat.ERR_RefreshEnemySkill();assert(combat.skill==EAISkill::CYLON);
|
|
combat.SetAISkill(EAISkill::ROOKIE);slot.occupant=nullptr;combat.ERR_RefreshEnemySkill();assert(combat.skill==EAISkill::EXPERT);
|
|
slot.occupant=&actor;combat.ERR_RefreshEnemySkill();assert(combat.skill==EAISkill::CYLON);
|
|
// Captured group refresh cannot mutate any soldier.
|
|
ERR_ConvoyRules::capture=true;int writes=combat.writes;combat.ERR_RefreshEnemySkill();assert(combat.writes==writes);ERR_ConvoyRules::capture=false;
|
|
combat.ERR_ReleaseEnemySkill(&mode);assert(combat.skill==EAISkill::REGULAR);
|
|
// A legitimately high underlying setting stays high after leaving a gunner seat.
|
|
combat.SetAISkill(EAISkill::CYLON);combat.ERR_BindEnemySkill(&mode,&group);slot.occupant=nullptr;combat.ERR_RefreshEnemySkill();assert(combat.skill==EAISkill::CYLON);
|
|
combat.ERR_ReleaseEnemySkill(&mode);assert(combat.skill==EAISkill::CYLON);
|
|
// Ordinary original infantry retain the existing floor without convoy identity.
|
|
hq.task=nullptr;combat.SetAISkill(EAISkill::REGULAR);combat.ERR_BindEnemySkill(&mode,&group);assert(combat.skill==EAISkill::EXPERT);
|
|
combat.SetAISkill(static_cast<EAISkill>(90));assert(static_cast<int>(combat.skill)==90);
|
|
combat.ERR_ReleaseEnemySkill(&mode);assert(combat.skill==EAISkill::REGULAR);
|
|
}
|
|
''')
|
|
print('PASS: actual original turret identity, native/canonical guards, scoped >=80/>=100 profile, reset, dismount/reboard, requested-skill preservation and restoration')
|