Fix inventory layout integration and qualify ordinary UI and radio guidance
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
// Successor-only compatibility for the installed stock inventory storage list.
|
||||
// Its stock HandlerAttached uses LayoutSlot on a root inserted under a grid.
|
||||
// Supply a real linear-layout parent; retain the complete stock handler unchanged.
|
||||
class ERR_InventoryGridPlacement
|
||||
{
|
||||
protected int m_Row;
|
||||
protected int m_Column;
|
||||
protected int m_RowSpan;
|
||||
protected int m_ColumnSpan;
|
||||
protected LayoutHorizontalAlign m_Horizontal;
|
||||
protected LayoutVerticalAlign m_Vertical;
|
||||
protected float m_Left, m_Top, m_Right, m_Bottom;
|
||||
bool Capture(Widget widget)
|
||||
{
|
||||
if (!widget || !GridLayoutWidget.Cast(widget.GetParent())) return false;
|
||||
m_Row = GridSlot.GetRow(widget); m_Column = GridSlot.GetColumn(widget);
|
||||
m_RowSpan = GridSlot.GetRowSpan(widget); m_ColumnSpan = GridSlot.GetColumnSpan(widget);
|
||||
m_Horizontal = AlignableSlot.GetHorizontalAlign(widget);
|
||||
m_Vertical = AlignableSlot.GetVerticalAlign(widget);
|
||||
AlignableSlot.GetPadding(widget, m_Left, m_Top, m_Right, m_Bottom);
|
||||
return true;
|
||||
}
|
||||
bool Apply(Widget widget)
|
||||
{
|
||||
if (!widget || !GridLayoutWidget.Cast(widget.GetParent())) return false;
|
||||
GridSlot.SetRow(widget, m_Row); GridSlot.SetColumn(widget, m_Column);
|
||||
GridSlot.SetRowSpan(widget, m_RowSpan); GridSlot.SetColumnSpan(widget, m_ColumnSpan);
|
||||
AlignableSlot.SetHorizontalAlign(widget, m_Horizontal);
|
||||
AlignableSlot.SetVerticalAlign(widget, m_Vertical);
|
||||
AlignableSlot.SetPadding(widget, m_Left, m_Top, m_Right, m_Bottom);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
modded class SCR_InventoryMenuUI
|
||||
{
|
||||
// Weak widget identities belong to this exact menu's native hierarchy.
|
||||
// The empty wrapper survives stock storage-list refresh, then dies with the menu.
|
||||
protected Widget m_ERRStorageLayoutRoot;
|
||||
protected GridLayoutWidget m_ERRStorageLayoutGrid;
|
||||
protected VerticalLayoutWidget m_ERRStorageLayoutHost;
|
||||
protected bool m_ERRStorageLayoutBound;
|
||||
|
||||
bool ERR_PrepareStorageLayout(Widget child)
|
||||
{
|
||||
if (!GetGame() || !GetGame().GetWorkspace() || !GetGame().GetMenuManager()) return false;
|
||||
ERR_GameMode mode = ERR_GameMode.Get();
|
||||
if (!mode || !mode.Active() || GetGame().GetWorld() != mode.GetWorld()) return false;
|
||||
if (SCR_InventoryMenuUI.GetInventoryMenu() != this) return false;
|
||||
Widget root = GetRootWidget();
|
||||
if (!root || !child || child.GetName() != "Frame0") return false;
|
||||
GridLayoutWidget grid = GridLayoutWidget.Cast(root.FindAnyWidget("StoragesListSlot"));
|
||||
if (!grid || child.GetParent() != grid) return false;
|
||||
if (m_ERRStorageLayoutBound && (m_ERRStorageLayoutRoot != root || m_ERRStorageLayoutGrid != grid || !m_ERRStorageLayoutHost)) return false;
|
||||
if (m_ERRStorageLayoutHost && (m_ERRStorageLayoutHost.GetParent() != grid || m_ERRStorageLayoutHost.GetChildren())) return false;
|
||||
// Never adopt a foreign widget that happens to use our private name.
|
||||
Widget named = grid.FindAnyWidget("ERR_InventoryStorageLayout");
|
||||
if (named && named != m_ERRStorageLayoutHost) return false;
|
||||
ref ERR_InventoryGridPlacement placement = new ERR_InventoryGridPlacement();
|
||||
if (!placement.Capture(child)) return false;
|
||||
bool created;
|
||||
ref ERR_InventoryGridPlacement previousHost = new ERR_InventoryGridPlacement();
|
||||
if (m_ERRStorageLayoutHost)
|
||||
{
|
||||
if (!previousHost.Capture(m_ERRStorageLayoutHost)) return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
Widget host = GetGame().GetWorkspace().CreateWidget(WidgetType.VerticalLayoutWidgetTypeID, WidgetFlags.VISIBLE, null, 0, grid);
|
||||
m_ERRStorageLayoutHost = VerticalLayoutWidget.Cast(host);
|
||||
if (!m_ERRStorageLayoutHost)
|
||||
{
|
||||
if (host) host.RemoveFromHierarchy();
|
||||
return false;
|
||||
}
|
||||
m_ERRStorageLayoutHost.SetName("ERR_InventoryStorageLayout");
|
||||
created = true;
|
||||
}
|
||||
if (!placement.Apply(m_ERRStorageLayoutHost))
|
||||
{
|
||||
if (created) { m_ERRStorageLayoutHost.RemoveFromHierarchy(); m_ERRStorageLayoutHost = null; }
|
||||
return false;
|
||||
}
|
||||
m_ERRStorageLayoutHost.AddChild(child);
|
||||
if (!child || !m_ERRStorageLayoutHost) return false;
|
||||
if (child.GetParent() != m_ERRStorageLayoutHost)
|
||||
{
|
||||
// Do not move a widget which an intervening callback moved elsewhere.
|
||||
if (child.GetParent() == grid) placement.Apply(child);
|
||||
if (created && !m_ERRStorageLayoutHost.GetChildren()) { m_ERRStorageLayoutHost.RemoveFromHierarchy(); m_ERRStorageLayoutHost = null; }
|
||||
else if (!created) previousHost.Apply(m_ERRStorageLayoutHost);
|
||||
return false;
|
||||
}
|
||||
// Outer grid placement retains padding/alignment; the sole inner child fills it.
|
||||
AlignableSlot.SetPadding(child, 0, 0, 0, 0);
|
||||
AlignableSlot.SetHorizontalAlign(child, LayoutHorizontalAlign.Stretch);
|
||||
AlignableSlot.SetVerticalAlign(child, LayoutVerticalAlign.Stretch);
|
||||
m_ERRStorageLayoutRoot = root; m_ERRStorageLayoutGrid = grid;
|
||||
m_ERRStorageLayoutBound = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
modded class SCR_InventoryStoragesListUI
|
||||
{
|
||||
override void HandlerAttached(Widget w)
|
||||
{
|
||||
// m_MenuHandler is assigned by the stock constructor before AddHandler.
|
||||
// All mismatches/failures retain stock behavior, including any original error.
|
||||
if (GetGame() && GetGame().GetMenuManager() && m_MenuHandler && m_MenuHandler == SCR_InventoryMenuUI.GetInventoryMenu())
|
||||
m_MenuHandler.ERR_PrepareStorageLayout(w);
|
||||
super.HandlerAttached(w);
|
||||
}
|
||||
}
|
||||
@@ -783,6 +783,7 @@ modded class ERR_GameMode
|
||||
if (!ERR_BaseCheck(RadioFeedback() == ERR_RadioFeedback.INACTIVE, "radio feedback remains inactive before tuning")) return;
|
||||
if (!ERR_BaseCheck(TuneRadio(m_ERRBasePlayer, m_ERRBaseRadio), "identified radio accepts transmission")) return;
|
||||
if (!ERR_BaseCheck(RadioFeedback() == ERR_RadioFeedback.WAITING && ERR_ObjectiveText.Description(this, false).Contains("Waiting for"), "radio feedback publishes waiting before first occupancy sample")) return;
|
||||
if (!ERR_BaseCheck(RadioFeedback() == ERR_RadioFeedback.WAITING && Objectives().Current() && Objectives().Current().GetTaskDescription().Contains("Waiting for a conscious defender") && !Objectives().Current().GetTaskDescription().Contains("interrupted"), "native radio task reports waiting before first occupancy sample")) return;
|
||||
m_ERRBaseStep = 31;
|
||||
m_ERRBaseWait = now + 5000;
|
||||
}
|
||||
|
||||
@@ -62,7 +62,11 @@ class ERR_ObjectiveText
|
||||
if (!progress) return RadioFeedbackMessage(mode.RadioFeedback());
|
||||
int seconds = mode.DefenceSecondsRemaining();
|
||||
if (!mode.DefenceOccupied())
|
||||
{
|
||||
if (mode.RadioFeedback() == ERR_RadioFeedback.WAITING)
|
||||
return string.Format("Waiting for a conscious defender near the radio. Keep someone within 60 metres for %1 seconds to finish the transmission. Leaving resets progress.", seconds);
|
||||
return string.Format("Transmission interrupted. Keep a conscious survivor within 60 metres of the radio for %1 seconds. Leaving resets progress.", seconds);
|
||||
}
|
||||
int displayed = Math.Ceil(seconds / 5.0) * 5;
|
||||
return string.Format("Transmission in progress: up to %1 seconds remaining. Keep a conscious survivor within 60 metres of the radio. Leaving resets progress.", displayed);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,19 @@
|
||||
#ifndef WORKBENCH
|
||||
// Opt-in observer calls this read-only snapshot only after exact menu ownership checks.
|
||||
modded class SCR_InventoryMenuUI
|
||||
{
|
||||
string ERR_InputObserverInventoryState()
|
||||
{
|
||||
int hitZones = -1;
|
||||
if (m_aHitZonePoints) hitZones = m_aHitZonePoints.Count();
|
||||
Widget root = GetRootWidget();
|
||||
Widget overlay;
|
||||
if (root) overlay = root.FindAnyWidget("LoadingOverlay");
|
||||
bool overlayPresent = overlay != null;
|
||||
bool overlayVisible = overlay && overlay.IsVisibleInHierarchy();
|
||||
return string.Format("pending=%1 stage=%2 hitZones=%3 overlay=%4 overlayVisible=%5", m_bProcessInitQueue, m_eInitStage, hitZones, overlayPresent, overlayVisible);
|
||||
}
|
||||
}
|
||||
// Explicit ordinary-game observation only. No input, actor, menu or camera writes.
|
||||
class ERR_OrdinaryInputObserverRules
|
||||
{
|
||||
@@ -37,6 +52,16 @@ class ERR_OrdinaryInputObserver : Managed
|
||||
protected bool m_Opened;
|
||||
protected bool m_Closed;
|
||||
protected SCR_InventoryMenuUI m_Menu;
|
||||
protected string m_CaptureSuffix;
|
||||
protected SCR_InventoryMenuUI m_CaptureMenu;
|
||||
protected Widget m_CaptureRoot;
|
||||
protected int m_CaptureDue;
|
||||
protected int m_CaptureExpires;
|
||||
protected int m_CaptureLoadedAt = -1;
|
||||
protected int m_InventoryStateLines;
|
||||
protected bool m_BaselineCaptureQueued;
|
||||
protected bool m_OpenCaptureQueued;
|
||||
protected bool m_ClosedCaptureQueued;
|
||||
protected int m_Start;
|
||||
protected int m_Previous;
|
||||
protected int m_Samples;
|
||||
@@ -90,9 +115,98 @@ class ERR_OrdinaryInputObserver : Managed
|
||||
{
|
||||
PrintFormat("ERR INPUT OBSERVE: run=%1 sample=%2 real=%3 world=%4 %5", m_Nonce, m_Samples, System.GetTickCount(), m_LastWorldTime, detail);
|
||||
}
|
||||
protected void CancelCapture()
|
||||
{
|
||||
if (m_CaptureSuffix != "" && GetGame() && GetGame().GetCallqueue()) GetGame().GetCallqueue().Remove(CaptureLater);
|
||||
m_CaptureSuffix = ""; m_CaptureMenu = null; m_CaptureRoot = null; m_CaptureDue = 0; m_CaptureExpires = 0; m_CaptureLoadedAt = -1;
|
||||
}
|
||||
protected void Capture(string suffix)
|
||||
{
|
||||
if (!m_Screenshots || !Valid()) return;
|
||||
if (!m_Screenshots || m_Finished || !Valid() || !GetGame().GetCallqueue() || !GetGame().GetMenuManager()) return;
|
||||
if (suffix == "baseline") { if (m_BaselineCaptureQueued) return; m_BaselineCaptureQueued = true; }
|
||||
else if (suffix == "open") { if (m_OpenCaptureQueued) return; m_OpenCaptureQueued = true; }
|
||||
else if (suffix == "closed") { if (m_ClosedCaptureQueued) return; m_ClosedCaptureQueued = true; }
|
||||
else return;
|
||||
// One owned pending capture. A later transition supersedes its old image;
|
||||
// never label a later UI state as the earlier transition.
|
||||
CancelCapture();
|
||||
SCR_InventoryMenuUI menu = SCR_InventoryMenuUI.GetInventoryMenu();
|
||||
if (suffix == "open")
|
||||
{
|
||||
if (!menu || menu.GetCharacterController() != m_Character || GetGame().GetMenuManager().GetTopMenu() != menu) return;
|
||||
Widget root = menu.GetRootWidget();
|
||||
if (!root || !root.IsVisibleInHierarchy()) return;
|
||||
m_CaptureMenu = menu; m_CaptureRoot = root;
|
||||
}
|
||||
else if (menu || GetGame().GetMenuManager().GetTopMenu()) return;
|
||||
m_CaptureSuffix = suffix;
|
||||
int captureStart = System.GetTickCount();
|
||||
m_CaptureDue = captureStart + 300;
|
||||
m_CaptureExpires = captureStart + 3000;
|
||||
GetGame().GetCallqueue().CallLater(CaptureLater, 300, false);
|
||||
}
|
||||
protected void CaptureLater()
|
||||
{
|
||||
string suffix = m_CaptureSuffix;
|
||||
SCR_InventoryMenuUI expectedMenu = m_CaptureMenu;
|
||||
Widget expectedRoot = m_CaptureRoot;
|
||||
int due = m_CaptureDue;
|
||||
int expires = m_CaptureExpires;
|
||||
int loadedAt = m_CaptureLoadedAt;
|
||||
CancelCapture();
|
||||
if (suffix == "" || !m_Screenshots || m_Finished || !Valid() || !GetGame().GetMenuManager()) return;
|
||||
int now = System.GetTickCount();
|
||||
if (now < due || !ERR_OrdinaryInputObserverRules.Within(m_Start, m_Previous, now, m_Samples)) return;
|
||||
float worldTime = m_World.GetWorldTime();
|
||||
if (worldTime < m_LastWorldTime) return;
|
||||
SCR_InventoryMenuUI menu = SCR_InventoryMenuUI.GetInventoryMenu();
|
||||
if (suffix == "open")
|
||||
{
|
||||
if (!m_Opened || m_Closed || !expectedMenu || !expectedRoot || menu != expectedMenu || menu != m_Menu) return;
|
||||
if (menu.GetCharacterController() != m_Character || menu.GetRootWidget() != expectedRoot || !expectedRoot.IsVisibleInHierarchy()) return;
|
||||
if (GetGame().GetMenuManager().GetTopMenu() != menu) return;
|
||||
// Separate prefix preserves the input-observer parser contract. At most
|
||||
// thirty read-only state lines for this already opt-in owned capture.
|
||||
if (m_InventoryStateLines < 30)
|
||||
{
|
||||
string stateReason = "retry";
|
||||
if (now + 100 >= expires) stateReason = "timeout";
|
||||
PrintFormat("ERR INVENTORY OBSERVE: run=%1 real=%2 reason=%3 %4", m_Nonce, now, stateReason, menu.ERR_InputObserverInventoryState());
|
||||
m_InventoryStateLines++;
|
||||
}
|
||||
if (now >= expires) return;
|
||||
// Stock OnQueueProcessed removes this widget only after inventory init.
|
||||
// Retain one pending request, never restart its original three-second bound.
|
||||
bool loading = expectedRoot.FindAnyWidget("LoadingOverlay") != null;
|
||||
if (loading) loadedAt = -1;
|
||||
else if (loadedAt < 0) loadedAt = now;
|
||||
// Require a later revalidated observation after native overlay removal,
|
||||
// avoiding a screenshot request in the same update that removed it.
|
||||
if (loading || now - loadedAt < 100)
|
||||
{
|
||||
if (now + 100 >= expires || !GetGame().GetCallqueue()) return;
|
||||
m_CaptureSuffix = suffix; m_CaptureMenu = expectedMenu; m_CaptureRoot = expectedRoot;
|
||||
m_CaptureDue = now + 100; m_CaptureExpires = expires; m_CaptureLoadedAt = loadedAt;
|
||||
GetGame().GetCallqueue().CallLater(CaptureLater, 100, false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (now >= expires) return;
|
||||
if (menu || GetGame().GetMenuManager().GetTopMenu()) return;
|
||||
if (suffix == "baseline")
|
||||
{
|
||||
if (!m_Ready || m_Opened || m_Closed || !GetGame().GetInputManager()) return;
|
||||
float forward = GetGame().GetInputManager().GetActionValue("CharacterForward");
|
||||
vector input = m_Character.GetMovementInput();
|
||||
vector velocity = m_Character.GetVelocity();
|
||||
float displacement = vector.Distance(m_Actor.GetOrigin(), m_BaselineOrigin);
|
||||
if (!ERR_OrdinaryInputObserverRules.Stationary(forward, input, velocity, displacement)) return;
|
||||
}
|
||||
if (suffix == "closed" && !m_Closed) return;
|
||||
}
|
||||
m_LastWorldTime = worldTime;
|
||||
System.MakeScreenshot("$logs:err-input-" + m_Nonce + "-" + suffix);
|
||||
Record("CAPTURE_REQUEST " + suffix);
|
||||
}
|
||||
@@ -145,6 +259,7 @@ class ERR_OrdinaryInputObserver : Managed
|
||||
vector input = m_Character.GetMovementInput();
|
||||
vector velocity = m_Character.GetVelocity();
|
||||
float forward = GetGame().GetInputManager().GetActionValue("CharacterForward");
|
||||
if (m_CaptureSuffix == "baseline" && !ERR_OrdinaryInputObserverRules.Stationary(forward, input, velocity, vector.Distance(origin, m_BaselineOrigin))) CancelCapture();
|
||||
m_Samples++;
|
||||
PrintFormat("ERR INPUT OBSERVE: run=%1 sample=%2 real=%3 world=%4 SAMPLE origin=%5 forward=%6 input=%7 velocity=%8 inventory=%9", m_Nonce, m_Samples, now, m_LastWorldTime, origin, forward, input, velocity, open);
|
||||
Observe(now, origin, forward, input, velocity, open);
|
||||
@@ -157,6 +272,7 @@ class ERR_OrdinaryInputObserver : Managed
|
||||
{
|
||||
if (m_Finished) return;
|
||||
m_Finished = true;
|
||||
CancelCapture();
|
||||
Record(reason);
|
||||
if (GetGame() && GetGame().GetCallqueue()) GetGame().GetCallqueue().Remove(Sample);
|
||||
if (m_Mode) m_Mode.OnStop().Remove(Cancel);
|
||||
|
||||
Reference in New Issue
Block a user