Files
EscapistsReintroduced/addons/map/Scripts/Game/ERRMap/ERRM_NativeMapOwnershipProbe.c
T

329 lines
16 KiB
C

#ifdef WORKBENCH
// Opt-in local UI evidence. These adapters execute production methods and always
// delegate their overrides; no native map/camera operation is intercepted.
modded class ERRM_SharedMapComponent
{
protected ERRM_NativeMapOwnershipProbe m_ERRMOwnershipProbe;
protected int m_ERRMOwnershipSlot;
void ERRM_ObserveOwnership(ERRM_NativeMapOwnershipProbe probe, int slot) { m_ERRMOwnershipProbe = probe; m_ERRMOwnershipSlot = slot; }
Widget ERRM_OwnershipRoot() { return m_ESMRootWidget; }
bool ERRM_OwnershipReady() { return m_ESMRootWidget && m_ESMRenderTarget; }
bool ERRM_BeginOwnershipCapture(IEntity holder)
{
m_bESMInHand = true;
m_ESMCharacterOwner = holder;
TryCaptureMapSurface();
// Deliberately delay only these fixture-owned callbacks until the replacement
// map has completed native initialization. The real method bodies run below.
GetGame().GetCallqueue().Remove(CenterMapOnHolder);
GetGame().GetCallqueue().Remove(FinishMapCapture);
return OwnsMapCapture();
}
void ERRM_StaleOwnershipCenter() { CenterMapOnHolder(); }
void ERRM_QueueStaleOwnershipCallbacks()
{
GetGame().GetCallqueue().CallLater(CenterMapOnHolder, 100, false);
GetGame().GetCallqueue().CallLater(FinishMapCapture, 200, false);
}
void ERRM_StaleOwnershipCamera() { RestoreCharacterCamera(); }
void ERRM_StopOwnershipCapture()
{
StopMapCapture();
// Include the real successor + vanilla ModeClear chain, even for a spare map.
ModeClear(EGadgetMode.IN_HAND);
}
protected override void OnDelete(IEntity owner)
{
ERRM_NativeMapOwnershipProbe probe = m_ERRMOwnershipProbe;
int slot = m_ERRMOwnershipSlot;
super.OnDelete(owner);
// Observe after the complete production/vanilla chain, without clearing any
// evidence references or overriding the native deletion operation.
if (probe) probe.ObserveDeletion(slot, !m_ESMRootWidget && !m_ESMRenderTarget && !m_ESMMapEntity && !m_ESMMapConfig && !m_ESMCharacterOwner && !m_bESMOwnsMapRenderer && !m_bESMInHand);
}
protected override void CenterMapOnHolder()
{
if (m_ERRMOwnershipProbe) m_ERRMOwnershipProbe.ObserveCenter();
super.CenterMapOnHolder();
}
protected override void FinishMapCapture()
{
if (m_ERRMOwnershipProbe) m_ERRMOwnershipProbe.ObserveFinish();
super.FinishMapCapture();
}
protected override void RestoreCharacterCamera()
{
if (m_ERRMOwnershipProbe) m_ERRMOwnershipProbe.ObserveRestore(OwnsMapCapture());
super.RestoreCharacterCamera();
}
protected override void CompleteMapModeSwitch()
{
if (m_ERRMOwnershipProbe) m_ERRMOwnershipProbe.ObserveCompletion();
super.CompleteMapModeSwitch();
}
}
class ERRM_NativeMapOwnershipProbe
{
protected static const ResourceName MAP_PREFAB = "{A46500E4A30B653B}Prefabs/Items/Equipment/ERRM_SharedFieldMap.et";
protected static const ResourceName MAP_LAYOUT = "{255E1265F702B4AE}UI/layouts/ERRMap/SharedMapRenderTarget.layout";
protected static const ResourceName MAP_CONFIG = "{A786DD4868598F15}Configs/Map/MapPlain.conf";
protected SCR_MapEntity m_Map;
protected IEntity m_CaptureEntity;
protected IEntity m_SpareEntity;
protected ERRM_SharedMapComponent m_Capture;
protected ERRM_SharedMapComponent m_Spare;
protected IEntity m_Holder;
protected Widget m_ReplacementRoot;
protected ref MapConfiguration m_ReplacementConfig;
protected MapConfiguration m_ReadyConfig;
protected int m_Step;
protected int m_CloseEvents;
protected int m_RestoreCalls;
protected int m_RestoreAuthorized;
protected int m_Completions;
protected int m_CenterCalls;
protected int m_FinishCalls;
protected int m_CenterBaseline;
protected int m_FinishBaseline;
protected int m_CloseBaseline;
protected int m_RestoreBaseline;
protected int m_AuthorizedBaseline;
protected int m_CompletionBaseline;
protected vector m_PanBaseline;
protected float m_ZoomBaseline;
protected bool m_DeleteRequested;
protected bool m_DeleteStateFailed;
protected int m_CaptureDeleted;
protected int m_SpareDeleted;
protected float m_DeleteDeadline;
protected float m_Deadline;
protected float m_WaitUntil;
protected bool m_Stopped;
protected bool m_Done;
static bool Enabled()
{
string enabled, outcome;
return System.GetCLIParam("errSmoke", enabled) && enabled == "1" && System.GetCLIParam("errSmokeOutcome", outcome) && outcome == "map-ownership";
}
void ObserveRestore(bool authorized)
{
m_RestoreCalls++;
if (authorized) m_RestoreAuthorized++;
}
void ObserveDeletion(int slot, bool released)
{
if (!m_DeleteRequested || !released) m_DeleteStateFailed = true;
if (slot == 1) m_CaptureDeleted++;
else if (slot == 2) m_SpareDeleted++;
else m_DeleteStateFailed = true;
Print(string.Format("ERR MAP DELETE: afterSuper slot=%1 released=%2 captureCallbacks=%3 spareCallbacks=%4", slot, released, m_CaptureDeleted, m_SpareDeleted));
}
protected bool DeletionComplete()
{
return m_DeleteRequested && !m_SpareEntity && !m_CaptureEntity && !m_Spare && !m_Capture && m_CaptureDeleted == 1 && m_SpareDeleted == 1 && !m_DeleteStateFailed;
}
protected void RequestOwnedDeletion()
{
if (m_DeleteRequested) return;
m_DeleteRequested = true;
if (m_SpareEntity) SCR_EntityHelper.DeleteEntityAndChildren(m_SpareEntity);
if (m_CaptureEntity) SCR_EntityHelper.DeleteEntityAndChildren(m_CaptureEntity);
}
protected void LogDeletionState(string when)
{
Print(string.Format("ERR MAP DELETE: %1 captureEntity=%2 spareEntity=%3 captureComponent=%4 spareComponent=%5 captureCallbacks=%6 spareCallbacks=%7 cleanupFailed=%8", when, m_CaptureEntity != null, m_SpareEntity != null, m_Capture != null, m_Spare != null, m_CaptureDeleted, m_SpareDeleted, m_DeleteStateFailed));
}
void ObserveCompletion() { m_Completions++; }
void ObserveCenter() { m_CenterCalls++; }
void ObserveFinish() { m_FinishCalls++; }
protected void OnMapClosed(MapConfiguration config) { m_CloseEvents++; }
protected void OnMapReady(MapConfiguration config) { m_ReadyConfig = config; }
protected bool Check(bool condition, string detail)
{
if (condition) { Print("ERR SMOKE PASS: shared map ownership " + detail); return true; }
Print("ERR SMOKE FAIL: shared map ownership " + detail, LogLevel.ERROR);
Stop(); return false;
}
protected IEntity SpawnMap(vector origin)
{
EntitySpawnParams params = new EntitySpawnParams();
params.TransformMode = ETransformMode.WORLD;
params.Transform[3] = origin;
return GetGame().SpawnEntityPrefab(Resource.Load(MAP_PREFAB), GetGame().GetWorld(), params);
}
// A distinct native configuration with the same vanilla plain-map settings.
// It intentionally shares immutable settings/module arrays with the closed
// capture configuration; neither configuration is serialized or resource-edited.
protected MapConfiguration CopyPlainConfig(MapConfiguration source, Widget root)
{
MapConfiguration copy = new MapConfiguration();
copy.MapEntityMode = source.MapEntityMode;
copy.RootWidgetRef = root;
copy.LayerCount = source.LayerCount;
copy.MapPropsConfig = source.MapPropsConfig;
copy.LayerConfig = source.LayerConfig;
copy.DescriptorVisibilityConfig = source.DescriptorVisibilityConfig;
copy.DescriptorDefsConfig = source.DescriptorDefsConfig;
copy.Modules = source.Modules;
copy.Components = source.Components;
copy.OtherComponents = source.OtherComponents;
return copy;
}
protected bool ReplacementIsCurrent()
{
return m_Map && SCR_MapEntity.GetMapInstance() == m_Map && m_Map.IsOpen() && m_ReplacementConfig && m_Map.GetMapConfig() == m_ReplacementConfig && m_ReplacementConfig.RootWidgetRef == m_ReplacementRoot && m_Map.GetMapMenuRoot() == m_ReplacementRoot;
}
protected void SaveCounters()
{
m_CloseBaseline = m_CloseEvents;
m_RestoreBaseline = m_RestoreCalls;
m_AuthorizedBaseline = m_RestoreAuthorized;
m_CompletionBaseline = m_Completions;
m_CenterBaseline = m_CenterCalls; m_FinishBaseline = m_FinishCalls;
}
protected bool BeginReplacement(bool reuseConfig)
{
if (!Check(m_Map && !m_Map.IsOpen() && m_Capture.ERRM_BeginOwnershipCapture(m_Holder), "actual owned prefab captures its native plain-map root")) return false;
MapConfiguration captured = m_Map.GetMapConfig();
if (!Check(captured && captured.RootWidgetRef == m_Capture.ERRM_OwnershipRoot(), "capture config belongs to exact fixture component")) return false;
// Close only the just-verified fixture view before replacing it; this also
// avoids the native double-open warning. Delayed callbacks remain stale.
m_Map.CloseMap();
if (reuseConfig) m_ReplacementConfig = m_Map.SetupMapConfig(EMapEntityMode.PLAIN, MAP_CONFIG, m_ReplacementRoot);
else m_ReplacementConfig = CopyPlainConfig(captured, m_ReplacementRoot);
if (!Check(m_ReplacementConfig && ((reuseConfig && m_ReplacementConfig == captured) || (!reuseConfig && m_ReplacementConfig != captured)), "requested distinct/reused native configuration identity established")) return false;
m_ReadyConfig = null;
m_Map.OpenMap(m_ReplacementConfig);
if (!Check(ReplacementIsCurrent(), "actual replacement root is current native map")) return false;
SaveCounters();
return true;
}
protected bool ExerciseStaleCallbacks()
{
if (!Check(ReplacementIsCurrent() && m_ReadyConfig == m_ReplacementConfig && !m_Map.IsZooming(), "replacement remains open after native open-complete event")) return false;
vector pan = m_Map.GetCurrentPan();
float zoom = m_Map.GetTargetZoomPPU();
m_Capture.ERRM_StaleOwnershipCenter();
if (!Check(m_Map.GetCurrentPan() == pan && m_Map.GetTargetZoomPPU() == zoom && !m_Map.IsZooming(), "actual stale center callback cannot pan or retarget replacement zoom")) return false;
m_Capture.ERRM_StaleOwnershipCamera();
if (!Check(m_RestoreCalls == m_RestoreBaseline + 1 && m_RestoreAuthorized == m_AuthorizedBaseline, "stale camera-restore method executes but native-write authorization is denied")) return false;
m_PanBaseline = pan; m_ZoomBaseline = zoom;
m_Capture.ERRM_QueueStaleOwnershipCallbacks();
return true;
}
protected bool VerifyStaleAndStop()
{
if (!Check(m_CenterCalls == m_CenterBaseline + 2 && m_FinishCalls == m_FinishBaseline + 1, "both real delayed production callbacks ran exactly once")) return false;
if (!Check(ReplacementIsCurrent() && m_Map.GetCurrentPan() == m_PanBaseline && m_Map.GetTargetZoomPPU() == m_ZoomBaseline, "real delayed center and finish leave replacement view unchanged")) return false;
m_Capture.ERRM_StopOwnershipCapture();
if (!Check(ReplacementIsCurrent() && m_CloseEvents == m_CloseBaseline && m_Completions == m_CompletionBaseline && m_RestoreCalls == m_RestoreBaseline + 1, "actual finish, stop and vanilla mode-clear preserve replacement and do not request camera restore")) return false;
return true;
}
// Returns 0 while waiting, 1 only after cleanup, -1 on failure. now is native
// world time in milliseconds, matching existing Chat smoke probes.
int Tick(float now)
{
if (m_Done) return 1;
if (m_Stopped) return -1;
if (!Enabled()) { Stop(); return -1; }
if (m_Step == 0)
{
m_Map = SCR_MapEntity.GetMapInstance();
PlayerController controller = GetGame().GetPlayerController();
if (!Check(m_Map && !m_Map.IsOpen() && controller && controller.GetControlledEntity() && GetGame().GetCameraManager().CurrentCamera() == controller.GetPlayerCamera(), "fixture starts only with closed map and actual local character camera")) return -1;
m_Holder = controller.GetControlledEntity();
m_Deadline = now + 15000;
m_ReplacementRoot = GetGame().GetWorkspace().CreateWidgets(MAP_LAYOUT);
if (!Check(m_ReplacementRoot != null, "owned replacement native map layout created")) return -1;
m_ReplacementRoot.SetVisible(false);
RTTextureWidget target = RTTextureWidget.Cast(m_ReplacementRoot.FindAnyWidget("RTTexture0"));
if (target) target.SetEnabled(false);
SCR_MapEntity.GetOnMapClose().Insert(OnMapClosed);
SCR_MapEntity.GetOnMapOpenComplete().Insert(OnMapReady);
m_CaptureEntity = SpawnMap(m_Holder.GetOrigin() + Vector(0, 0.2, 0));
m_SpareEntity = SpawnMap(m_Holder.GetOrigin() + Vector(0.3, 0.2, 0));
if (!Check(m_CaptureEntity && m_SpareEntity && m_CaptureEntity != m_SpareEntity, "two separate real successor map prefabs spawned without inventory changes")) return -1;
m_WaitUntil = now + 1000; m_Step = 1; return 0;
}
if (now > m_Deadline) { Check(false, "bounded fixture deadline"); return -1; }
if (now < m_WaitUntil) return 0;
if (m_Step == 1)
{
if (!Check(m_CaptureEntity && m_SpareEntity, "both fixture entities survive until native component initialization")) return -1;
m_Capture = ERRM_SharedMapComponent.Cast(m_CaptureEntity.FindComponent(ERRM_SharedMapComponent));
m_Spare = ERRM_SharedMapComponent.Cast(m_SpareEntity.FindComponent(ERRM_SharedMapComponent));
if (!Check(m_Capture && m_Spare && m_Capture.ERRM_OwnershipReady() && m_Spare.ERRM_OwnershipReady(), "both native map components initialize actual render roots")) return -1;
m_Capture.ERRM_ObserveOwnership(this, 1); m_Spare.ERRM_ObserveOwnership(this, 2);
if (!BeginReplacement(false)) return -1;
m_WaitUntil = now + 1500; m_Step = 2; return 0;
}
if (m_Step == 2)
{
if (!ExerciseStaleCallbacks()) return -1;
m_WaitUntil = now + 750; m_Step = 3; return 0;
}
if (m_Step == 3)
{
if (!VerifyStaleAndStop()) return -1;
m_Map.CloseMap(); // exact replacement was checked immediately above
if (!BeginReplacement(true)) return -1;
m_WaitUntil = now + 1500; m_Step = 4; return 0;
}
if (m_Step == 4)
{
if (!ExerciseStaleCallbacks()) return -1;
m_WaitUntil = now + 750; m_Step = 5; return 0;
}
if (m_Step == 5)
{
if (!VerifyStaleAndStop()) return -1;
SaveCounters();
m_Spare.ERRM_StopOwnershipCapture();
RequestOwnedDeletion();
LogDeletionState("request returned");
m_DeleteDeadline = now + 3000;
m_WaitUntil = now + 100; m_Step = 6; return 0;
}
if (m_Step == 6)
{
if (!ReplacementIsCurrent() || m_CloseEvents != m_CloseBaseline || m_RestoreCalls != m_RestoreBaseline)
{
Check(false, "replacement remains unchanged while native deletion finishes"); return -1;
}
if (!DeletionComplete())
{
if (now >= m_DeleteDeadline)
{
LogDeletionState("bounded deletion timeout");
Check(false, "both actual entities/components delete and both superclass cleanup callbacks complete within three seconds"); return -1;
}
return 0;
}
LogDeletionState("completed on later tick");
if (!Check(DeletionComplete(), "actual prefab deletion runs successor and vanilla superclass cleanup")) return -1;
if (!Check(ReplacementIsCurrent() && m_CloseEvents == m_CloseBaseline && m_RestoreCalls == m_RestoreBaseline, "spare and interrupted map deletion preserve replacement without restore calls")) return -1;
m_WaitUntil = now + 1500; m_Step = 7; return 0;
}
if (!Check(ReplacementIsCurrent() && m_CloseEvents == m_CloseBaseline && m_RestoreCalls == m_RestoreBaseline && m_Completions == m_CompletionBaseline && m_CenterCalls == m_CenterBaseline && m_FinishCalls == m_FinishBaseline, "replacement survives delayed native callback window after both deletions")) return -1;
Stop(); m_Done = true;
Print("ERR SMOKE PASS: shared map ownership owned UI removed; camera render flag has no native getter and is not claimed as measured");
return 1;
}
void Stop()
{
if (m_Stopped) return;
m_Stopped = true;
if (m_Capture) m_Capture.ERRM_StopOwnershipCapture();
if (m_Spare) m_Spare.ERRM_StopOwnershipCapture();
if (ReplacementIsCurrent()) m_Map.CloseMap();
SCR_MapEntity.GetOnMapClose().Remove(OnMapClosed);
SCR_MapEntity.GetOnMapOpenComplete().Remove(OnMapReady);
RequestOwnedDeletion();
if (m_ReplacementRoot) m_ReplacementRoot.RemoveFromHierarchy();
m_ReplacementRoot = null; m_ReplacementConfig = null; m_ReadyConfig = null; m_Map = null;
m_Capture = null; m_Spare = null; m_Holder = null;
}
}
#endif