Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
95 lines
8.0 KiB
Python
95 lines
8.0 KiB
Python
"""Actual fixture staging and hitzone cap, with vanilla max-health damage boundary."""
|
|
import os
|
|
from pathlib import Path
|
|
from policy_harness import ROOT, body, run
|
|
base=Path(os.environ.get('ERR_DEATH_FIXTURE_ROOT', ROOT))
|
|
p=base/'addons/core/Scripts/Game/ERR/00_Core/ERR_PlayerMedical.c'
|
|
f=base/'addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSmoke.c'
|
|
def convert(s):
|
|
s=s.replace('protected ', '').replace('override ','').replace('notnull Instigator instigator','int instigator').replace('IEntity expectedActor','Actor* expectedActor')
|
|
s=s.replace('Replication.IsServer()','server').replace('System.GetCLIParam','System::GetCLIParam').replace('super.','Native::')
|
|
s=s.replace('SCR_CharacterDamageManagerComponent damage = SCR_CharacterDamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(SCR_CharacterDamageManagerComponent));','Medical* damage = &medical;')
|
|
s=s.replace('CharacterControllerComponent character = CharacterControllerComponent.Cast(m_ERRSmokePlayer.FindComponent(CharacterControllerComponent));','Actor* character = m_ERRSmokePlayer;')
|
|
s=s.replace('Instigator.CreateInstigator(m_ERRSmokePlayer)','0').replace('ERR_Actors.Alive(m_ERRSmokePlayer)','m_ERRSmokePlayer->alive')
|
|
s=s.replace('IEntity actor = GetOwner();','Actor* actor = GetOwner();').replace('ERR_GameMode mode = ERR_GameMode.Get();','Mode* mode = Mode::current;').replace('ERR_Actors.Alive(actor)','(actor && actor->alive)')
|
|
s=s.replace('mode.', 'mode->').replace('Players().CurrentActor','Players()->CurrentActor')
|
|
s=s.replace('damage.','damage->').replace('character.','character->')
|
|
return s
|
|
kill=convert(body(p,'override void Kill('))
|
|
fatal=convert(body(p,'bool ERR_ForceFatalLossForTest('))
|
|
# Permit the unchanged helper's older signature for the runtime red test.
|
|
if 'Actor* expectedActor' not in fatal:
|
|
fatal=fatal.replace('int instigator)', 'int instigator, Actor* expectedActor)')
|
|
begin=convert(body(f,'protected void ERR_BeginProtectedDeathProbe('))
|
|
complete=convert(body(f,'protected void ERR_CompleteProtectedDeathProbe('))
|
|
complete=complete.replace('ERR_ForceFatalLossForTest(0)', 'ERR_ForceFatalLossForTest(0, m_ERRSmokePlayer)')
|
|
cap=body(p,'static float AllowedDamage(').replace('Math.Clamp','std::clamp').replace('Math.Min','std::min').replace('Math.Max','std::max').replace('0.01','0.01f').replace(', 1)',', 1.0f)').replace('(0,','(0.0f,')
|
|
effective=body(p,'override float ComputeEffectiveDamage(')
|
|
effective=effective.replace('override ', '').replace('notnull BaseDamageContext damageContext','float damageContext').replace('super.','NativeZone::')
|
|
effective=effective.replace('Replication.IsServer()','server').replace('SCR_CharacterResilienceHitZone.Cast(this)','resilience')
|
|
effective=effective.replace('SCR_CharacterDamageManagerComponent manager = SCR_CharacterDamageManagerComponent.Cast(GetHitZoneContainer());','Medical* manager = medical;').replace('manager.','manager->').replace('ERR_PlayerMedicalRules.','').replace('EDamageState.DESTROYED','0')
|
|
run(r'''
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <string>
|
|
#include <map>
|
|
using string=std::string;
|
|
bool server=true;
|
|
namespace System { std::map<string,string> args; bool GetCLIParam(string key,string& out){auto it=args.find(key);if(it==args.end())return false;out=it->second;return true;} }
|
|
struct Actor {bool alive=true, unconscious=false;bool IsUnconscious(){return unconscious;}};
|
|
struct Roster {Actor* owned=nullptr;bool CurrentActor(Actor* actor){return actor==owned;}};
|
|
struct Mode {static inline Mode* current=nullptr;bool active=true,haveRoster=true;Roster roster;bool Active(){return active;}Roster* Players(){return haveRoster?&roster:nullptr;}};
|
|
struct Native {Actor* actor;int fatalities=0;bool deferred=false,damagePending=false;virtual float AppliedDamage(float)=0;void Kill(int){fatalities++;if(deferred){damagePending=true;return;}Damage();}void Damage(){damagePending=false;actor->alive=AppliedDamage(100)<100;}Actor* GetOwner(){return actor;}};
|
|
struct Medical: Native {
|
|
bool m_ERRRevivablePlayer=true,pending=false; int requests=0;
|
|
void ERR_RequestKnockout(){requests++;pending=true;}
|
|
void Settle(){if(pending){actor->unconscious=true;pending=false;}}
|
|
bool ERR_IsRevivablePlayer(){return m_ERRRevivablePlayer;}
|
|
float AppliedDamage(float damage) override;
|
|
''' + kill+fatal+r'''
|
|
};
|
|
''' + cap + r'''
|
|
struct NativeZone {float ComputeEffectiveDamage(float damage,bool){return damage;}};
|
|
struct Zone: NativeZone {bool m_ERRWoundQuery=false,resilience=false;Medical* medical;float GetHealth(){return 100;}float GetMaxHealth(){return 100;}float GetDamageStateThreshold(int){return 0;}
|
|
''' + effective + r'''
|
|
};
|
|
float Medical::AppliedDamage(float damage){Zone zone;zone.medical=this;return zone.ComputeEffectiveDamage(damage,false);}
|
|
void Print(string){}
|
|
struct Fixture {
|
|
Actor actor;Medical medical;Mode mode;Actor* m_ERRSmokePlayer=&actor;int m_ERRSmokeStep=0;float m_ERRSmokeWait=0;int failures=0;
|
|
Fixture(){medical.actor=&actor;mode.roster.owned=&actor;Mode::current=&mode;}
|
|
bool ERR_Check(bool ok,string){if(!ok)failures++;return ok;}
|
|
''' + begin+complete+r'''
|
|
};
|
|
int main(){
|
|
System::args={{"errSmoke","1"}};
|
|
Fixture f; f.ERR_BeginProtectedDeathProbe(100);
|
|
assert(f.m_ERRSmokeStep==92 && f.actor.alive && f.medical.requests==1 && f.medical.fatalities==0);
|
|
assert(!f.actor.unconscious && f.m_ERRSmokeWait==1100);
|
|
f.medical.Settle(); f.ERR_CompleteProtectedDeathProbe(1100);
|
|
// Old helper failed here: super.Kill still goes through the actual hitzone cap.
|
|
assert(f.failures==0 && f.m_ERRSmokeStep==9 && !f.actor.alive && f.medical.fatalities==1);
|
|
assert(!f.medical.m_ERRRevivablePlayer && f.medical.requests==1 && f.m_ERRSmokeWait==5100);
|
|
{Fixture q;q.medical.deferred=true;assert(q.medical.ERR_ForceFatalLossForTest(0,&q.actor));assert(q.actor.alive&&q.medical.damagePending&&!q.medical.m_ERRRevivablePlayer);q.medical.Damage();assert(!q.actor.alive);}
|
|
// An unrelated ordinary actor retains full protection; test flag never global.
|
|
{Fixture q;q.medical.Kill(0);assert(q.actor.alive&&q.medical.requests==1&&q.medical.fatalities==0&&q.medical.m_ERRRevivablePlayer);}
|
|
{Fixture q;q.ERR_CompleteProtectedDeathProbe(1100);assert(q.failures==1&&q.medical.fatalities==0);}
|
|
{Fixture q;q.actor.alive=false;q.ERR_CompleteProtectedDeathProbe(1100);assert(q.failures==1&&q.medical.fatalities==0);}
|
|
{Fixture q;q.medical.m_ERRRevivablePlayer=false;q.ERR_BeginProtectedDeathProbe(100);assert(q.failures==1&&q.medical.requests==0&&q.medical.fatalities==0);}
|
|
{Fixture q;Actor other;assert(!q.medical.ERR_ForceFatalLossForTest(0,&other)&&q.medical.m_ERRRevivablePlayer&&q.actor.alive);assert(!q.medical.ERR_ForceFatalLossForTest(0,nullptr)&&q.medical.m_ERRRevivablePlayer);}
|
|
{Fixture q;q.mode.roster.owned=nullptr;assert(!q.medical.ERR_ForceFatalLossForTest(0,&q.actor)&&q.medical.m_ERRRevivablePlayer);}
|
|
{Fixture q;q.mode.active=false;assert(!q.medical.ERR_ForceFatalLossForTest(0,&q.actor)&&q.medical.m_ERRRevivablePlayer);}
|
|
{Fixture q;q.mode.haveRoster=false;assert(!q.medical.ERR_ForceFatalLossForTest(0,&q.actor)&&q.medical.m_ERRRevivablePlayer);}
|
|
{Fixture q;Mode::current=nullptr;assert(!q.medical.ERR_ForceFatalLossForTest(0,&q.actor)&&q.medical.m_ERRRevivablePlayer);}
|
|
{Fixture q;server=false;assert(!q.medical.ERR_ForceFatalLossForTest(0,&q.actor)&&q.actor.alive&&q.medical.m_ERRRevivablePlayer);server=true;}
|
|
{Fixture q;System::args.clear();assert(!q.medical.ERR_ForceFatalLossForTest(0,&q.actor)&&q.actor.alive&&q.medical.m_ERRRevivablePlayer);System::args["errSmoke"]="0";assert(!q.medical.ERR_ForceFatalLossForTest(0,&q.actor)&&q.actor.alive&&q.medical.m_ERRRevivablePlayer);}
|
|
{Fixture q;q.medical.m_ERRRevivablePlayer=false;q.medical.Kill(0);assert(!q.actor.alive&&q.medical.requests==0&&q.medical.fatalities==1);}
|
|
}
|
|
''')
|
|
s=p.read_text();at=s.index('bool ERR_ForceFatalLossForTest(')
|
|
assert s.rfind('#ifdef WORKBENCH',0,at)>s.rfind('#endif',0,at)
|
|
tick=body(f,'protected void ERR_SmokeTick()')
|
|
assert tick.count('damage.ERR_ForceFatalLossForTest(')==7
|
|
assert 'damage.Kill(' not in tick
|
|
print('PASS: actual protected/capped damage plus exact-actor terminal fixture bypass through delayed native damage, fail-closed ownership and untouched ordinary actor; no native claim')
|