329 lines
16 KiB
C
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
|