Merge owned systems into one addon while preserving resource identities. Gate player admission on the replicated prison scene, restore the original framed door, and hold enemy AI until escape. Correct scenario branding and author metadata, include publishing assets, and retain regression and native verification evidence.
58 lines
5.2 KiB
Python
58 lines
5.2 KiB
Python
"""Actual result callbacks/UI lifecycle with engine boundaries doubled, not native replication."""
|
|
import os,re,sys
|
|
from pathlib import Path
|
|
REPO=Path(os.environ.get('ERR_REPO','/home/jakub/devel/escapistsrereintroduced'))
|
|
sys.path.insert(0,str(REPO/'tests'))
|
|
from policy_harness import body,run
|
|
SOURCE=Path(os.environ.get('ERR_RESULT_SOURCE',REPO/'addons/core/Scripts/Game/ERR/00_Core/ERR_EscapeGameOverScreenInfo.c'))
|
|
MODE=Path(os.environ.get('ERR_MODE_SOURCE',REPO/'addons/core/Scripts/Game/ERR/00_Core/ERR_GameMode.c'))
|
|
def adapt(s):
|
|
s=s.replace('protected ','').replace('override ','').replace('null','nullptr')
|
|
s=s.replace('ERR_GameMode.Get()','Mode::Get()').replace('ERR_GameMode mode','Mode* mode')
|
|
s=s.replace('ERR_FailureGameOverScreenInfo.Cast(', 'FailureInfo::Cast(').replace('ERR_EscapeGameOverScreenInfo.Cast(', 'EscapeInfo::Cast(')
|
|
s=s.replace('ERR_FailureGameOverScreenInfo info = new ERR_FailureGameOverScreenInfo();','FailureInfo info;')
|
|
s=s.replace('TextWidget.Cast(', 'TextWidget::Cast(').replace('TextWidget debrief','TextWidget* debrief')
|
|
s=s.replace('SCR_PlayerController.ERR_OnEscapeResultChanged()','escapeEvent')
|
|
s=s.replace('SCR_GameOverScreenUIContentData endScreenUIContent','Data* endScreenUIContent').replace('Widget w','int w')
|
|
s=s.replace('array<int> otherPlayerIds','void* otherPlayerIds').replace('string ','std::string ')
|
|
s=s.replace('super.InitContent(endScreenUIContent);','nativeInit++;').replace('super.HandlerDeattached(w);','nativeDetach++;')
|
|
for name in ('m_ERRFailureMode','m_wRoot','debrief','mode','endScreenUIContent'):s=s.replace(name+'.',name+'->')
|
|
s=s.replace('OnEndReasonChanged().','OnEndReasonChanged()->').replace('EndReason().IsEmpty()','EndReason().empty()')
|
|
s=s.replace('Insert(ERR_RefreshFailureContent)','Insert(1)').replace('Remove(ERR_RefreshFailureContent)','Remove(1)')
|
|
s=s.replace('Insert(ERR_RefreshEscapeContent)','Insert(2)').replace('Remove(ERR_RefreshEscapeContent)','Remove(2)')
|
|
return s
|
|
failure=body(SOURCE,'class ERR_FailureGameOverScreenInfo')
|
|
failure=failure[failure.index(' override string'):failure.rindex('}')]
|
|
callbacks='\n'.join(body(SOURCE,x) for x in ('override void InitContent(','protected void ERR_RefreshFailureContent(','override void HandlerDeattached('))
|
|
notify=body(MODE,'protected void ERR_OnEndReasonReplicated(')
|
|
assert '[RplProp(onRplName: "ERR_OnEndReasonReplicated")]' in MODE.read_text()
|
|
run(r'''
|
|
#include <cassert>
|
|
#include <string>
|
|
#include <functional>
|
|
struct Event{int count=0;std::function<void()> handler;void Insert(int){count=1;}void Remove(int){count=0;}void Invoke(){if(count&&handler)handler();}};
|
|
struct Mode{static inline Mode* current=nullptr;Event m_OnEndReasonChanged;std::string reason;static Mode* Get(){return current;}std::string EndReason(){return reason;}Event* OnEndReasonChanged(){return &m_OnEndReasonChanged;}
|
|
NOTIFY
|
|
};
|
|
struct FailureInfo{static bool Cast(int kind){return kind==1;}
|
|
FAILURE
|
|
};
|
|
struct EscapeInfo{static EscapeInfo* Cast(int kind){static EscapeInfo value;return kind==2?&value:nullptr;}};
|
|
struct TextWidget{std::string text;int writes=0;static TextWidget* Cast(TextWidget* w){return w;}void SetText(std::string value){text=value;writes++;}};
|
|
struct Root{TextWidget debrief;bool exists=true;TextWidget* FindAnyWidget(std::string){return exists?&debrief:nullptr;}};
|
|
struct Data{int m_GameOverInfo;};
|
|
struct Content{Event escapeEvent;bool m_ERREscapeContent=false;Mode* m_ERRFailureMode=nullptr;Root* m_wRoot=nullptr;std::string m_sDebriefingName="debrief";int nativeInit=0,nativeDetach=0,escapeRefresh=0;void ERR_RefreshEscapeContent(){escapeRefresh++;}
|
|
CALLBACKS
|
|
};
|
|
int main(){Mode mode;Mode::current=&mode;Root root;Content content;content.m_wRoot=&root;mode.m_OnEndReasonChanged.handler=[&](){content.ERR_RefreshFailureContent();};Data failure{1},escape{2},other{3};
|
|
content.InitContent(&failure);assert(mode.m_OnEndReasonChanged.count==1&&root.debrief.text.find("No surviving")!=std::string::npos);
|
|
mode.reason="An administrator abandoned this round.";mode.ERR_OnEndReasonReplicated();assert(root.debrief.text==mode.reason&&root.debrief.writes==2);
|
|
content.HandlerDeattached(0);assert(mode.m_OnEndReasonChanged.count==0&&content.m_ERRFailureMode==nullptr);mode.reason="late";mode.ERR_OnEndReasonReplicated();assert(root.debrief.writes==2);
|
|
content.InitContent(&failure);assert(root.debrief.writes==3);content.InitContent(&escape);assert(mode.m_OnEndReasonChanged.count==0&&content.escapeEvent.count==1&&content.escapeRefresh==1);mode.ERR_OnEndReasonReplicated();assert(root.debrief.writes==3);
|
|
content.InitContent(&failure);assert(content.escapeEvent.count==0);Mode replacement;Mode::current=&replacement;mode.ERR_OnEndReasonReplicated();assert(root.debrief.writes==4);
|
|
Mode::current=&mode;content.m_wRoot=nullptr;mode.ERR_OnEndReasonReplicated();assert(root.debrief.writes==4);content.m_wRoot=&root;root.exists=false;mode.ERR_OnEndReasonReplicated();assert(root.debrief.writes==4);
|
|
content.InitContent(&other);assert(content.m_ERRFailureMode==nullptr&&mode.m_OnEndReasonChanged.count==0&&content.escapeEvent.count==0);
|
|
}
|
|
'''.replace('NOTIFY',adapt(notify)).replace('FAILURE',adapt(failure)).replace('CALLBACKS',adapt(callbacks)))
|
|
print('PASS: late authoritative failure reason refreshes open result; detach, mode change and success subscriptions preserved')
|