Files
EscapistsReintroduced/tests/test_escape_result_wording.py
T
jakub cab998fb64 Combine Workshop addon and fix prison startup
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.
2026-09-20 16:42:52 +02:00

106 lines
5.6 KiB
Python

"""Run actual personal-result title/debrief and refresh methods with UI adapters.
No native replication, end-screen rendering or multiplayer claim.
"""
import re
from policy_harness import ROOT, body, run
SOURCE=ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_EscapeGameOverScreenInfo.c'
methods='\n'.join(body(SOURCE, signature) for signature in (
'protected ERR_PersonalEscapeResult PersonalResult(',
'override string GetTitle(',
'override string GetDebriefing('))
refresh=body(SOURCE,'protected void ERR_RefreshEscapeContent(')
def adapt(text):
text=re.sub(r' #ifdef WORKBENCH\n.*? #endif\n','',text,flags=re.S)
text=text.replace('protected ','').replace('override ','')
text=text.replace('ERR_PersonalEscapeResult.','Result::').replace('ERR_PersonalEscapeResult','Result::Value')
text=text.replace('array<int> otherPlayerIds','std::vector<int>* otherPlayerIds')
text=re.sub(r'\bstring\b','std::string',text)
text=text.replace('GetGame().GetPlayerController()', 'GetGame()->GetPlayerController()')
text=text.replace('SCR_PlayerController.Cast(', 'Controller::Cast(').replace('SCR_PlayerController controller', 'Controller* controller')
text=text.replace('TextWidget.Cast(', 'TextWidget::Cast(')
text=text.replace('TextWidget title', 'TextWidget* title').replace('TextWidget debrief', 'TextWidget* debrief')
# Enforce manages this local object; the C++ adapter uses scoped ownership.
text=text.replace('ERR_EscapeGameOverScreenInfo info = new ERR_EscapeGameOverScreenInfo();', 'auto info = std::make_unique<Info>();')
for variable in ('controller','info','title','debrief','m_wRoot'):
text=text.replace(variable+'.',variable+'->')
text=re.sub(r'\bnull\b','nullptr',text)
return text
run(r'''
#include <cassert>
#include <memory>
#include <string>
#include <vector>
namespace Result {enum Value {UNKNOWN,ESCAPED,MISSED};}
struct Controller {
int id=7;Result::Value result=Result::UNKNOWN;
static Controller* Cast(Controller* value){return value;}
int GetPlayerId(){return id;}Result::Value ERR_EscapeResult(){return result;}
};
struct Game {Controller* local=nullptr;Controller* GetPlayerController(){return local;}} game;
Game* GetGame(){return &game;}
struct Info {
'''+adapt(methods)+r'''
};
struct TextWidget {std::string value;int writes=0;
static TextWidget* Cast(TextWidget* widget){return widget;}
void SetText(std::string text){value=text;writes++;}
};
struct Root {TextWidget title,debrief;bool hasTitle=true,hasDebrief=true;
TextWidget* FindAnyWidget(std::string name){if(name=="title")return hasTitle?&title:nullptr;return hasDebrief?&debrief:nullptr;}
};
struct Content {
bool m_ERREscapeContent=true;Root* m_wRoot=nullptr;
std::string m_sTileName="title",m_sDebriefingName="debrief";
'''+adapt(refresh)+r'''
};
int main(){
Controller controller;game.local=&controller;Info info;
std::vector<int> staleWinners={7};std::vector<int> noWinners;
const char* titles[]={"Round ended","You escaped Everon","The helicopter escaped"};
const char* descriptions[]={"Your personal escape result is not available.","You crossed the extraction boundary aboard the helicopter.","You did not escape with the helicopter."};
for(int state=0;state<3;state++){
controller.result=static_cast<Result::Value>(state);
for(auto* winners:{&staleWinners,&noWinners,static_cast<std::vector<int>*>(nullptr)}){
assert(info.GetTitle(7,winners)==titles[state]);
assert(info.GetDebriefing(7,winners)==descriptions[state]);
// A different connection ID cannot use this local controller's result.
assert(info.GetTitle(8,winners)==titles[0]);
assert(info.GetDebriefing(8,winners)==descriptions[0]);
}
}
game.local=nullptr;
assert(info.GetTitle(7,&staleWinners)==titles[0]);
assert(info.GetDebriefing(7,&staleWinners)==descriptions[0]);
// The unchanged actual refresh method pulls only the current personal result;
// late arrival, account reset, and missing local controller refresh both texts.
game.local=&controller;Root root;Content content;content.m_wRoot=&root;
for(int state:{0,1,2,0}){
controller.result=static_cast<Result::Value>(state);content.ERR_RefreshEscapeContent();
assert(root.title.value==titles[state]&&root.debrief.value==descriptions[state]);
}
game.local=nullptr;content.ERR_RefreshEscapeContent();
assert(root.title.value==titles[0]&&root.debrief.value==descriptions[0]);
assert(root.title.writes==5&&root.debrief.writes==5);
content.m_ERREscapeContent=false;content.ERR_RefreshEscapeContent();
assert(root.title.writes==5&&root.debrief.writes==5);
content.m_ERREscapeContent=true;content.m_wRoot=nullptr;content.ERR_RefreshEscapeContent();
assert(root.title.writes==5&&root.debrief.writes==5);
content.m_wRoot=&root;root.hasTitle=false;content.ERR_RefreshEscapeContent();
assert(root.title.writes==5&&root.debrief.writes==6);
root.hasTitle=true;root.hasDebrief=false;content.ERR_RefreshEscapeContent();
assert(root.title.writes==6&&root.debrief.writes==6);
}
''')
# Keep native subscription/detach wiring intact; actual text refresh is executed above.
init=body(SOURCE,'override void InitContent(')
detach=body(SOURCE,'override void HandlerDeattached(')
assert 'SCR_PlayerController.ERR_OnEscapeResultChanged().Remove(ERR_RefreshEscapeContent);' in init
assert 'SCR_PlayerController.ERR_OnEscapeResultChanged().Insert(ERR_RefreshEscapeContent);' in init
assert 'ERR_RefreshEscapeContent();' in init
assert 'SCR_PlayerController.ERR_OnEscapeResultChanged().Remove(ERR_RefreshEscapeContent);' in detach
assert 'super.HandlerDeattached(w);' in detach
print('PASS: actual three-state result wording and UI refresh; stale winner IDs never manufacture escape')