120 lines
10 KiB
Python
120 lines
10 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','vehicle','record','slot'):
|
|
s=s.replace(obj+'.',obj+'->')
|
|
for field in ('Receipt','Entity'):
|
|
s=s.replace(field+'.',field+'->')
|
|
s=s.replace('Forces().','Forces()->').replace('Convoys().','Convoys()->')
|
|
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 ERR_ConvoyTask TaskForGroup'):helper.rfind('}')]
|
|
helper=convert(helper).replace('static ERR_ConvoyTask* TaskForGroup','ERR_ConvoyTask* ERR_EnemyGunnerSkill::TaskForGroup').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,unconscious=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;ERR_ConvoyTask* extra=nullptr;auto Convoys(){return this;}int Count(){return extra?2:1;}auto At(int index){return index==0?task:extra;}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;}bool IsMissionEnemy(int f){return f==1;}auto Command(){return hq;}auto Forces(){return ledger;}};
|
|
namespace ERR_Actors{bool CanFight(IEntity* a){return a->alive&&!a->unconscious;}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 ERR_ConvoyTask* TaskForGroup(ERR_HQ*,SCR_AIGroup*);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));
|
|
ERR_ConvoyTask empty;hq.task=∅hq.extra=&task;assert(ERR_EnemyGunnerSkill::OriginalGunner(&mode,&group,&actor));hq.task=&task;hq.extra=nullptr;
|
|
actor.unconscious=true;
|
|
assert(ERR_EnemyEquipmentRules::Owned(&mode,&group,&actor)&&!ERR_EnemyEquipmentRules::Eligible(&mode,&group,&actor));
|
|
actor.unconscious=false;
|
|
// 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(combat.skill==EAISkill::CYLON);
|
|
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::CYLON);
|
|
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 also receive maximum skill without convoy identity.
|
|
hq.task=nullptr;combat.SetAISkill(EAISkill::REGULAR);combat.ERR_BindEnemySkill(&mode,&group);assert(combat.skill==EAISkill::CYLON);
|
|
combat.SetAISkill(static_cast<EAISkill>(90));assert(combat.skill==EAISkill::CYLON);
|
|
combat.ERR_ReleaseEnemySkill(&mode);assert(combat.skill==EAISkill::REGULAR);
|
|
}
|
|
''')
|
|
print('PASS: actual original turret identity, native/canonical guards, scoped maximum-skill profile, reset, dismount/reboard, requested-skill preservation and restoration')
|