Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
106 lines
5.6 KiB
Python
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","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')
|