Add mission customization, HQ support and aggression-driven AI

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.
This commit is contained in:
2026-09-23 15:07:14 +02:00
parent 04bd19327b
commit 1b1a8b5173
242 changed files with 12153 additions and 875 deletions
+14 -13
View File
@@ -9,11 +9,11 @@ def convert(s):
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'):
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()->')
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 (')
@@ -22,8 +22,8 @@ def convert(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')
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>
@@ -44,12 +44,12 @@ struct Ledger{ERR_ForceRecord* record=nullptr;auto FindGroup(SCR_AIGroup*){retur
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;}};
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;}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 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(){
@@ -59,6 +59,7 @@ int main(){
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=&empty;hq.extra=&task;assert(ERR_EnemyGunnerSkill::OriginalGunner(&mode,&group,&actor));hq.task=&task;hq.extra=nullptr;
// 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;
@@ -96,9 +97,9 @@ int main(){
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=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::EXPERT);
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;
@@ -106,10 +107,10 @@ int main(){
// 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);
// 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 >=80/>=100 profile, reset, dismount/reboard, requested-skill preservation and restoration')
print('PASS: actual original turret identity, native/canonical guards, scoped maximum-skill profile, reset, dismount/reboard, requested-skill preservation and restoration')