Files
EscapistsReintroduced/tests/test_item_station_lifecycle.py
T
jakub 6a667d8deb Capture current mission, AI, map and administration development
Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
2026-10-13 22:52:53 +02:00

71 lines
5.6 KiB
Python

"""Execute production item-station lifecycle with modeled native subscriptions."""
from policy_harness import ROOT, body, run
p=ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_ItemStationLifecycle.c'
code='\n'.join(body(p,x) for x in ['override void OnPostInit(', 'protected override void DelayedInit(', 'protected override void AddRemoveOnDamageStateChanged(', 'override void OnDelete('])
code=code.replace('protected ','').replace('override ','').replace('IEntity owner','IEntity* owner')
for name in ['owner','m_ERRItemStationManager','m_ERRItemStationResource']:
code=code.replace(name+'.',name+'->')
for name in ['SCR_Global','ERR_GameMode','ERR_TerrainTakeoverSettings','SCR_EntityCatalogManagerComponent','SCR_SupportStationManagerComponent']:
code=code.replace(name+'.',name+'::')
code=code.replace('GetGame().','GetGame()->').replace('Remove(DelayedInit)','Remove(0)').replace('Remove(TEMP_OnInteractorReplicated)','Remove(1)')
for method in ['OnPostInit','DelayedInit','OnDelete','AddRemoveOnDamageStateChanged']:
code=code.replace('super.'+method,'Native'+method)
run(r'''
#include <cassert>
#define null nullptr
namespace SCR_Global{bool editing=false;bool IsEditMode(){return editing;}}
namespace ERR_GameMode{bool present=true;bool Get(){return present;}}
namespace ERR_TerrainTakeoverSettings{bool enabled=true;bool Enabled(){return enabled;}}
namespace SCR_EntityCatalogManagerComponent{bool present=true;bool GetInstance(){return present;}}
struct Queue{int cancelled=0;void Remove(int){cancelled++;}};
struct Game{int world=1;Queue queue;int GetWorld(){return world;}Queue& GetCallqueue(){return queue;}}game;
Game* gamePtr=&game;Game* GetGame(){return gamePtr;}
struct Event{int subscribed=0,removed=0;void Remove(int){subscribed=0;removed++;}};
struct Resource{Event event;Event& TEMP_GetOnInteractorReplicated(){return event;}};
struct Manager{int removed=0;void RemoveSupportStation(void*){removed++;}}oldManager,newManager;
namespace SCR_SupportStationManagerComponent{Manager* current=&oldManager;Manager* GetInstance(){return current;}}
struct IEntity{int world=1;int GetWorld(){return world;}};
struct Station{
bool m_ERRItemStationScoped=false,m_ERRItemStationInitializing=false,m_ERRItemStationInitialized=false,m_ERRItemStationDeleted=false;
Resource* m_ERRItemStationResource=nullptr;Manager* m_ERRItemStationManager=nullptr;
bool m_ResourceConsumer=false,m_ResourceGenerator=false,range=false,invalid=false;
Resource resource;int initCalls=0,errors=0,deleteCalls=0,damageAdded=0,damageRemoved=0;
Resource* GetResourceComponent(){return &resource;}bool UsesRange(){return range;}
void NativeOnPostInit(IEntity*){}
void NativeDelayedInit(IEntity* owner){initCalls++;if(!SCR_EntityCatalogManagerComponent::present||invalid){errors++;return;}m_ResourceConsumer=true;resource.event.subscribed++;AddRemoveOnDamageStateChanged(owner,true);}
void NativeOnDelete(IEntity*){deleteCalls++;}
void NativeAddRemoveOnDamageStateChanged(IEntity*,bool subscribe){if(subscribe)damageAdded++;else damageRemoved++;}
'''+code+r'''
};
int main(){
IEntity main,preview{2};
// Live initialization validates normally and records both subscriptions.
Station live;live.range=true;live.OnPostInit(&main);live.DelayedInit(&main);
assert(live.m_ERRItemStationInitialized&&live.damageAdded==1&&live.resource.event.subscribed==1);
// Destroy the catalog and replace the global manager before cleanup.
SCR_EntityCatalogManagerComponent::present=false;SCR_SupportStationManagerComponent::current=&newManager;
live.OnDelete(&main);live.OnDelete(&main);assert(!live.deleteCalls&&live.damageRemoved==1&&live.resource.event.removed==1);
assert(oldManager.removed==1&&newManager.removed==0&&!live.m_ERRItemStationResource&&!live.m_ERRItemStationManager);
live.DelayedInit(&main);assert(live.initCalls==1); // Late queued init cannot resurrect subscriptions.
// Delete before the scheduled initialization: no subscription and no startup revalidation.
Station unborn;unborn.OnPostInit(&main);unborn.OnDelete(&main);unborn.DelayedInit(&main);
assert(unborn.initCalls==0&&unborn.damageRemoved==0&&unborn.errors==0);
// A catalog-less preload object is not a live station and requires no validation.
Station preload;preload.OnPostInit(&preview);preload.DelayedInit(&preview);preload.OnDelete(&preview);
assert(!preload.initCalls&&!preload.errors&&!preload.damageRemoved);
// Missing catalogs in the MAIN world remain a real validation error.
Station broken;broken.OnPostInit(&main);broken.DelayedInit(&main);assert(broken.errors==1&&!broken.m_ERRItemStationInitialized);
broken.OnDelete(&main);assert(!broken.damageRemoved);
// Non-range stations also detach the resource event, which native teardown skipped.
SCR_EntityCatalogManagerComponent::present=true;Station portable;portable.OnPostInit(&main);portable.DelayedInit(&main);portable.OnDelete(&main);
assert(portable.resource.event.removed==1&&newManager.removed==0);
Station badType;badType.invalid=true;badType.OnPostInit(&main);badType.DelayedInit(&main);badType.OnDelete(&main);assert(badType.errors==1&&!badType.damageRemoved);
// Opted-out unrelated modes and editor mode keep their original lifecycle.
ERR_GameMode::present=false;ERR_TerrainTakeoverSettings::enabled=false;
Station unrelated;unrelated.OnPostInit(&main);unrelated.OnDelete(&main);assert(unrelated.deleteCalls==1);
ERR_TerrainTakeoverSettings::enabled=true;SCR_Global::editing=true;
Station editing;editing.OnPostInit(&preview);editing.OnDelete(&preview);assert(editing.deleteCalls==1);
}
''')
print('PASS: catalog-independent cleanup removes original subscriptions once, handles never-initialized/preload items, preserves live validation and opted-out/editor behavior')