Embed RHS 1.9 compatibility; record Workshop 0.0.141
continuous-integration/drone/push Build is passing
continuous-integration/drone/push Build is passing
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
"""Delivered ammo gates new tasks without becoming active-order cancellation."""
|
||||
from policy_harness import ROOT, body, run
|
||||
H=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQ.c'
|
||||
from pathlib import Path
|
||||
import os
|
||||
H=Path(os.environ.get('ERR_HQ_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQ.c'))
|
||||
for method in ('protected void Investigate(', 'protected void Retask(', 'protected void Assign('):
|
||||
s=body(H,method)
|
||||
assert 'if (m_Watch.ArmedSearchBlocked(unit.ForceSourceId, now)) continue;' in s
|
||||
@@ -8,20 +10,23 @@ for method in ('protected void Investigate(', 'protected void Retask(', 'protect
|
||||
s=body(H,'protected void DeliverOrder(')
|
||||
s=s[:s.index(' if (task.Investigation())')]+' admitted++;\n }'
|
||||
s=s.replace('protected ','').replace('null','nullptr').replace('.IsEmpty()', '.empty()').replace('ERR_HQUnit unit','ERR_HQUnit* unit').replace('ERR_HQOperationRole.','ERR_HQOperationRole::')
|
||||
s=s.replace('ERR_HQSquadJudgment squad', 'ERR_HQSquadJudgment* squad').replace('squad.', 'squad->')
|
||||
s=s.replace('ERR_HQReport order','ERR_HQReport* order').replace('ERR_HQTask task','ERR_HQTask* task')
|
||||
for name in ('unit','task','order'):s=s.replace(name+'.',name+'->')
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
struct ERR_HQReport{};
|
||||
enum class ERR_HQOperationRole{SEARCH,WITHDRAW};
|
||||
enum class ERR_HQOperationRole{SEARCH,WITHDRAW,RESERVE};
|
||||
struct ERR_HQSquadJudgment{bool Recovering=false;};
|
||||
struct Operations{ERR_HQSquadJudgment* squad=nullptr;ERR_HQSquadJudgment* Judgment(int id){assert(id==1);return squad;}};
|
||||
struct ERR_HQTask{int OperationId=0;ERR_HQOperationRole OperationRole=ERR_HQOperationRole::SEARCH;std::string SecuritySite;float ExpiresAt=100000;bool ReleaseOnly=false;ERR_HQTask* ReplacesTask=nullptr;};
|
||||
struct ERR_HQUnit{ERR_HQReport* PendingOrder=nullptr;ERR_HQTask* PendingTask=nullptr;ERR_HQTask* Task=nullptr;
|
||||
std::string ForceSourceId="original-1";bool eligible=true;int cancellations=0;
|
||||
int Id=1;std::string ForceSourceId="original-1";bool eligible=true;int cancellations=0;
|
||||
bool Eligible(void*){return eligible;}void CancelPending(){cancellations++;PendingOrder=nullptr;PendingTask=nullptr;}};
|
||||
struct Watch {bool empty=false;int calls=0;bool ArmedSearchBlocked(std::string s,float){assert(s=="original-1");calls++;return empty;}};
|
||||
struct Journal {void Write(std::string,std::string){}};
|
||||
struct HQ{Watch m_Watch;Journal m_Journal;void* m_Mode=nullptr;int admitted=0,lost=0,released=0;
|
||||
struct HQ{bool m_Coordinated=false;Operations m_Operations;Watch m_Watch;Journal m_Journal;void* m_Mode=nullptr;int admitted=0,lost=0,released=0;
|
||||
void LostOrder(ERR_HQUnit* u,float){lost++;u->CancelPending();}void ReleaseUnit(ERR_HQUnit* u,float){released++;u->Task=nullptr;u->CancelPending();}
|
||||
'''+s+r'''
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -45,11 +45,11 @@ struct SCR_ManualCamera:CameraBase{
|
||||
// changes only its own currently selected camera, never a foreign current one.
|
||||
~SCR_ManualCamera(){Terminate();if(manager.current==this&&previous)manager.SetCamera(previous);deleted++;}
|
||||
};
|
||||
struct SCR_HintManagerComponent{int attempts=0;static SCR_HintManagerComponent*GetInstance(){static SCR_HintManagerComponent h;return &h;}bool ShowCustom(string,string,int){attempts++;return true;}};
|
||||
struct SCR_HintManagerComponent{int attempts=0;bool silent=false;static SCR_HintManagerComponent*GetInstance(){static SCR_HintManagerComponent h;return &h;}bool ShowCustom(string,string,int,bool quiet=false){attempts++;silent=quiet;return true;}};
|
||||
struct SCR_RespawnSystemComponent{int clears=0;static SCR_RespawnSystemComponent*GetInstance(){static SCR_RespawnSystemComponent r;return &r;}void DestroyLoadingPlaceholder(){clears++;}};
|
||||
struct SCR_EditorManagerEntity{bool opened=false;int closes=0;static SCR_EditorManagerEntity*GetInstance(){static SCR_EditorManagerEntity e;return &e;}bool IsOpened(){return opened;}void Close(bool){opened=false;closes++;}};
|
||||
struct RplId{static int Invalid(){return -1;}};namespace LogLevel{enum{WARNING};}void Print(const char*,int){}
|
||||
namespace System{bool IsConsoleApp(){return false;}}
|
||||
namespace System{int now=0;int GetTickCount(){return now;}bool IsConsoleApp(){return false;}}
|
||||
struct Queue{std::set<void*>owners;int added=0;void CallLater(void*owner,int,bool){owners.insert(owner);added++;}void Remove(void*owner){owners.erase(owner);}} queue;
|
||||
struct World{float now=1;float GetWorldTime(){return now;}} world;
|
||||
struct Game{void*local=nullptr;int spawns=0;void*GetPlayerController(){return local;}Queue*GetCallqueue(){return &queue;}World*GetWorld(){return &world;}CameraManager*GetCameraManager(){return &manager;}
|
||||
@@ -59,7 +59,8 @@ struct Controller{
|
||||
string ERR_SelfEpinephrineHint(){return "";}
|
||||
IEntity actor=nullptr,m_ERRRecoveryBody=nullptr;float m_ERRRecoveryDeadline=-1;
|
||||
bool m_ERRObserverMonitor=false,m_ERRObserverReady=false,m_ERRShownObserverAvailable=false;
|
||||
string m_ERRShownRecoveryStatus;
|
||||
string m_ERRShownRecoveryStatus,m_ERRPendingRecoveryStatus;
|
||||
int m_ERRRecoveryStatusSince=0;
|
||||
int m_ERRObserverReason=0,m_ERRShownObserverReason=0,m_ERRObservedId=1,starts=0;
|
||||
Vec m_ERRObserverAnchor;SCR_ManualCamera*m_ERRObserverCamera=nullptr;CameraBase*playerCamera=nullptr;
|
||||
IEntity GetControlledEntity(){return actor;}CameraBase*GetPlayerCamera(){return playerCamera;}
|
||||
|
||||
@@ -114,3 +114,100 @@ p.m_ERRRevivablePlayer=false;assert(!p.ERR_CanRecoverNaturally());
|
||||
}
|
||||
''')
|
||||
print('PASS: viable passive recovery survives native delay; bleeding, disabled/full effects and low-blood resilience do not block defeat')
|
||||
|
||||
|
||||
"""Actual combat injector admission/effect; native component/effect boundaries doubled.
|
||||
No animation, inventory consumption, replication or native base-effect qualification.
|
||||
"""
|
||||
from pathlib import Path
|
||||
import os, re
|
||||
from policy_harness import ROOT, body, run
|
||||
p=Path(os.environ.get('ERR_EPINEPHRINE_SOURCE', ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_Epinephrine.c'))
|
||||
s=p.read_text()
|
||||
def method(class_name, signature):
|
||||
start=s.index('class '+class_name)
|
||||
a=s.index(signature,start); b=s.index('{',a); end=b+1; depth=1
|
||||
while depth:
|
||||
depth+=(s[end]=='{')-(s[end]=='}'); end+=1
|
||||
return s[a:end]
|
||||
def cpp(t):
|
||||
t=t.replace('override ', '').replace('notnull ', '').replace('out SCR_EConsumableFailReason failReason','SCR_EConsumableFailReason& failReason')
|
||||
t=t.replace('SCR_CharacterDamageManagerComponent.Cast(target.FindComponent(SCR_CharacterDamageManagerComponent))','target->damage')
|
||||
t=t.replace('SCR_CharacterDamageManagerComponent.Cast(dmgManager)','dmgManager')
|
||||
t=t.replace('CharacterControllerComponent.Cast(target.FindComponent(CharacterControllerComponent))','target->controller')
|
||||
t=t.replace('CharacterControllerComponent.Cast(damage.GetOwner().FindComponent(CharacterControllerComponent))','damage->owner->controller')
|
||||
t=t.replace('SCR_CharacterHitZone.Cast(zone)','dynamic_cast<SCR_CharacterHitZone*>(zone)')
|
||||
t=t.replace('array<HitZone>', 'Array<HitZone*>').replace('foreach (', 'for (').replace('null','nullptr')
|
||||
for typ in ('SCR_CharacterDamageManagerComponent','SCR_ExtendedDamageManagerComponent','HitZone','SCR_CharacterBloodHitZone','SCR_CharacterResilienceHitZone','SCR_CharacterHitZone','CharacterControllerComponent'):
|
||||
t=re.sub(r'\b'+typ+r' (\w+)',typ+r'* \1',t)
|
||||
t=t.replace('HitZone* blood = damage.GetBloodHitZone(), resilience = damage.GetResilienceHitZone();','HitZone* blood = damage.GetBloodHitZone(); HitZone* resilience = damage.GetResilienceHitZone();')
|
||||
for obj in ('damage','blood','resilience','zone','physical','controller'):
|
||||
t=t.replace(obj+'.',obj+'->')
|
||||
for obj in ('EDamageState','SCR_EConsumableFailReason','ECharacterBloodState','ECharacterHitZoneGroup','ERR_CombatEpinephrineRules','Replication','Math'):
|
||||
t=t.replace(obj+'.',obj+'::')
|
||||
t=t.replace('super.', 'Native::')
|
||||
return t
|
||||
rules=cpp(body(p,'class ERR_CombatEpinephrineRules')).replace('class ERR_CombatEpinephrineRules','struct ERR_CombatEpinephrineRules')+';'
|
||||
admit=cpp(method('ERR_ConsumableCombatEpinephrine','override bool CanApplyEffect('))
|
||||
effect=cpp(method('ERR_CombatEpinephrineEffect','override void OnEffectAdded('))
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
namespace EDamageState{enum{UNDAMAGED,DESTROYED};}
|
||||
enum class SCR_EConsumableFailReason{NONE,UNDAMAGED};
|
||||
namespace ECharacterBloodState{enum{FAINTING,UNCONSCIOUS};}
|
||||
enum class ECharacterHitZoneGroup{HEAD,UPPERTORSO,LOWERTORSO,LEFTARM,RIGHTARM,LEFTLEG,RIGHTLEG};
|
||||
namespace Replication{bool server=true;bool IsServer(){return server;}}
|
||||
namespace Math{float Min(float a,float b){return std::min(a,b);}}
|
||||
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;};
|
||||
struct HitZone{float health=1;virtual ~HitZone()=default;float GetHealthScaled(){return health;}void SetHealthScaled(float n){health=n;}virtual float GetDamageStateThreshold(int){return .7f;}};
|
||||
struct SCR_CharacterBloodHitZone:HitZone{float faint=.7f,unconscious=.5f;float GetDamageStateThreshold(int state)override{return state==ECharacterBloodState::FAINTING?faint:unconscious;}};
|
||||
struct SCR_CharacterResilienceHitZone:HitZone{};
|
||||
struct SCR_CharacterHitZone:HitZone{ECharacterHitZoneGroup group=ECharacterHitZoneGroup::LEFTARM;int GetHitZoneGroup(){return int(group);}};
|
||||
// The source compares enum to an int; Enforce permits this conversion.
|
||||
bool operator==(int a,ECharacterHitZoneGroup b){return a==int(b);}
|
||||
struct CharacterControllerComponent{bool unconscious=false;bool IsUnconscious(){return unconscious;}void SetUnconscious(bool n){unconscious=n;}};
|
||||
struct SCR_CharacterDamageManagerComponent;
|
||||
struct Entity{SCR_CharacterDamageManagerComponent* damage=nullptr;CharacterControllerComponent*controller=nullptr;};using IEntity=Entity*;
|
||||
struct SCR_CharacterDamageManagerComponent{Entity*owner=nullptr;int state=EDamageState::UNDAMAGED;bool bleeding=false;SCR_CharacterBloodHitZone*blood=nullptr;SCR_CharacterResilienceHitZone*resilience=nullptr;Array<HitZone*>zones;
|
||||
int GetState(){return state;}bool IsBleeding(){return bleeding;}SCR_CharacterBloodHitZone*GetBloodHitZone(){return blood;}SCR_CharacterResilienceHitZone*GetResilienceHitZone(){return resilience;}void GetPhysicalHitZones(Array<HitZone*>&out){out=zones;}void RemoveGroupBleeding(ECharacterHitZoneGroup){bleeding=false;}
|
||||
};
|
||||
using SCR_ExtendedDamageManagerComponent=SCR_CharacterDamageManagerComponent;
|
||||
// Native inherited effect handling is opaque. No extra health restoration is
|
||||
// fabricated: the authored effect is instant and supplies no generic damage.
|
||||
struct Native{int calls=0;void OnEffectAdded(SCR_ExtendedDamageManagerComponent*){calls++;}};
|
||||
'''+rules+'\nstruct Admission{'+admit+'};\nstruct Effect:Native{'+effect+r'''};
|
||||
struct Fixture{Entity actor;CharacterControllerComponent controller;SCR_CharacterDamageManagerComponent damage;SCR_CharacterBloodHitZone blood;SCR_CharacterResilienceHitZone resilience;SCR_CharacterHitZone arm,leg;Admission admission;Effect effect;
|
||||
Fixture(){actor.damage=&damage;actor.controller=&controller;damage.owner=&actor;damage.blood=&blood;damage.resilience=&resilience;leg.group=ECharacterHitZoneGroup::LEFTLEG;damage.zones={&arm,&leg};Replication::server=true;}
|
||||
bool Allowed(){SCR_EConsumableFailReason reason=SCR_EConsumableFailReason::NONE;return admission.CanApplyEffect(&actor,&actor,reason);}
|
||||
void Apply(){effect.OnEffectAdded(&damage);}
|
||||
};
|
||||
int main(){
|
||||
// Concrete baseline mismatch: mild blood loss is the only injury. Applying the
|
||||
// actual effect changes no health/bleeding/consciousness, so admission must refuse.
|
||||
for(float value:{.7001f,.85f,.999f}){Fixture f;f.blood.health=value;f.Apply();assert(f.effect.calls==1&&f.blood.health==value&&f.resilience.health==1&&f.arm.health==1&&f.leg.health==1&&!f.damage.bleeding&&!f.controller.unconscious);assert(!f.Allowed());}
|
||||
// Native threshold is data-driven, including exact equality; no guessed cutoff.
|
||||
for(float cutoff:{.6f,.7f,.8f}){Fixture f;f.blood.faint=cutoff;f.blood.health=cutoff;assert(f.Allowed());f.Apply();assert(f.blood.health>cutoff);}
|
||||
{Fixture f;f.blood.health=.3f;assert(f.Allowed());f.Apply();assert(f.blood.health>.3f);}
|
||||
// Waking is an independent implemented benefit, including full health.
|
||||
for(float blood:{.9f,1.f}){Fixture f;f.blood.health=blood;f.controller.unconscious=true;assert(f.Allowed());f.Apply();assert(!f.controller.unconscious&&f.blood.health==blood);}
|
||||
{Fixture f;f.controller.unconscious=true;f.damage.state=EDamageState::DESTROYED;assert(!f.Allowed());f.Apply();assert(f.controller.unconscious);}
|
||||
// Preserve all other useful existing benefits even with mildly depleted blood.
|
||||
{Fixture f;f.blood.health=.9f;f.damage.bleeding=true;assert(f.Allowed());f.Apply();assert(!f.damage.bleeding&&f.blood.health==.9f);}
|
||||
{Fixture f;f.blood.health=.9f;f.resilience.health=.2f;f.controller.unconscious=true;assert(f.Allowed());f.Apply();assert(f.resilience.health==1&&!f.controller.unconscious&&f.blood.health==.9f);}
|
||||
{Fixture f;f.blood.health=.9f;f.arm.health=.5f;assert(f.Allowed());f.Apply();assert(std::abs(f.arm.health-.65f)<.00001f);}
|
||||
{Fixture f;f.leg.health=.2f;assert(f.Allowed());f.Apply();assert(std::abs(f.leg.health-.7f)<.00001f);}
|
||||
// A missing controller must not suppress non-consciousness benefits.
|
||||
for(int benefit=0;benefit<4;benefit++){Fixture f;f.actor.controller=nullptr;if(benefit==0)f.damage.bleeding=true;if(benefit==1)f.blood.health=.3f;if(benefit==2)f.resilience.health=.3f;if(benefit==3)f.arm.health=.3f;assert(f.Allowed());f.Apply();}
|
||||
{Fixture f;assert(!f.Allowed());f.actor.damage=nullptr;assert(!f.Allowed());}
|
||||
{Fixture f;f.damage.state=EDamageState::DESTROYED;f.damage.bleeding=true;f.blood.health=.1f;assert(!f.Allowed());f.Apply();assert(f.blood.health==.1f&&f.damage.bleeding);}
|
||||
{Fixture f;f.damage.blood=nullptr;f.damage.resilience=nullptr;assert(!f.Allowed());f.arm.health=.5f;assert(f.Allowed());}
|
||||
{Fixture f;f.blood.health=.1f;Replication::server=false;f.Apply();assert(f.blood.health==.1f);}
|
||||
}
|
||||
''')
|
||||
prefab=(ROOT/'addons/core/Prefabs/ERR/CombatEpinephrine.et').read_text()
|
||||
assert 'm_fItemRegenerationSpeed 0' in prefab and 'm_fItemRegenerationDuration 0' in prefab
|
||||
assert 'ERR_CombatEpinephrineEffect' in prefab
|
||||
print('PASS: actual combat injector admission/effect alignment; mild blood-only refusal, native threshold equality and all existing useful treatments preserved; engine consumption untested')
|
||||
|
||||
@@ -123,5 +123,5 @@ int main(){System system;Mode mode;Respawn original,replacement,unrelated;Mode::
|
||||
""")
|
||||
create=body(SOURCE,'protected override void CreateLoadingPlaceholder(')
|
||||
assert create.index('super.CreateLoadingPlaceholder()') < create.index('ERR_BindEscapeRespawn(this)')
|
||||
assert 'm_fAutoReloadTime 10' in prefab
|
||||
assert 'm_fAutoReloadTime 120' in [line.strip() for line in prefab.splitlines()]
|
||||
print('PASS: replaced respawn system receives frame updates, disabled updater wakes, stale/non-Escape binding refused; terminal and stale preload leases release black overlay')
|
||||
|
||||
Reference in New Issue
Block a user