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.
This commit is contained in:
@@ -2,26 +2,27 @@
|
||||
No native layout rendering, multiplayer, or real startup-failure claim.
|
||||
"""
|
||||
from policy_harness import ROOT, body, run
|
||||
SOURCE=ROOT/'addons/core/Scripts/Game/ERR/ERR_PreparationStatus.c'
|
||||
SOURCE=ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_PreparationStatus.c'
|
||||
def cpp(s):
|
||||
s=s.replace('protected ','').replace('override ','').replace('null','nullptr')
|
||||
s=s.replace('ERR_GameMode mode = ERR_GameMode.Get();','ERR_GameMode* mode = ERR_GameMode::Get();')
|
||||
s=s.replace('ERR_GameMode mode,','ERR_GameMode* mode,')
|
||||
s=s.replace('ERR_GameMode mode)', 'ERR_GameMode* mode)')
|
||||
for name in ['System','ERR_GameMode','ERR_Phase','ERR_ObjectiveText']:s=s.replace(name+'.',name+'::')
|
||||
s=s.replace('TextWidget.Cast(','CastText(').replace('Widget widget)', 'Widget* widget)').replace('widget.', 'widget->')
|
||||
for name in ['mode','m_wLoadingPlaceholder','m_ERRPreparationSpinner','m_ERRPreparationRoot','m_ERRPreparationTitle','m_ERRPreparationDescription']:
|
||||
s=s.replace(name+'.',name+'->')
|
||||
s=s.replace('GetGame().','GetGame()->').replace('GetWorkspace().','GetWorkspace()->').replace('.IsEmpty()','.empty()')
|
||||
return s.replace('super.','Native::')
|
||||
methods='\n'.join(cpp(body(SOURCE,key)) for key in ('protected void ERR_ClearPreparationStatus(', 'override void UpdateLoadingPlaceholder(', 'override void DestroyLoadingPlaceholder(', 'protected bool ERR_PreparationWidgetVisible(', 'bool ERR_ProbePreparationVisible('))
|
||||
methods='\n'.join(cpp(body(SOURCE,key)) for key in ('void ERR_KeepSpawnSceneLoading(', 'void ERR_ShowSpawnSceneLoading(', 'protected string ERR_PreparationDescription(', 'protected void ERR_ClearPreparationStatus(', 'override void UpdateLoadingPlaceholder(', 'override void DestroyLoadingPlaceholder(', 'protected bool ERR_PreparationWidgetVisible(', 'bool ERR_ProbePreparationVisible('))
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include <map>
|
||||
using string=std::string;
|
||||
namespace System{bool console=false;bool IsConsoleApp(){return console;}}
|
||||
namespace ERR_Phase{enum{PREPARING,PRISON};}
|
||||
struct ERR_GameMode{static ERR_GameMode* current;int phase=ERR_Phase::PREPARING;string error;static ERR_GameMode* Get(){return current;}int Phase(){return phase;}string PreparationError(){return error;}};ERR_GameMode* ERR_GameMode::current=nullptr;
|
||||
namespace ERR_Phase{enum{PREPARING,PRISON,RADIO_SEARCH,RADIO_DEFENCE,EXTRACTION,VICTORY,DEFEAT};}
|
||||
struct ERR_GameMode{static ERR_GameMode* current;int phase=ERR_Phase::PREPARING;string error;static ERR_GameMode* Get(){return current;}int Phase(){return phase;}bool Active(){return phase>=ERR_Phase::PRISON&&phase<=ERR_Phase::EXTRACTION;}string PreparationError(){return error;}};ERR_GameMode* ERR_GameMode::current=nullptr;
|
||||
namespace ERR_ObjectiveText{string Description(ERR_GameMode* mode,bool){return mode->error.empty()?"Preparing sites":"Mission failed; ask an administrator to correct and restart";}}
|
||||
struct Widget{Widget* parent=nullptr;bool visible=true,removed=false,textWidget=false;string text;int textWrites=0,visibilityWrites=0;float width=900,height=170;std::map<string,Widget*> children;
|
||||
void GetScreenSize(float& w,float& h){w=width;h=height;}Widget* GetParent(){return parent;}void RemoveFromHierarchy(){removed=true;parent=nullptr;}void SetVisible(bool value){visible=value;visibilityWrites++;}bool IsVisible(){return visible;}
|
||||
@@ -29,23 +30,28 @@ struct Widget{Widget* parent=nullptr;bool visible=true,removed=false,textWidget=
|
||||
Widget* CastText(Widget* w){return w&&w->textWidget?w:nullptr;}
|
||||
struct Workspace{int creates=0;bool fail=false,missingText=false;Widget* CreateWidgets(const char*,Widget* parent){creates++;if(fail)return nullptr;auto root=new Widget;root->parent=parent;for(string name:{"ERR_PreparationTitle","ERR_PreparationDescription"}){auto child=new Widget;child->parent=root;child->textWidget=true;root->children[name]=child;}if(missingText)root->children.erase("ERR_PreparationTitle");return root;}};
|
||||
struct Game{Workspace workspace;Workspace* GetWorkspace(){return &workspace;}};Game game;Game* GetGame(){return &game;}
|
||||
struct Native{Widget* m_wLoadingPlaceholder=nullptr;ERR_GameMode* m_pGameMode=nullptr;int nativeUpdates=0,destroys=0;void UpdateLoadingPlaceholder(float){nativeUpdates++;}void DestroyLoadingPlaceholder(){destroys++;if(m_wLoadingPlaceholder)m_wLoadingPlaceholder->RemoveFromHierarchy();m_wLoadingPlaceholder=nullptr;}};
|
||||
struct Native{Widget* m_wLoadingPlaceholder=nullptr;ERR_GameMode* m_pGameMode=nullptr;int nativeUpdates=0,destroys=0;void CreateLoadingPlaceholder(){m_wLoadingPlaceholder=new Widget;}void UpdateLoadingPlaceholder(float){nativeUpdates++;}void DestroyLoadingPlaceholder(){destroys++;if(m_wLoadingPlaceholder)m_wLoadingPlaceholder->RemoveFromHierarchy();m_wLoadingPlaceholder=nullptr;}};
|
||||
struct Probe:Native{
|
||||
Widget *m_ERRPreparationParent=nullptr,*m_ERRPreparationRoot=nullptr,*m_ERRPreparationTitle=nullptr,*m_ERRPreparationDescription=nullptr,*m_ERRPreparationSpinner=nullptr;
|
||||
bool m_ERRPreparationSpinnerHidden=false,m_ERRPreparationSpinnerWasVisible=false;string m_ERRPreparationLastTitle,m_ERRPreparationLastDescription;
|
||||
bool m_ERRKeepSpawnSceneLoading=false,m_ERRPreparationSpinnerHidden=false,m_ERRPreparationSpinnerWasVisible=false;string m_ERRPreparationLastTitle,m_ERRPreparationLastDescription;
|
||||
'''+methods+r'''
|
||||
};
|
||||
struct Fixture{ERR_GameMode mode;Widget parent,spinner;Probe p;Fixture(){game.workspace={};System::console=false;ERR_GameMode::current=&mode;p.m_pGameMode=&mode;p.m_wLoadingPlaceholder=&parent;parent.children["Spinner"]=&spinner;spinner.parent=&parent;}};
|
||||
int main(){
|
||||
{Fixture f;f.mode.phase=ERR_Phase::PRISON;f.p.ERR_ShowSpawnSceneLoading();auto root=f.p.m_ERRPreparationRoot;f.p.ERR_KeepSpawnSceneLoading(true);f.p.DestroyLoadingPlaceholder();assert(!root->removed&&!f.parent.removed&&f.p.destroys==0);f.p.ERR_KeepSpawnSceneLoading(false);f.p.DestroyLoadingPlaceholder();assert(root->removed&&f.p.destroys==1);f.p.ERR_ShowSpawnSceneLoading();assert(f.p.m_wLoadingPlaceholder&&f.p.ERR_ProbePreparationVisible(&f.mode,false));}
|
||||
{Fixture f;f.p.UpdateLoadingPlaceholder(.1f);assert(f.p.nativeUpdates==1&&game.workspace.creates==1&&f.p.ERR_ProbePreparationVisible(&f.mode,false));
|
||||
Widget* root=f.p.m_ERRPreparationRoot;auto title=f.p.m_ERRPreparationTitle;auto description=f.p.m_ERRPreparationDescription;
|
||||
for(int i=0;i<200;i++){f.p.UpdateLoadingPlaceholder(.1f);}assert(game.workspace.creates==1&&title->textWrites==1&&description->textWrites==1);
|
||||
f.mode.error="invalid catalog";f.p.UpdateLoadingPlaceholder(.1f);assert(f.p.ERR_ProbePreparationVisible(&f.mode,true)&&!f.spinner.visible);
|
||||
for(int i=0;i<200;i++){f.p.UpdateLoadingPlaceholder(.1f);}assert(f.p.m_ERRPreparationRoot==root&&title->textWrites==2&&description->textWrites==2&&f.spinner.visibilityWrites==1);
|
||||
title->width=0;assert(!f.p.ERR_ProbePreparationVisible(&f.mode,true));title->width=900;f.mode.error.clear();f.p.UpdateLoadingPlaceholder(.1f);assert(f.spinner.visible&&f.p.ERR_ProbePreparationVisible(&f.mode,false));
|
||||
f.mode.phase=ERR_Phase::PRISON;f.p.UpdateLoadingPlaceholder(.1f);assert(root->removed&&!f.parent.removed&&f.spinner.visible&&!f.p.m_ERRPreparationRoot);
|
||||
f.mode.phase=ERR_Phase::PRISON;f.p.UpdateLoadingPlaceholder(.1f);assert(!root->removed&&!f.parent.removed&&f.spinner.visible&&f.p.ERR_ProbePreparationVisible(&f.mode,false));
|
||||
assert(description->text=="Please wait while your spawn area and equipment finish loading. You will take control when preparation is complete.");
|
||||
for(int phase=ERR_Phase::PRISON;phase<=ERR_Phase::EXTRACTION;phase++){f.mode.phase=phase;for(int i=0;i<200;i++)f.p.UpdateLoadingPlaceholder(.1f);assert(f.p.m_ERRPreparationRoot==root&&f.p.ERR_ProbePreparationVisible(&f.mode,false));}
|
||||
f.mode.phase=ERR_Phase::VICTORY;f.p.UpdateLoadingPlaceholder(.1f);assert(root->removed&&!f.parent.removed&&f.spinner.visible&&!f.p.m_ERRPreparationRoot);
|
||||
}
|
||||
for(int mismatch=0;mismatch<6;mismatch++){Fixture f;ERR_GameMode other;if(mismatch==0)ERR_GameMode::current=nullptr;if(mismatch==1)f.p.m_pGameMode=&other;if(mismatch==2)f.mode.phase=ERR_Phase::PRISON;if(mismatch==3)System::console=true;if(mismatch==4)f.p.m_wLoadingPlaceholder=nullptr;if(mismatch==5)f.p.m_pGameMode=nullptr;f.p.UpdateLoadingPlaceholder(.1f);assert(game.workspace.creates==0&&f.p.nativeUpdates==1&&!f.parent.removed&&f.spinner.visibilityWrites==0);}
|
||||
for(int mismatch=0;mismatch<6;mismatch++){Fixture f;ERR_GameMode other;if(mismatch==0)ERR_GameMode::current=nullptr;if(mismatch==1)f.p.m_pGameMode=&other;if(mismatch==2)f.mode.phase=ERR_Phase::VICTORY;if(mismatch==3)System::console=true;if(mismatch==4)f.p.m_wLoadingPlaceholder=nullptr;if(mismatch==5)f.p.m_pGameMode=nullptr;f.p.UpdateLoadingPlaceholder(.1f);assert(game.workspace.creates==0&&f.p.nativeUpdates==1&&!f.parent.removed&&f.spinner.visibilityWrites==0);}
|
||||
{Fixture f;f.mode.phase=ERR_Phase::PRISON;f.p.UpdateLoadingPlaceholder(.1f);assert(f.p.ERR_ProbePreparationVisible(&f.mode,false));Widget* root=f.p.m_ERRPreparationRoot;f.p.DestroyLoadingPlaceholder();assert(root->removed&&f.parent.removed&&!f.p.m_ERRPreparationRoot&&f.p.destroys==1);f.p.UpdateLoadingPlaceholder(.1f);assert(game.workspace.creates==1);}
|
||||
{Fixture f;f.mode.error="bad";f.spinner.visible=false;f.p.UpdateLoadingPlaceholder(.1f);assert(f.p.ERR_ProbePreparationVisible(&f.mode,true));f.p.ERR_ClearPreparationStatus();assert(!f.spinner.visible);}
|
||||
{Fixture f;f.mode.error="bad";f.p.UpdateLoadingPlaceholder(.1f);Widget* old=f.p.m_ERRPreparationRoot;Widget newParent,newSpinner;newParent.children["Spinner"]=&newSpinner;newSpinner.parent=&newParent;f.p.m_wLoadingPlaceholder=&newParent;f.p.UpdateLoadingPlaceholder(.1f);assert(old->removed&&f.spinner.visible&&!newSpinner.visible&&f.p.m_ERRPreparationParent==&newParent&&game.workspace.creates==2);f.p.DestroyLoadingPlaceholder();assert(newParent.removed&&!f.parent.removed&&newSpinner.visible&&!f.p.m_ERRPreparationRoot&&f.p.destroys==1);}
|
||||
{Fixture f;f.mode.error="bad";f.p.UpdateLoadingPlaceholder(.1f);f.p.m_ERRPreparationParent=nullptr;f.p.m_ERRPreparationRoot=nullptr;f.p.m_ERRPreparationTitle=nullptr;f.p.m_ERRPreparationDescription=nullptr;f.p.m_ERRPreparationSpinner=nullptr;f.p.m_wLoadingPlaceholder=nullptr;f.p.UpdateLoadingPlaceholder(.1f);assert(!f.p.m_ERRPreparationSpinnerHidden);}
|
||||
@@ -54,7 +60,7 @@ int main(){
|
||||
{Fixture f;f.p.UpdateLoadingPlaceholder(.1f);Widget other;Widget* moved=f.p.m_ERRPreparationRoot;moved->parent=&other;f.p.UpdateLoadingPlaceholder(.1f);assert(moved->removed&&f.p.m_ERRPreparationRoot!=moved&&f.p.m_ERRPreparationParent==&f.parent);}
|
||||
}
|
||||
''')
|
||||
probe=ROOT/'addons/core/Scripts/Game/ERR/ERR_NativePreparationStatusProbe.c'
|
||||
probe=ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_NativePreparationStatusProbe.c'
|
||||
s=body(probe,'static int Tick(').replace('ERR_GameMode mode,','ERR_GameMode* mode,').replace('ERR_GameMode.Get()','ERR_GameMode::Get()').replace('ERR_Phase.PREPARING','PREPARING').replace('mode.','mode->').replace('.IsEmpty()','.empty()').replace('SCR_RespawnSystemComponent respawn','SCR_RespawnSystemComponent* respawn').replace('SCR_RespawnSystemComponent.GetInstance()','SCR_RespawnSystemComponent::GetInstance()').replace('respawn.','respawn->').replace('System.MakeScreenshot','System::MakeScreenshot')
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
@@ -72,8 +78,10 @@ int main(){ERR_GameMode mode;ERR_GameMode::current=&mode;
|
||||
}
|
||||
''')
|
||||
text=SOURCE.read_text()
|
||||
prefab=(ROOT/'addons/core/Prefabs/ERR/GameMode.et').read_text()
|
||||
assert 'm_bUseSpawnPreload 1' in prefab and 'm_bUseSpawnPreload 0' not in prefab
|
||||
assert 'GetOwner(' not in text
|
||||
assert text.count('m_pGameMode != mode') == 2
|
||||
assert text.count('m_pGameMode != mode') == 3
|
||||
# m_pGameMode is the installed vanilla protected owner field, assigned by
|
||||
# SCR_RespawnSystemComponent.OnInit(IEntity owner). The adapter deliberately
|
||||
# exposes that field rather than inventing a ScriptComponent.GetOwner API.
|
||||
@@ -81,7 +89,7 @@ for forbidden in ('CallLater(', 'SetCamera(', 'SpawnEntity', 'SetControlledEntit
|
||||
assert forbidden not in text,forbidden
|
||||
native=probe.read_text()
|
||||
for forbidden in ('CreateWidgets(', 'UpdateLoadingPlaceholder(', 'SetText(', 'SetVisible('):assert forbidden not in native
|
||||
base=(ROOT/'addons/core/Scripts/Game/ERR/ERR_NativeBaseSmoke.c').read_text()
|
||||
base=(ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_NativeBaseSmoke.c').read_text()
|
||||
assert 'ERR_NativePreparationStatusProbe.Tick(this, now)' in base
|
||||
assert 'native-end-active' in base and 'native-end-preparing' in base
|
||||
print('PASS: actual preparation status scope, persistent updates, spinner restoration, placeholder replacement/deletion/destruction, native observation persistence/timeout; rendering remains native-pending')
|
||||
|
||||
Reference in New Issue
Block a user