|
|
|
@@ -7,7 +7,7 @@ OBSERVER=Path(os.environ.get('ERR_OBSERVER_SOURCE', ROOT/'addons/core/Scripts/Ga
|
|
|
|
|
PROBE=Path(os.environ.get('ERR_OBSERVER_PROBE_SOURCE', ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_UnconsciousCameraProbe.c'))
|
|
|
|
|
def cpp(text):
|
|
|
|
|
text=text.replace(' = null;', ' = nullptr;').replace('protected ', '').replace('override ', '').replace('super.OnControlledEntityChanged(from, to);', 'nativeChanges++;')
|
|
|
|
|
for name in ('ERR_GameMode','ERR_Phase','ERR_ObserverReason','ERR_Actors','ERR_ObjectiveText','SCR_HintManagerComponent','ERR_ObserverGuidance','ERR_ObserverRules','Resource','ETransformMode','SCR_ManualCamera','SCR_RespawnSystemComponent','SCR_EditorManagerEntity','RplId'):
|
|
|
|
|
for name in ('System','ERR_GameMode','ERR_Phase','ERR_ObserverReason','ERR_Actors','ERR_ObjectiveText','SCR_HintManagerComponent','ERR_ObserverGuidance','ERR_ObserverRules','Resource','ETransformMode','SCR_ManualCamera','SCR_RespawnSystemComponent','SCR_EditorManagerEntity','RplId'):
|
|
|
|
|
text=text.replace(name+'.',name+'::')
|
|
|
|
|
for typename in ('ERR_GameMode','SCR_HintManagerComponent','Resource','SCR_RespawnSystemComponent','SCR_EditorManagerEntity','CameraManager'):
|
|
|
|
|
text=text.replace(typename+' ',typename+'* ')
|
|
|
|
@@ -23,6 +23,7 @@ run(r'''#include <cassert>
|
|
|
|
|
#include <string>
|
|
|
|
|
#include <set>
|
|
|
|
|
struct string:std::string {using std::string::string;using std::string::operator=;string(std::string s):std::string(s){}bool IsEmpty()const{return empty();}};
|
|
|
|
|
namespace System {int now=0;int GetTickCount(){return now;}}
|
|
|
|
|
namespace ERR_Phase {enum {PREPARING,PRISON,RADIO_SEARCH,RADIO_DEFENCE,EXTRACTION};}
|
|
|
|
|
namespace ERR_ObserverReason {enum {NONE,SPENT_LIFE,LATE_EXTRACTION,SPAWN_UNAVAILABLE,UNCONSCIOUS};}
|
|
|
|
|
struct Actor {bool alive=true;bool unconscious=false;} actor;using IEntity=Actor*;
|
|
|
|
@@ -30,7 +31,7 @@ struct ERR_Actors {static bool Alive(Actor* a){return a&&a->alive;}static bool C
|
|
|
|
|
struct ERR_GameMode {string recovery;string RecoveryStatus(){return recovery;}int phase=ERR_Phase::PRISON;bool active=true;string message="prison";static ERR_GameMode* current;static ERR_GameMode* Get(){return current;}int Phase(){return phase;}bool Active(){return active;}} mode;
|
|
|
|
|
ERR_GameMode* ERR_GameMode::current=&mode;
|
|
|
|
|
struct ERR_ObjectiveText {static string Description(ERR_GameMode* m,bool){return m->message;}static string Title(int){return "objective";}};
|
|
|
|
|
struct SCR_HintManagerComponent {static SCR_HintManagerComponent* current;bool accepts=true;int attempts=0,shown=0;string last;static SCR_HintManagerComponent* GetInstance(){return current;}bool ShowCustom(string text,string,int){attempts++;if(!accepts)return false;shown++;last=text;return true;}} hints;
|
|
|
|
|
struct SCR_HintManagerComponent {static SCR_HintManagerComponent* current;bool accepts=true,silent=false;int attempts=0,shown=0;string last;static SCR_HintManagerComponent* GetInstance(){return current;}bool ShowCustom(string text,string,int,bool quiet=false){attempts++;if(!accepts)return false;shown++;last=text;silent=quiet;return true;}} hints;
|
|
|
|
|
SCR_HintManagerComponent* SCR_HintManagerComponent::current=&hints;
|
|
|
|
|
struct ERR_ObserverRules {
|
|
|
|
|
'''+cpp(body(OBSERVER,'static bool View('))+r'''
|
|
|
|
@@ -62,11 +63,13 @@ struct Controller {
|
|
|
|
|
SCR_ManualCamera* m_ERRObserverCamera=nullptr;Vec m_ERRObserverAnchor;
|
|
|
|
|
int m_ERRObservedId=1;bool m_ERRObserverReady=false;
|
|
|
|
|
string m_ERRShownRecoveryStatus;
|
|
|
|
|
string m_ERRPendingRecoveryStatus;int m_ERRRecoveryStatusSince=0;
|
|
|
|
|
int m_ERRShownObserverReason=0;bool m_ERRShownObserverAvailable=false;int closes=0;
|
|
|
|
|
Actor* GetControlledEntity(){return controlled;}int ERR_ObserverState(){return m_ERRObserverReason;}
|
|
|
|
|
void ERR_CloseObserverCamera(){closes++;m_ERRObserverReady=false;m_ERRShownRecoveryStatus="";m_ERRObserverCamera=nullptr;}
|
|
|
|
|
void ERR_CloseObserverCamera(){closes++;m_ERRObserverReady=false;m_ERRShownRecoveryStatus="";m_ERRPendingRecoveryStatus="";m_ERRRecoveryStatusSince=0;m_ERRObserverCamera=nullptr;}
|
|
|
|
|
'''+methods+r'''
|
|
|
|
|
};
|
|
|
|
|
void settled(Controller& c){c.ERR_UpdateObserverClient();System::now+=3000;c.ERR_UpdateObserverClient();}
|
|
|
|
|
void reset(){queue=Queue();gameAvailable=true;mode=ERR_GameMode();ERR_GameMode::current=&mode;hints=SCR_HintManagerComponent();SCR_HintManagerComponent::current=&hints;actor=Actor();Resource::current=&resource;manager.current=&camera;}
|
|
|
|
|
int main(){
|
|
|
|
|
reset();Controller g;
|
|
|
|
@@ -100,8 +103,8 @@ int main(){
|
|
|
|
|
int attempts=hints.attempts;c.ERR_UpdateGuide();assert(hints.attempts==attempts&&c.m_ERRGuidePhase==-1);
|
|
|
|
|
actor.alive=true;c.controlled=&actor;c.m_ERRObserverReason=ERR_ObserverReason::NONE;c.ERR_UpdateGuide();assert(hints.attempts==attempts+1);
|
|
|
|
|
}
|
|
|
|
|
// Unconscious living prisoners retain recovery guidance. Preparing needs no body.
|
|
|
|
|
Controller unconscious;actor.unconscious=true;unconscious.ERR_UpdateGuide();assert(unconscious.m_ERRGuidePhase==mode.phase);actor.unconscious=false;
|
|
|
|
|
// Local unconsciousness suppresses objectives even before observer metadata arrives.
|
|
|
|
|
Controller unconscious;actor.unconscious=true;int beforeUnconscious=hints.attempts;unconscious.ERR_UpdateGuide();assert(unconscious.m_ERRGuidePhase==-1&&hints.attempts==beforeUnconscious);actor.unconscious=false;
|
|
|
|
|
Controller preparing;preparing.controlled=nullptr;mode.phase=ERR_Phase::PREPARING;mode.message="preparing";preparing.ERR_UpdateGuide();assert(preparing.m_ERRGuideMessage=="preparing");
|
|
|
|
|
ERR_GameMode::current=nullptr;int attempts=hints.attempts;preparing.ERR_UpdateGuide();assert(hints.attempts==attempts);
|
|
|
|
|
// A prior local controller can remain alive until native teardown. Its
|
|
|
|
@@ -142,36 +145,47 @@ int main(){
|
|
|
|
|
// Unconscious observation preserves the original body, delivers recovery wording,
|
|
|
|
|
// and closes as soon as the local body wakes even before server metadata clears.
|
|
|
|
|
reset();Controller casualty;actor.unconscious=true;casualty.m_ERRObserverReason=ERR_ObserverReason::UNCONSCIOUS;
|
|
|
|
|
casualty.ERR_UpdateObserverClient();assert(casualty.m_ERRObserverCamera==&camera&&casualty.controlled==&actor&&hints.last.find("You are unconscious")!=string::npos);
|
|
|
|
|
casualty.ERR_UpdateObserverClient();assert(hints.shown==1);
|
|
|
|
|
settled(casualty);assert(casualty.m_ERRObserverCamera==&camera&&casualty.controlled==&actor&&hints.last.find("You are unconscious")!=string::npos);
|
|
|
|
|
settled(casualty);assert(hints.shown==1);
|
|
|
|
|
// The phase-guide explicitly excludes this server-designated unconscious
|
|
|
|
|
// observer; the existing observer monitor must deliver recovery/defeat facts.
|
|
|
|
|
int attemptsBeforeGuide=hints.attempts;casualty.ERR_UpdateGuide();assert(hints.attempts==attemptsBeforeGuide);
|
|
|
|
|
mode.recovery="Everyone is unconscious with no treatment or recovery progressing. The mission fails after 30 uninterrupted seconds in this state.";
|
|
|
|
|
casualty.ERR_UpdateObserverClient();assert(hints.shown==2&&hints.last.find(mode.recovery)!=string::npos&&casualty.ERR_ProbeAcceptedRecoveryStatus()==mode.recovery);
|
|
|
|
|
casualty.ERR_UpdateObserverClient();assert(hints.shown==2); // stable status never repeats
|
|
|
|
|
settled(casualty);assert(hints.shown==2&&hints.last.find(mode.recovery)!=string::npos&&casualty.ERR_ProbeAcceptedRecoveryStatus()==mode.recovery);
|
|
|
|
|
settled(casualty);assert(hints.shown==2); // stable status never repeats
|
|
|
|
|
mode.recovery="Treatment or natural recovery is possible; the round continues.";hints.accepts=false;
|
|
|
|
|
casualty.ERR_UpdateObserverClient();assert(hints.shown==2&&casualty.m_ERRShownRecoveryStatus.find("30 uninterrupted")!=string::npos);
|
|
|
|
|
hints.accepts=true;casualty.ERR_UpdateObserverClient();assert(hints.shown==3&&hints.last.find(mode.recovery)!=string::npos);
|
|
|
|
|
settled(casualty);assert(hints.shown==2&&casualty.m_ERRShownRecoveryStatus.find("30 uninterrupted")!=string::npos);
|
|
|
|
|
hints.accepts=true;settled(casualty);assert(hints.shown==3&&hints.last.find(mode.recovery)!=string::npos);
|
|
|
|
|
mode.recovery="A conscious prisoner's reconnection may provide help.";SCR_HintManagerComponent::current=nullptr;
|
|
|
|
|
casualty.ERR_UpdateObserverClient();assert(hints.shown==3&&casualty.m_ERRShownRecoveryStatus.find("natural recovery")!=string::npos);
|
|
|
|
|
SCR_HintManagerComponent::current=&hints;casualty.ERR_UpdateObserverClient();assert(hints.shown==4&&hints.last.find(mode.recovery)!=string::npos);
|
|
|
|
|
mode.recovery="";casualty.ERR_UpdateObserverClient();assert(hints.shown==5&&casualty.m_ERRShownRecoveryStatus.empty()&&hints.last.find("reconnection")==string::npos);
|
|
|
|
|
casualty.ERR_UpdateObserverClient();assert(hints.shown==5);
|
|
|
|
|
settled(casualty);assert(hints.shown==3&&casualty.m_ERRShownRecoveryStatus.find("natural recovery")!=string::npos);
|
|
|
|
|
SCR_HintManagerComponent::current=&hints;settled(casualty);assert(hints.shown==4&&hints.last.find(mode.recovery)!=string::npos);
|
|
|
|
|
mode.recovery="";settled(casualty);assert(hints.shown==5&&casualty.m_ERRShownRecoveryStatus.empty()&&hints.last.find("reconnection")==string::npos);
|
|
|
|
|
settled(casualty);assert(hints.shown==5);
|
|
|
|
|
// Rapid alternating medical samples must not replace the accepted message.
|
|
|
|
|
int stableShows=hints.shown;
|
|
|
|
|
for(int i=0;i<20;i++){mode.recovery=i%2?"recovering":"stalled";System::now+=250;casualty.ERR_UpdateObserverClient();}
|
|
|
|
|
assert(hints.shown==stableShows&&casualty.m_ERRShownRecoveryStatus.empty());
|
|
|
|
|
mode.recovery="stable medical warning";casualty.ERR_UpdateObserverClient();
|
|
|
|
|
System::now+=2999;casualty.ERR_UpdateObserverClient();assert(hints.shown==stableShows);
|
|
|
|
|
System::now+=1;casualty.ERR_UpdateObserverClient();assert(hints.shown==stableShows+1&&hints.silent&&casualty.m_ERRShownRecoveryStatus==mode.recovery);
|
|
|
|
|
// A regressed clock restarts the settling interval rather than accepting stale time.
|
|
|
|
|
mode.recovery="after clock reset";System::now=0;casualty.ERR_UpdateObserverClient();assert(hints.shown==stableShows+1);
|
|
|
|
|
System::now=3000;casualty.ERR_UpdateObserverClient();assert(hints.shown==stableShows+2);
|
|
|
|
|
int afterStableUpdates=hints.shown;
|
|
|
|
|
// Waking closes the view; a new status cannot keep a recovered observer hint alive.
|
|
|
|
|
mode.recovery="remaining casualty warning";actor.unconscious=false;casualty.ERR_UpdateObserverClient();assert(!casualty.m_ERRObserverCamera&&casualty.closes==1&&hints.shown==5&&casualty.ERR_ProbeAcceptedRecoveryStatus().empty());
|
|
|
|
|
mode.recovery="remaining casualty warning";actor.unconscious=false;casualty.ERR_UpdateObserverClient();assert(!casualty.m_ERRObserverCamera&&casualty.closes==1&&hints.shown==afterStableUpdates&&casualty.ERR_ProbeAcceptedRecoveryStatus().empty());
|
|
|
|
|
// An old controller cannot consume the replacement's recovery-status update.
|
|
|
|
|
reset();Controller oldCasualty;actor.unconscious=true;oldCasualty.m_ERRObserverReason=ERR_ObserverReason::UNCONSCIOUS;oldCasualty.ERR_UpdateObserverClient();
|
|
|
|
|
reset();Controller oldCasualty;actor.unconscious=true;oldCasualty.m_ERRObserverReason=ERR_ObserverReason::UNCONSCIOUS;settled(oldCasualty);
|
|
|
|
|
Controller newCasualty;newCasualty.m_ERRObserverReason=ERR_ObserverReason::UNCONSCIOUS;mode.recovery="recovery warning after reconnect";int beforeOld=hints.shown;
|
|
|
|
|
oldCasualty.ERR_UpdateObserverClient();assert(hints.shown==beforeOld&&oldCasualty.m_ERRShownRecoveryStatus.empty());
|
|
|
|
|
newCasualty.ERR_UpdateObserverClient();assert(hints.shown==beforeOld+1&&hints.last.find(mode.recovery)!=string::npos);
|
|
|
|
|
mode.active=false;mode.recovery="old round recovery";newCasualty.ERR_UpdateObserverClient();assert(hints.shown==beforeOld+1&&!newCasualty.m_ERRObserverReady&&newCasualty.m_ERRShownRecoveryStatus.empty());
|
|
|
|
|
settled(oldCasualty);assert(hints.shown==beforeOld&&oldCasualty.m_ERRShownRecoveryStatus.empty());
|
|
|
|
|
settled(newCasualty);assert(hints.shown==beforeOld+1&&hints.last.find(mode.recovery)!=string::npos);
|
|
|
|
|
mode.active=false;mode.recovery="old round recovery";settled(newCasualty);assert(hints.shown==beforeOld+1&&!newCasualty.m_ERRObserverReady&&newCasualty.m_ERRShownRecoveryStatus.empty());
|
|
|
|
|
// A later active round's first observer hint contains only its current fact.
|
|
|
|
|
mode.active=true;mode.recovery="new round recovery";newCasualty.ERR_UpdateObserverClient();assert(hints.shown==beforeOld+2&&hints.last.find("new round recovery")!=string::npos&&hints.last.find("old round recovery")==string::npos);
|
|
|
|
|
mode.active=true;mode.recovery="new round recovery";settled(newCasualty);assert(hints.shown==beforeOld+2&&hints.last.find("new round recovery")!=string::npos&&hints.last.find("old round recovery")==string::npos);
|
|
|
|
|
// An admin-enabled self-epi hint refreshes once, and disappears when disabled.
|
|
|
|
|
newCasualty.epiHint="Press Space to use epinephrine";newCasualty.ERR_UpdateObserverClient();assert(hints.shown==beforeOld+3&&hints.last.find(newCasualty.epiHint)!=string::npos);
|
|
|
|
|
newCasualty.ERR_UpdateObserverClient();assert(hints.shown==beforeOld+3);
|
|
|
|
|
newCasualty.epiHint="";newCasualty.ERR_UpdateObserverClient();assert(hints.shown==beforeOld+4&&hints.last.find("epinephrine")==string::npos);
|
|
|
|
|
newCasualty.epiHint="Press Space to use epinephrine";settled(newCasualty);assert(hints.shown==beforeOld+3&&hints.last.find(newCasualty.epiHint)!=string::npos);
|
|
|
|
|
settled(newCasualty);assert(hints.shown==beforeOld+3);
|
|
|
|
|
newCasualty.epiHint="";settled(newCasualty);assert(hints.shown==beforeOld+4&&hints.last.find("epinephrine")==string::npos);
|
|
|
|
|
}
|
|
|
|
|
''')
|
|
|
|
|
for path,signature in ((GUIDE,'protected void ERR_UpdateGuide('),(OBSERVER,'protected void ERR_UpdateObserverClient(')):
|
|
|
|
|