Embed RHS 1.9 compatibility; record Workshop 0.0.141
continuous-integration/drone/push Build is passing

This commit is contained in:
2026-11-05 16:39:35 +01:00
parent 9418b6950a
commit 9caed3f12d
325 changed files with 76722 additions and 25 deletions
+57 -7
View File
@@ -7,13 +7,13 @@ ROAD=Path(os.environ.get('ERR_HQ_ROAD_SOURCE',ROOT/'addons/core/Scripts/Game/ERR
def cpp(s):
s=s.replace('EngineerMine.', 'EngineerMine->')
s=s.replace('ref ', '')
s=s.replace('protected ','').replace('null','nullptr').replace('string.Format','Format')
for name in ('ERR_SupplyConvoyTactics','Math','ERR_GameMode','SCR_EntityHelper','ERR_HQPursuitRules','AIWaypoint','ERR_Actors','ERR_Resources','ERR_ForceDuty','ERR_HQRoadIntercept','ERR_ForceRules','ERR_HQPursuitRole','ERR_HQMemoryKind','ERR_HQOperationRole'):
s=s.replace('override ', '').replace('protected ','').replace('null','nullptr').replace('string.Format','Format')
for name in ('ERR_SupplyConvoyTactics','Math','ERR_GameMode','SCR_EntityHelper','ERR_HQPursuitRules','AIWaypoint','ERR_Actors','ERR_Resources','ERR_ForceDuty','ERR_HQRoadIntercept','ERR_ForceRules','ERR_HQPursuitRole','ERR_HQMemoryKind','ERR_HQOperationRole','ERR_HQExecutionRules'):
s=s.replace(name+'.',name+'::')
for typ in ('ERR_HQUnit','ERR_HQTask','ERR_GameMode','ERR_HQReport','ERR_HQBelief','ERR_ForceRecord','ERR_HQHuntWindow','AIWaypoint'):
for typ in ('ERR_HQUnit','ERR_HQTask','ERR_GameMode','ERR_HQReport','ERR_HQBelief','ERR_ForceRecord','ERR_HQHuntWindow','AIWaypoint','ERR_HQSquadJudgment','ERR_HQOperationSlot'):
s=re.sub(r'\b'+typ+r' (?=\w)',typ+'* ',s)
s=re.sub(r'foreach \((ERR_HQUnit\*) (\w+) : ([^)]+)\)',r'for (\1 \2 : \3)',s)
for name in ('unit','other','task','planned','order','belief','record','mode','Group','hunt','waypoint','m_Mode','lostActive','lostPending'):
for name in ('unit','other','task','planned','order','belief','record','mode','Group','hunt','waypoint','m_Mode','lostActive','lostPending','squad','slot'):
s=s.replace(name+'.',name+'->')
s=s.replace('ResourceName ', 'int ').replace('.IsEmpty()', '.empty()')
s=s.replace('PendingOrder.', 'PendingOrder->').replace('Assignment.', 'Assignment->')
@@ -28,6 +28,8 @@ if 'protected void RememberCompletedHunt(' in P.read_text(): methods=cpp(body(P,
if 'protected void ReleaseLostForce(' in P.read_text(): methods=cpp(body(P,'protected void ReleaseLostForce('))+'\n'+methods
missing=cpp(body(P,'if (!unit.Group && !unit.Uplink)')).replace('m_Units.Remove(i)', 'm_Units.erase(m_Units.begin()+i)')
cleanup=cpp(body(P,' foreach (ERR_HQUnit unit : m_Units)\n {\n if (!unit.Assignment && !unit.PendingOrder) continue;\n bool security = SecurityAssigned(unit);'))
C=Path(os.environ.get('ERR_HQ_COORDINATED_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQCoordinated.c'))
methods=cpp(body(C,'override protected bool CoordinatedDelivery('))+'\n'+methods
R=Path(os.environ.get('ERR_HQ_PURSUIT_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQPursuit.c'))
rules='\n'.join(cpp(body(R,x)) for x in ('static bool Finite(', 'static bool PositionValid(', 'static bool Actionable(', 'static bool Heading(', 'static ERR_HQPursuitRole Role('))
eligible=cpp(body(ROAD,'static bool Eligible('))
@@ -49,7 +51,7 @@ struct ERR_HQPursuitRules{
'''+rules+r'''
};
struct Math{static float Min(float a,float b){return std::min(a,b);}static float Max(float a,float b){return std::max(a,b);}};
struct ERR_HQTask{ERR_HQOperationRole OperationRole=ERR_HQOperationRole::SEARCH;int OperationId=0,ExecutionId=0;string SecuritySite;bool Maneuver=false,RoadIntercept=false,ReleaseOnly=false;ERR_HQTask* ReplacesTask=nullptr;string WatchSourceId;int WatchMessageId=0,WatchRequestId=0;
struct ERR_HQTask{ERR_HQOperationRole OperationRole=ERR_HQOperationRole::SEARCH;int OperationId=0,ExecutionId=0,OperationRevision=2,SupportSourceId=0;float SupportUntil=0;string SecuritySite;bool Maneuver=false,RoadIntercept=false,ReleaseOnly=false;ERR_HQTask* ReplacesTask=nullptr;string WatchSourceId;int WatchMessageId=0,WatchRequestId=0;
int BeliefId=1,BeliefRevision=2;ERR_HQPursuitRole Role=ERR_HQPursuitRole::SEARCH;vector Destination{40,0,50};float SearchRadiusMetres=60,QueuedAt=0,ExpiresAt=60000;bool Investigation(){return !WatchSourceId.empty();}string Name(){return "search";}};
struct ERR_HQReport{string TravelMode="on foot";int MessageId=10;float ObservedAt=0;};
struct ERR_HQRoute{int MessageId=20;};
@@ -67,7 +69,7 @@ struct MineJob {int stops=0;void Stop(){stops++;}};
struct TestWorld{operator bool(){return true;}float GetWorldTime(){return 0;}};struct TestGame{operator bool(){return true;}TestWorld GetWorld(){return {};}};TestGame GetGame(){return {};}struct ExecutionDouble{void Update(void*,float){}operator bool(){return false;}void Stop(float,bool){}void Receive(ERR_HQTask*,float){}};
struct ERR_HQUnit{ExecutionDouble Execution;MineJob* EngineerMine=nullptr;int Id=1;string ForceSourceId="original";SCR_AIGroup* Group=nullptr;AIWaypoint* Waypoint=nullptr;
ERR_HQTask* Task=nullptr;ERR_HQTask* PendingTask=nullptr;ERR_HQReport* Assignment=nullptr;ERR_HQReport* PendingOrder=nullptr;
ERR_HQRoute* Downlink=nullptr;ERR_HQRoute* Uplink=nullptr;void* PendingConvoyOrder=nullptr;bool HomeGuard=false,PendingCheckin=false,eligible=true,fighting=false,PlanEligible=true;
ERR_HQRoute* Downlink=nullptr;ERR_HQRoute* Uplink=nullptr;void* PendingConvoyOrder=nullptr;int LogisticsReservation=0;bool ReservedForSupport=false,HomeGuard=false,PendingCheckin=false,eligible=true,fighting=false,PlanEligible=true;
bool Eligible(ERR_GameMode*){return eligible&&Group;}
'''+units+r'''
};
@@ -79,7 +81,11 @@ struct Memory{ERR_HQBelief* belief=nullptr;ERR_HQBelief* GetById(int id){return
struct Watch{bool empty=false;int finished=0;bool ArmedSearchBlocked(string,float){return empty;}bool FinishInvestigation(string,int,int,float){finished++;return true;}};
struct Journal{int writes=0;void Write(string,string,int=0){writes++;}};
struct ERR_HQHuntWindow{float CompletedUntil=0;float NextHunt=200000;};
struct HQ{bool CoordinatedDelivery(ERR_HQUnit*,ERR_HQTask*,float){return false;}struct Operations{void Release(int,int,float){}}m_Operations;ERR_GameMode* m_Mode;Memory m_Memory;Watch m_Watch;Journal m_Journal;std::vector<ERR_HQUnit*>m_Units;ERR_HQHuntWindow hunt,otherHunt;bool watchValid=true;
struct ERR_HQSquadJudgment{bool Recovering=false;};
struct ERR_HQOperationSlot{ERR_HQTask* Task=nullptr;};
struct ERR_HQExecutionRules{static bool SupportFresh(float until,float now){return std::isfinite(until)&&until>now&&until<=now+20000;}};
struct Operations{ERR_HQSquadJudgment* squad=nullptr;ERR_HQOperationSlot* slot=nullptr;ERR_HQSquadJudgment* Judgment(int id){assert(id==1);return squad;}ERR_HQOperationSlot* Find(int id){assert(id==1);return slot;}void Release(int,int,float){}};
struct HQ{bool m_Coordinated=false;Operations m_Operations;ERR_GameMode* m_Mode;Memory m_Memory;Watch m_Watch;Journal m_Journal;std::vector<ERR_HQUnit*>m_Units;ERR_HQHuntWindow hunt,otherHunt;bool watchValid=true;
bool Fighting(ERR_HQUnit* u){return u->fighting;}bool WatchTaskValid(ERR_HQTask*){return watchValid;}ERR_HQHuntWindow* Hunt(int id){return id==2?&otherHunt:&hunt;}
'''+methods+'\nvoid Cleanup(float now,int assigned,int globalLimit){'+cleanup+'}\nvoid RemoveMissing(float now){for(int i=int(m_Units.size())-1;i>=0;i--){ERR_HQUnit* unit=m_Units[i];'+missing+'}}'+r'''
};
@@ -89,6 +95,50 @@ struct Fixture{ERR_GameMode mode;HQ hq;ERR_HQUnit unit;SCR_AIGroup group;AIWaypo
void Retained(){assert(unit.Task==&active&&unit.Assignment==&oldReport&&unit.Waypoint==&previous&&previous.deletes==0&&group.removals==0&&!unit.PendingTask&&!unit.PendingOrder&&!unit.Downlink);}
};
int main(){
// Real delivery and actual coordinated identity admission must permit rest for
// a known-empty recovering squad, without admitting ordinary reserve/attacks.
for(bool withdrawing:{false,true}){
Fixture f;ERR_HQSquadJudgment mood;ERR_HQOperationSlot slot;
mood.Recovering=true;slot.Task=&f.next;f.hq.m_Operations.squad=&mood;f.hq.m_Operations.slot=&slot;
f.hq.m_Coordinated=true;f.hq.m_Watch.empty=true;f.next.OperationId=1;f.next.ExecutionId=12;
f.next.OperationRole=withdrawing?ERR_HQOperationRole::WITHDRAW:ERR_HQOperationRole::RESERVE;
// WITHDRAW keeps its original exemption without a recovery judgment.
if(withdrawing)f.hq.m_Operations.squad=nullptr;
f.hq.DeliverOrder(&f.unit,1000);
assert(f.unit.Task==&f.next&&f.unit.Assignment==&f.newReport&&f.unit.Waypoint&&f.unit.Waypoint->resource==2);
assert(ERR_Actors::spawns==1&&f.group.additions==1&&f.previous.deletes==1&&!f.unit.PendingTask);
}
for(int invalid=0;invalid<15;invalid++){
Fixture f;ERR_HQSquadJudgment mood;ERR_HQOperationSlot slot;ERR_HQTask foreign;
mood.Recovering=true;slot.Task=&f.next;f.hq.m_Operations.squad=&mood;f.hq.m_Operations.slot=&slot;
f.hq.m_Coordinated=true;f.hq.m_Watch.empty=true;f.next.OperationId=1;f.next.ExecutionId=12;f.next.OperationRole=ERR_HQOperationRole::RESERVE;
if(invalid==0)mood.Recovering=false;
if(invalid==1)f.hq.m_Operations.squad=nullptr;
if(invalid==2)f.next.OperationId=0;
if(invalid==3)f.hq.m_Coordinated=false;
if(invalid==4)slot.Task=&foreign;
if(invalid==5)f.hq.m_Operations.slot=nullptr;
if(invalid==6)f.next.ExpiresAt=1000;
if(invalid==7)f.unit.eligible=false;
if(invalid==8)f.next.BeliefRevision=3;
if(invalid==9)f.next.OperationRevision=3;
if(invalid==10)f.unit.HomeGuard=true;
if(invalid==11)f.unit.ReservedForSupport=true;
if(invalid==12)f.unit.LogisticsReservation=1;
if(invalid==13)f.hq.m_Memory.belief=nullptr;
if(invalid==14)f.belief.Confidence=0;
f.hq.DeliverOrder(&f.unit,1000);f.Retained();assert(ERR_Actors::spawns==0);
}
for(auto role:{ERR_HQOperationRole::SEARCH,ERR_HQOperationRole::OBSERVE,ERR_HQOperationRole::FIX,ERR_HQOperationRole::FLANK,ERR_HQOperationRole::BLOCK}){
Fixture f;ERR_HQSquadJudgment mood;ERR_HQOperationSlot slot;mood.Recovering=true;slot.Task=&f.next;
f.hq.m_Coordinated=true;f.hq.m_Watch.empty=true;f.hq.m_Operations.squad=&mood;f.hq.m_Operations.slot=&slot;
f.next.OperationId=1;f.next.OperationRole=role;f.hq.DeliverOrder(&f.unit,1000);f.Retained();assert(ERR_Actors::spawns==0);
}
// A tactical reserve still uses the ordinary ammo gate: with ammo present it
// is valid even without a recovery judgment; empty it is rejected above.
{Fixture f;ERR_HQOperationSlot slot;slot.Task=&f.next;f.hq.m_Coordinated=true;f.hq.m_Operations.slot=&slot;
f.next.OperationId=1;f.next.OperationRole=ERR_HQOperationRole::RESERVE;f.hq.DeliverOrder(&f.unit,1000);assert(f.unit.Task==&f.next&&ERR_Actors::spawns==1);}
// The copied radius is used before installation even if current uncertainty grows.
for(float radius:{45.f,60.f,110.f}){Fixture f;f.next.SearchRadiusMetres=radius;f.belief.Radius=500;
float deadline=f.next.ExpiresAt;f.hq.DeliverOrder(&f.unit,1000);