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:
+19
-12
@@ -30,9 +30,9 @@ ammo=ammo.replace('class ','struct ').replace('ERR_HQAmmoSnapshot Snapshot(', 'E
|
||||
ammo=ammo.replace('ERR_HQAmmoSnapshot copy', 'ERR_HQAmmoSnapshot* copy').replace('copy.', 'copy->')
|
||||
ammo=re.sub(r'\b(bool|int) (\w+);',r'\1 \2=0;',ammo).replace('\n}\n','\n};\n')
|
||||
rules='\n'.join(cpp(body(P,s)) for s in ('static bool Valid(', 'static bool RequestDue(',
|
||||
'static bool InvestigationDue(', 'static int SquadLimit('))
|
||||
'static bool InvestigationDue(', 'static int SquadLimit(', 'static int Aggression(', 'static int Hunters('))
|
||||
methods='\n'.join(cpp(body(P,s)) for s in ('int Count()', 'ERR_HQWatchEntry Find(', 'ERR_HQWatchEntry At(',
|
||||
'int ConfirmedLosses()', 'int SquadLimit(int baseline)', 'bool ArmedSearchBlocked(', 'protected ERR_HQWatchEntry Create(',
|
||||
'int IdentifiedBodies()', 'int Aggression()', 'int HunterTarget()', 'int ConfirmedLosses()', 'int SquadLimit(int baseline)', 'bool ArmedSearchBlocked(', 'protected ERR_HQWatchEntry Create(',
|
||||
'protected void ApplyLosses(', 'bool Ingest(', 'bool IngestBody(', 'bool RequestDue(string',
|
||||
'bool MarkRequested(', 'bool InvestigationDue(string', 'bool MarkInvestigating(', 'bool FinishInvestigation(', 'void Clear('))
|
||||
common='\n'.join(cpp(body(U,s)) for s in ('static bool Finite(', 'static bool PositionValid('))
|
||||
@@ -72,7 +72,7 @@ struct ERR_HQPursuitRules{
|
||||
};
|
||||
'''+helper+ammo+r'''
|
||||
struct ERR_HQReport{
|
||||
string TravelMode="body",CasualtySourceId="patrol-1";bool Distress=false;int MessageId=99,CasualtySlot=0,CasualtyStrength=4;
|
||||
string RoadStatus="road unknown"; string TravelMode="body",CasualtySourceId="patrol-1";bool Distress=false;int MessageId=99,CasualtySlot=0,CasualtyStrength=4;
|
||||
vector Position{900,30,900},Velocity;float ObservedAt=1000,DetailsObservedAt=-1;
|
||||
void* Target=nullptr;void* Source=nullptr;bool HasVelocity=false,Engaged=false;
|
||||
int SeenEscapists=-1;string SeenWeapons="unknown";
|
||||
@@ -86,7 +86,7 @@ struct ERR_HQStatusReport{
|
||||
struct ERR_HQWatchEntry{
|
||||
string SourceId;int MessageId=0,RequestId=0;vector Position;
|
||||
ERR_HQAmmoSnapshot* AmmoState=nullptr;
|
||||
int Strength=0,Count=-1,Fit=-1,ConfirmedLosses=0;
|
||||
int Strength=0,Count=-1,Fit=-1,ConfirmedLosses=0;Array<int> IdentifiedSlots;
|
||||
ERR_HQDeliveredCasualties* Casualties=new ERR_HQDeliveredCasualties();float LastBodyDeliveredAt=-1;
|
||||
float ObservedAt=-1,LastDeliveredAt=-1,AskedAt=-1,NextAttempt=0;
|
||||
bool Investigating=false,Closed=false;
|
||||
@@ -96,7 +96,7 @@ struct ERR_HQWatchRules{
|
||||
};
|
||||
struct Watch{
|
||||
Array<ERR_HQWatchEntry*>m_Entries;Map<string,ERR_HQWatchEntry*>m_BySource;
|
||||
int m_ConfirmedLosses=0;bool m_CapLogged=false;
|
||||
int m_ConfirmedLosses=0,m_IdentifiedBodies=0;bool m_CapLogged=false;
|
||||
'''+methods+r'''
|
||||
};
|
||||
struct ERR_MissionRules{static bool ContactFresh(float t,float now){return std::isfinite(t)&&std::isfinite(now)&&t>=0&&now>=t&&now-t<=120000;}};
|
||||
@@ -106,9 +106,9 @@ struct MemoryAdapter{int attempts=0;bool Ingest(ERR_HQReport* r,float){attempts+
|
||||
struct BodyAdapter{int confirmations=0;void ConfirmDelivered(void*){confirmations++;}};
|
||||
struct CoverageAdapter{int forgotten=0;void Forget(vector){forgotten++;}};
|
||||
struct JournalAdapter{void Write(string,string,int){}};
|
||||
string Format(string,int,int){return "delivered";}
|
||||
template<class... Args> string Format(string,Args...){return "delivered";}
|
||||
struct HQDelivery{
|
||||
Watch m_Watch;MemoryAdapter m_Memory;BodyAdapter m_Bodies;CoverageAdapter m_Coverage;JournalAdapter m_Journal;
|
||||
Watch m_Watch;MemoryAdapter m_Memory;BodyAdapter m_Bodies;struct RecoveryAdapter{int calls=0;void ReportBody(ERR_HQReport*){calls++;}} m_Recovery;CoverageAdapter m_Coverage;JournalAdapter m_Journal;
|
||||
Array<ERR_HQReport*>m_Contacts;
|
||||
'''+accept+r'''
|
||||
};
|
||||
@@ -212,12 +212,12 @@ int main(){
|
||||
assert(!watch.Ingest(&late,120001)&&watch.Ingest(&late,120000));
|
||||
assert(!watch.RequestDue("late",nan));
|
||||
Watch cap;ERR_HQStatusReport item;item.Count=0;item.Fit=0;item.ConfirmedLosses=4;item.DeathSlots={0,1,2,3};
|
||||
for(int i=0;i<128;i++){item.SourceId=std::to_string(i);assert(cap.Ingest(&item,1000));}
|
||||
assert(cap.Count()==128&&cap.ConfirmedLosses()==512);
|
||||
for(int i=0;i<256;i++){item.SourceId=std::to_string(i);assert(cap.Ingest(&item,1000));}
|
||||
assert(cap.Count()==256&&cap.ConfirmedLosses()==1024);
|
||||
item.SourceId="overflow";assert(!cap.Ingest(&item,1000));
|
||||
item.SourceId="overflow2";assert(!cap.Ingest(&item,1000)&&logs==1);
|
||||
item.SourceId="0";item.MessageId=2;item.ObservedAt=2000;
|
||||
assert(cap.Ingest(&item,2000)&&cap.ConfirmedLosses()==512&&cap.Count()==128);
|
||||
assert(cap.Ingest(&item,2000)&&cap.ConfirmedLosses()==1024&&cap.Count()==256);
|
||||
cap.Clear();assert(cap.Count()==0&&cap.ConfirmedLosses()==0&&!cap.Find("0"));
|
||||
// Delivered bodies merge exact original roster slots, never aggregate totals or body positions.
|
||||
Watch merged;ERR_HQStatusReport status;status.Count=3;status.Fit=2;status.ConfirmedLosses=1;status.DeathSlots={0};
|
||||
@@ -234,9 +234,16 @@ int main(){
|
||||
body.CasualtySlot=3;assert(merged.IngestBody(&body,4001));
|
||||
assert(known->Closed&&known->Count==0&&known->Fit==0&&merged.ConfirmedLosses()==4);
|
||||
assert(known->MessageId==2&&known->ObservedAt==3000&&known->Position.x==200);
|
||||
assert(merged.IngestBody(&body,4002)&&merged.ConfirmedLosses()==4); // replay or different observer
|
||||
assert(!merged.IngestBody(&body,4002)&&merged.ConfirmedLosses()==4); // replay or different observer
|
||||
status.MessageId=3;status.ObservedAt=5000;assert(merged.Ingest(&status,5000)&&known->Closed);
|
||||
assert(!merged.RequestDue("patrol-1",999999));
|
||||
assert(merged.IdentifiedBodies()==4&&merged.Aggression()==1&&merged.HunterTarget()==1);
|
||||
for(int bodies=0;bodies<=300;bodies++){
|
||||
assert(ERR_HQWatchRules::Aggression(bodies)==std::min(30,bodies/3));
|
||||
assert(ERR_HQWatchRules::Hunters(ERR_HQWatchRules::Aggression(bodies))==std::min(15,(bodies/3+1)/2));
|
||||
}
|
||||
assert(ERR_HQWatchRules::Aggression(-10)==0&&ERR_HQWatchRules::Hunters(-10)==0);
|
||||
merged.Clear();assert(merged.IdentifiedBodies()==0&&merged.HunterTarget()==0);
|
||||
// Exercise the actual HQ delivered-intake method and actual Report.Snapshot, including corpse deletion.
|
||||
HQDelivery intake;ERR_HQReport local;local.CasualtySourceId="deleted-during-uplink";
|
||||
local.CasualtyStrength=1;local.CasualtySlot=0;local.Target=&local;local.MessageId=501;
|
||||
@@ -279,4 +286,4 @@ for forbidden in ('IEntity','SCR_AIGroup','GetGame(','GetOrigin(','GetPlayers(',
|
||||
'GetControlledEntity(','Spawn(','CallLater(','ERR_HQMemory'):
|
||||
assert forbidden not in source,forbidden
|
||||
assert all(len(line)<120 for line in source.splitlines())
|
||||
print('PASS: delivered-only status snapshots, two missed check-ins/reply deadline, last reported coordinates, episode/replay guards, confirmed-loss idempotence and 128-record cap')
|
||||
print('PASS: delivered-only status snapshots, two missed check-ins/reply deadline, last reported coordinates, episode/replay guards, confirmed-loss idempotence and 256-record cap')
|
||||
|
||||
Reference in New Issue
Block a user