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.
111 lines
7.4 KiB
Python
111 lines
7.4 KiB
Python
"""Execute actual HQ assignment ownership and failed-radio backoff bodies."""
|
|
from policy_harness import ROOT, body, run
|
|
import re
|
|
p=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQ.c'
|
|
unit='\n'.join(body(p,s) for s in ('void CancelPending(', 'ERR_HQTask PlannedTask(', 'void ReleaseAssignment(', 'void Release()'))
|
|
lost='\n'.join(body(p,s) for s in ('protected void FinishWatch(', 'protected void LostOrder(')).replace('protected ', '')
|
|
def adapt(code):
|
|
code=code.replace('.IsEmpty()', '.empty()')
|
|
code=code.replace('null','nullptr').replace('Math.','Math::').replace('ERR_GameMode.Get()', 'ERR_GameMode::Get()')
|
|
code=code.replace('SCR_EntityHelper.DeleteEntityAndChildren', 'SCR_EntityHelper::DeleteEntityAndChildren')
|
|
code=code.replace('ERR_HQUnit unit', 'ERR_HQUnit* unit')
|
|
code=code.replace('ERR_HQTask ', 'ERR_HQTask* ').replace('ERR_GameMode ', 'ERR_GameMode* ')
|
|
code=code.replace('ERR_ForceRecord record;', 'ERR_ForceRecord* record=nullptr;')
|
|
code=code.replace('ERR_HQHuntWindow hunt', 'ERR_HQHuntWindow* hunt')
|
|
code=code.replace('foreach (ERR_HQUnit other : m_Units)', 'for (ERR_HQUnit* other : m_Units)')
|
|
code=re.sub(r'\b(mode|record|Group|unit|other|task|planned|hunt)\.', r'\1->', code)
|
|
code=code.replace('mode->Forces().FindGroup', 'mode->Forces()->FindGroup')
|
|
return code
|
|
run(r'''
|
|
#include <cassert>
|
|
#include <vector>
|
|
#include <algorithm>
|
|
#include <string>
|
|
struct Math {static float Min(float a,float b){return std::min(a,b);}};
|
|
struct ERR_HQTask {bool ReleaseOnly=false;int BeliefId=0;std::string WatchSourceId,SecuritySite;int WatchMessageId=0,WatchRequestId=0;bool Investigation(){return !WatchSourceId.empty();}};
|
|
struct ERR_HQReport {};
|
|
struct ERR_HQRoute {};
|
|
struct AIWaypoint {int deleted=0;};
|
|
struct SCR_AIGroup {int removals=0; AIWaypoint* last=nullptr;
|
|
void RemoveWaypoint(AIWaypoint* p){removals++;last=p;}};
|
|
struct ERR_ForceRecord {bool Eliminated=false;int Duty=2,HomeDuty=1,DutyTarget=200,HomePosition=100;};
|
|
struct Ledger {ERR_ForceRecord* record=nullptr;ERR_ForceRecord* FindGroup(SCR_AIGroup*){return record;}};
|
|
struct ERR_GameMode {Ledger ledger;static ERR_GameMode* current;
|
|
static ERR_GameMode* Get(){return current;} Ledger* Forces(){return &ledger;}};
|
|
ERR_GameMode* ERR_GameMode::current=nullptr;
|
|
struct SCR_EntityHelper {static void DeleteEntityAndChildren(AIWaypoint* p){p->deleted++;}};
|
|
struct ERR_HQHuntWindow {float NextHunt=0;};
|
|
struct ERR_HQUnit {
|
|
SCR_AIGroup* Group=nullptr;AIWaypoint* Waypoint=nullptr;
|
|
ERR_HQTask *Task=nullptr,*PendingTask=nullptr;
|
|
ERR_HQReport *Assignment=nullptr,*PendingOrder=nullptr;
|
|
void* PendingConvoyOrder=nullptr;
|
|
ERR_HQRoute* Downlink=nullptr;bool PendingCheckin=false;
|
|
'''+adapt(unit)+r'''};
|
|
struct Watch {int calls=0;std::string source;int message=0,request=0;
|
|
bool FinishInvestigation(std::string s,int m,int r,float){calls++;source=s;message=m;request=r;return true;}};
|
|
struct HQ {Watch m_Watch;std::vector<ERR_HQUnit*> m_Units;ERR_HQHuntWindow window;
|
|
ERR_HQHuntWindow* Hunt(int){return &window;}
|
|
'''+adapt(lost)+r'''};
|
|
int main(){
|
|
ERR_GameMode mode;ERR_GameMode::current=&mode;ERR_ForceRecord record;mode.ledger.record=&record;
|
|
SCR_AIGroup group;ERR_HQReport oldReport,newReport;ERR_HQTask oldTask,newTask;ERR_HQRoute route;
|
|
oldTask.BeliefId=1;newTask.BeliefId=2;AIWaypoint oldWaypoint;
|
|
ERR_HQUnit u;u.Group=&group;u.Waypoint=&oldWaypoint;u.Assignment=&oldReport;u.Task=&oldTask;
|
|
u.PendingOrder=&newReport;u.PendingTask=&newTask;u.Downlink=&route;
|
|
assert(u.PlannedTask()==&newTask);
|
|
u.CancelPending();
|
|
assert(!u.PendingOrder&&!u.PendingTask&&!u.Downlink);
|
|
assert(u.Task==&oldTask&&u.Assignment==&oldReport&&u.Waypoint==&oldWaypoint);
|
|
assert(oldWaypoint.deleted==0&&group.removals==0&&record.Duty==2);
|
|
assert(u.PlannedTask()==&oldTask);
|
|
// Expiry of the current task must leave its replacement radio transaction intact.
|
|
u.PendingOrder=&newReport;u.PendingTask=&newTask;u.Downlink=&route;
|
|
u.ReleaseAssignment();
|
|
assert(!u.Assignment&&!u.Task&&!u.Waypoint);
|
|
assert(u.PendingOrder==&newReport&&u.PendingTask==&newTask&&u.Downlink==&route);
|
|
assert(oldWaypoint.deleted==1&&group.removals==1&&group.last==&oldWaypoint);
|
|
assert(record.Duty==record.HomeDuty&&record.DutyTarget==record.HomePosition);
|
|
u.ReleaseAssignment();assert(oldWaypoint.deleted==1&&group.removals==1);
|
|
u.Release();assert(!u.PendingOrder&&!u.PendingTask&&!u.Downlink);
|
|
// Elimination and absent group/mode cannot rewrite casualty state or double-delete.
|
|
AIWaypoint detached;u.Waypoint=&detached;u.Group=nullptr;record.Eliminated=true;record.Duty=9;
|
|
ERR_GameMode::current=nullptr;u.Release();assert(detached.deleted==1&&record.Duty==9);
|
|
ERR_GameMode::current=&mode;u.Group=&group;u.ReleaseAssignment();assert(record.Duty==9);
|
|
// A failed initial dispatch can retry after a short backoff.
|
|
HQ hq;hq.m_Units={&u};hq.window.NextHunt=300000;
|
|
u.PendingOrder=&newReport;u.PendingTask=&newTask;u.Downlink=&route;
|
|
hq.LostOrder(&u,1000);assert(hq.window.NextHunt==16000&&!u.PendingOrder);
|
|
// Failed retask preserves delivered assignment and its active hunt window.
|
|
u.Task=&newTask;u.Assignment=&oldReport;u.Waypoint=&oldWaypoint;
|
|
u.PendingOrder=&newReport;u.PendingTask=&newTask;u.Downlink=&route;hq.window.NextHunt=300000;
|
|
hq.LostOrder(&u,1000);assert(hq.window.NextHunt==300000&&u.Task==&newTask&&u.Waypoint==&oldWaypoint);
|
|
// Another group's pending hunt prevents duplicate retry commitments.
|
|
u.Task=nullptr;u.Assignment=nullptr;u.Waypoint=nullptr;
|
|
ERR_HQUnit peer;peer.PendingTask=&newTask;hq.m_Units.push_back(&peer);
|
|
u.PendingTask=&newTask;u.PendingOrder=&newReport;u.Downlink=&route;
|
|
hq.LostOrder(&u,2000);assert(hq.window.NextHunt==300000);
|
|
// A failed pending watch investigation completes only that pending lineage.
|
|
ERR_HQTask activeWatch,pendingWatch;activeWatch.WatchSourceId="patrol-A";activeWatch.WatchMessageId=41;activeWatch.WatchRequestId=1;
|
|
pendingWatch.WatchSourceId="patrol-B";pendingWatch.WatchMessageId=42;pendingWatch.WatchRequestId=2;
|
|
u.Task=&activeWatch;u.Waypoint=&oldWaypoint;u.Assignment=&oldReport;
|
|
u.PendingTask=&pendingWatch;u.PendingOrder=&newReport;u.Downlink=&route;
|
|
hq.LostOrder(&u,3000);assert(hq.m_Watch.calls==1&&hq.m_Watch.source=="patrol-B"&&hq.m_Watch.message==42&&hq.m_Watch.request==2);
|
|
assert(u.Task==&activeWatch&&u.Waypoint==&oldWaypoint&&u.Assignment==&oldReport);
|
|
assert(!u.PendingTask&&!u.PendingOrder&&!u.Downlink&&hq.window.NextHunt==300000);
|
|
// Dropped status-check-in transport cannot finish the delivered investigation.
|
|
u.PendingCheckin=true;u.Downlink=&route;hq.LostOrder(&u,4000);
|
|
assert(!u.PendingCheckin&&!u.Downlink&&u.Task==&activeWatch&&hq.m_Watch.calls==1);
|
|
ERR_HQTask recall;recall.ReleaseOnly=true;u.PendingTask=&recall;u.PendingOrder=&newReport;u.Downlink=&route;
|
|
hq.LostOrder(&u,5000);assert(!u.PendingTask&&u.Task==&activeWatch&&u.Waypoint==&oldWaypoint&&hq.m_Watch.calls==1);
|
|
u.Task=nullptr;u.Waypoint=nullptr;u.Assignment=nullptr;
|
|
ERR_HQTask security;security.SecuritySite="radio-1";u.PendingTask=&security;u.PendingOrder=&newReport;u.Downlink=&route;
|
|
hq.window.NextHunt=300000;hq.LostOrder(&u,5000);
|
|
assert(!u.PendingTask&&!u.Downlink&&hq.window.NextHunt==300000&&hq.m_Watch.calls==1);
|
|
// Unrelated belief cannot suppress bounded retry, and existing sooner retry remains sooner.
|
|
peer.PendingTask=&oldTask;u.PendingTask=&newTask;hq.window.NextHunt=5000;
|
|
hq.LostOrder(&u,2000);assert(hq.window.NextHunt==5000);
|
|
}
|
|
''')
|
|
print('PASS: actual assignment cancellation/release preserves radio ownership, native waypoint lifecycle and failed-order retry boundaries')
|