Add admin-controlled self epinephrine for unconscious players
This commit is contained in:
@@ -10,7 +10,7 @@ source = BASE / 'addons/core/Scripts/Game/ERR/00_Core/ERR_AdminMenu.c'
|
||||
layout = BASE / 'addons/core/UI/layouts/ERR/AdminMenu.layout'
|
||||
keys = re.findall(r'"([A-Za-z]+)"', body(source, 'static array<string> Keys('))
|
||||
settings = re.findall(r'"([A-Za-z]+)"', body(BASE / 'addons/core/Scripts/Game/ERR/00_Core/ERR_Settings.c', 'static array<string> Keys('))
|
||||
assert len(keys) == len(set(keys)) and set(settings) - set(keys) == {"automaticConvoyEnabled", "keepWeaponOnUnconscious", "planeReconEnabled", "restartOnEmpty"}
|
||||
assert len(keys) == len(set(keys)) and set(settings) - set(keys) == {"automaticConvoyEnabled", "keepWeaponOnUnconscious", "selfEpinephrine", "planeReconEnabled", "restartOnEmpty"}
|
||||
assert set(keys) <= set(settings) # Every menu field must have a server setting.
|
||||
# Native layouts place child widget declarations inside an anonymous Children
|
||||
# block. A missing brace can preserve most widget names yet prevent menu load.
|
||||
|
||||
@@ -56,6 +56,7 @@ struct Game{void*local=nullptr;int spawns=0;void*GetPlayerController(){return lo
|
||||
SCR_ManualCamera*SpawnEntityPrefabLocal(Resource*,World*,EntitySpawnParams){spawns++;auto*c=new SCR_ManualCamera();c->previous=manager.current;manager.SetCamera(c);return c;}} game;
|
||||
bool gameAvailable=true;Game*GetGame(){return gameAvailable?&game:nullptr;}
|
||||
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;
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
"""Execute self-injection eligibility and authoritative request throttling."""
|
||||
from policy_harness import ROOT, body, run
|
||||
p=ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_SelfEpinephrine.c'
|
||||
rule=body(p,'static bool Allowed(')
|
||||
request=body(p,'protected void ERR_RequestSelfEpinephrine(').replace('protected ','').replace('Replication.IsServer()', 'server').replace('GetGame().GetWorld().GetWorldTime()', 'this->now').replace('Rpc(ERR_SelfEpinephrineResponse, result)', 'Reply(result)')
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
struct string:std::string{using std::string::string;bool IsEmpty(){return empty();}};
|
||||
'''+rule+r'''
|
||||
struct Controller{bool server=true;float now=0,m_ERRLastSelfEpinephrine=-10000;int calls=0,replies=0;bool eligible=true;
|
||||
string ERR_TrySelfEpinephrine(){calls++;return eligible?"used":"";}void Reply(string){replies++;}
|
||||
'''+request+r'''
|
||||
};
|
||||
int main(){
|
||||
for(int bits=0;bits<32;bits++)assert(Allowed(bits&1,bits&2,bits&4,bits&8,bits&16)==(bits==31));
|
||||
Controller c;c.server=false;c.ERR_RequestSelfEpinephrine();assert(c.calls==0);
|
||||
c.server=true;c.ERR_RequestSelfEpinephrine();assert(c.calls==1&&c.replies==1);
|
||||
for(int i=0;i<100;i++){c.ERR_RequestSelfEpinephrine();}assert(c.calls==1);
|
||||
c.now=999;c.ERR_RequestSelfEpinephrine();assert(c.calls==1);
|
||||
c.now=1000;c.eligible=false;c.ERR_RequestSelfEpinephrine();assert(c.calls==2&&c.replies==1);
|
||||
c.eligible=true;c.now=1999;c.ERR_RequestSelfEpinephrine();assert(c.calls==2);
|
||||
c.now=2000;c.ERR_RequestSelfEpinephrine();assert(c.calls==3&&c.replies==2);
|
||||
c.now=0;c.ERR_RequestSelfEpinephrine();assert(c.calls==4&&c.replies==3);
|
||||
}
|
||||
''')
|
||||
source=body(p,'protected string ERR_TrySelfEpinephrine(')
|
||||
assert 'if (!Replication.IsServer()) return "";' in source
|
||||
assert 'GetControlledEntity()' in source and 'life.Actor == actor' in source and 'mode.Players().CurrentActor(actor) == life' in source
|
||||
assert 'mode.Settings().Get("selfEpinephrine") == 1' in source
|
||||
assert 'inventory.Contains(injector)' in source and 'items.Count() > 512' in source
|
||||
assert source.index('effect.CanApplyEffect(') < source.index('item.ApplyItemEffect(')
|
||||
assert 'effect.GetDeleteOnUse()' in source and 'Spawn(' not in source and 'SetHealth' not in source
|
||||
settings=ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_Settings.c'
|
||||
assert '"selfEpinephrine"' in body(settings,'static array<string> Keys(')
|
||||
assert 'key == "selfEpinephrine"' in body(settings,'static bool Valid(')
|
||||
assert 'if (key == "startMode" || key == "selfEpinephrine") return 0;' in body(settings,'static int AddedDefault(')
|
||||
assert 'selfEpinephrine' not in body(settings,'static bool Randomizable(')
|
||||
menu=(ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_AdminMenu.c').read_text()
|
||||
assert 'ERR_AdminSubmit("mission selfepi set " + selfEpi, true)' in menu
|
||||
assert 'Pair(GetRootWidget(), "SelfEpiOn", "SelfEpiOff", m_SelfEpiState)' in menu
|
||||
assert 'button.SetEnabled(m_Ready && m_SelfEpiState == 0)' in menu
|
||||
assert 'button.SetEnabled(m_Ready && m_SelfEpiState == 1)' in menu
|
||||
config=(ROOT/'addons/core/Configs/ERR/ObserverInput.conf').read_text()
|
||||
assert 'keyboard:KC_SPACE' in body(ROOT/'addons/core/Configs/ERR/ObserverInput.conf','Action ERR_SelfEpinephrine')
|
||||
print('PASS: actual self-injection eligibility matrix and authority/rate limit; default-off persistence, authenticated menu route, owned finite injector and native effect/consumption boundaries')
|
||||
@@ -5,7 +5,7 @@ from policy_harness import ROOT,body,run
|
||||
spec=importlib.util.spec_from_file_location('import_settings',BASE/'tools/import_settings.py')
|
||||
m=importlib.util.module_from_spec(spec);spec.loader.exec_module(m)
|
||||
result,deferred=m.merge({'configVersion':1,'reportIntervalSeconds':20.0,'supportSquadLimit':2,'responseRadiusMetres':1500,'convoyCount':3},{'aiIntelChancePercent':0,'intelRevealChancePercent':100})
|
||||
assert result==dict(aiDoctrine=0,aiShadow=0,planeAltitudeMetres=250,planeReplacementSeconds=60,planeSectorSeconds=120,planeReconEnabled=1,roadblockCount=4,civilianTrafficCount=3,startMode=0,keepWeaponOnUnconscious=1,restartOnEmpty=1,emptyRestartHours=5,reportIntervalSeconds=20,supportSquadLimit=2,responseRadiusMetres=1500,aiIntelChancePercent=0,intelRevealChancePercent=100,radioDefenceSeconds=180,startTimeMinutes=840,timeMultiplier=1,startDateCode=0,weatherProfile=0,viewDistanceMetres=0,automaticEquipmentChancePercent=25,antiAirChancePercent=7,antiPersonnelMineChancePercent=60,automaticConvoyEnabled=1,convoyCount=3,heliTimerMinutes=15)
|
||||
assert result==dict(aiDoctrine=0,aiShadow=0,planeAltitudeMetres=250,planeReplacementSeconds=60,planeSectorSeconds=120,planeReconEnabled=1,roadblockCount=4,civilianTrafficCount=3,startMode=0,keepWeaponOnUnconscious=1,selfEpinephrine=0,restartOnEmpty=1,emptyRestartHours=5,reportIntervalSeconds=20,supportSquadLimit=2,responseRadiusMetres=1500,aiIntelChancePercent=0,intelRevealChancePercent=100,radioDefenceSeconds=180,startTimeMinutes=840,timeMultiplier=1,startDateCode=0,weatherProfile=0,viewDistanceMetres=0,automaticEquipmentChancePercent=25,antiAirChancePercent=7,antiPersonnelMineChancePercent=60,automaticConvoyEnabled=1,convoyCount=3,heliTimerMinutes=15)
|
||||
assert deferred==[]
|
||||
for count in (0,1,3,20):
|
||||
values,notes=m.merge(ai={'convoyCount':count})
|
||||
|
||||
@@ -53,16 +53,25 @@ int main(){
|
||||
c.ERR_UpdateRecoveryCamera(&mode);assert(!c.m_ERRRecoveryBody&&manager.sets==before);
|
||||
}
|
||||
''')
|
||||
assert 'm_InputResumeFrames = 2;' in body(p,'override bool EOnCameraInit(')
|
||||
input_body=body(p,'void Input(').replace('SCR_PlayerController controller = SCR_PlayerController.Cast(GetGame().GetPlayerController());','SCR_PlayerController* controller = &pc;').replace('controller.','controller->').replace('ERR_ObserverRules.','ERR_ObserverRules::')
|
||||
run(r'''#include <cassert>
|
||||
struct ERR_ObserverRules {
|
||||
'''+body(p,'static int Cycle(')+body(p,'static bool Toggle(')+r'''
|
||||
};
|
||||
struct SCR_PlayerController {int calls=0,direction=0;void ERR_CycleObserver(int d){calls++;direction=d;}} pc;
|
||||
struct Follow {bool m_LeftDown=false,m_RightDown=false,m_EnterDown=false,m_PlayerView=false;int m_InputResumeFrames=0;
|
||||
struct SCR_PlayerController {int calls=0,direction=0,epi=0;void ERR_UseSelfEpinephrine(){epi++;}void ERR_CycleObserver(int d){calls++;direction=d;}} pc;
|
||||
struct Follow {bool m_LeftDown=false,m_RightDown=false,m_EnterDown=false,m_PlayerView=false,m_SpaceDown=false;int m_InputResumeFrames=0;
|
||||
'''+input_body+r'''
|
||||
};
|
||||
int main(){
|
||||
Follow initial;initial.m_InputResumeFrames=2;
|
||||
initial.Input(true,false,false,false,true);initial.Input(true,false,false,false,true);assert(pc.epi==0);
|
||||
initial.Input(true,false,false,false,false);initial.Input(true,false,false,false,true);assert(pc.epi==1);pc.epi=0;
|
||||
Follow epi;
|
||||
epi.Input(false,false,false,false,true); epi.Input(true,false,false,false,true); epi.Input(true,false,false,false,true);assert(pc.epi==0);
|
||||
epi.Input(true,false,false,false,false);epi.Input(true,false,false,false,true);assert(pc.epi==1);
|
||||
epi.Input(true,false,false,false,true);assert(pc.epi==1);
|
||||
epi.Input(true,false,false,false,false);epi.Input(true,false,false,false,true);assert(pc.epi==2);
|
||||
Follow f;f.Input(true,true,false,false);assert(pc.calls==1&&pc.direction==-1);
|
||||
f.Input(true,true,false,false);assert(pc.calls==1);
|
||||
f.Input(true,false,false,false);f.Input(true,false,true,false);assert(pc.calls==2&&pc.direction==1);
|
||||
@@ -179,8 +188,8 @@ ui_follow=r'''
|
||||
struct ERR_ObserverRules {
|
||||
'''+body(p,'static int Cycle(')+body(p,'static bool Toggle(')+r'''
|
||||
};
|
||||
struct SCR_PlayerController {int calls=0,direction=0;void ERR_CycleObserver(int d){calls++;direction=d;}} pc;
|
||||
struct Follow {bool m_LeftDown=false,m_RightDown=false,m_EnterDown=false,m_PlayerView=false;int m_InputResumeFrames=0;
|
||||
struct SCR_PlayerController {int calls=0,direction=0,epi=0;void ERR_UseSelfEpinephrine(){epi++;}void ERR_CycleObserver(int d){calls++;direction=d;}} pc;
|
||||
struct Follow {bool m_LeftDown=false,m_RightDown=false,m_EnterDown=false,m_PlayerView=false,m_SpaceDown=false;int m_InputResumeFrames=0;
|
||||
'''+input_body+r'''
|
||||
};
|
||||
'''
|
||||
|
||||
Reference in New Issue
Block a user