From 1d141be9bf63fcc5ccc8a71f07aba104a975db8b Mon Sep 17 00:00:00 2001
From: Trident35ro <107998245+Trident35ro@users.noreply.github.com>
Date: Tue, 11 Aug 2026 09:39:13 +0300
Subject: [PATCH] Add WindowManager GUI API for custom screens
Lets mods register AssetBundle-backed custom UI screens and show/hide/
destroy them under the game's main GUI canvas. Instantiated screens are
written into WindowPrefabsControl.prefab_screens_instantiated so the
game's own GetScreen/GetObject/DestroyScreen calls keep working on them,
mirroring how WorldPrefabManager injects custom world prefabs. Wired
into HAMHMod.Load() subsystem init.
---
.../Base/Base.GamePlugin.cs | 6 +
HAModHelper.GamePlugin/Gui/Gui.Events.cs | 15 ++
HAModHelper.GamePlugin/Gui/Gui.Interfaces.cs | 57 +++++++
HAModHelper.GamePlugin/Gui/Gui.Systems.cs | 148 ++++++++++++++++++
4 files changed, 226 insertions(+)
create mode 100644 HAModHelper.GamePlugin/Gui/Gui.Events.cs
create mode 100644 HAModHelper.GamePlugin/Gui/Gui.Interfaces.cs
create mode 100644 HAModHelper.GamePlugin/Gui/Gui.Systems.cs
diff --git a/HAModHelper.GamePlugin/Base/Base.GamePlugin.cs b/HAModHelper.GamePlugin/Base/Base.GamePlugin.cs
index a7e5cfa..e6224d1 100644
--- a/HAModHelper.GamePlugin/Base/Base.GamePlugin.cs
+++ b/HAModHelper.GamePlugin/Base/Base.GamePlugin.cs
@@ -1,6 +1,7 @@
using System.Diagnostics;
using HAModHelper.GamePlugin.Items.Systems;
using HAModHelper.GamePlugin.Perks.Systems;
+using HAModHelper.GamePlugin.Gui.Systems;
using HAModHelper.GamePlugin.Core.Debug;
using System.Net;
using BepInEx.Unity.IL2CPP;
@@ -82,6 +83,11 @@ public override void Load()
stopwatch4.Stop();
Log.LogInfo($"[HAMH] Initialized CraftingInjectionManager in {stopwatch4.ElapsedMilliseconds}ms.");
+ var stopwatch5 = Stopwatch.StartNew();
+ WindowManager.Instance.Initialize();
+ stopwatch5.Stop();
+ Log.LogInfo($"[HAMH] Initialized WindowManager in {stopwatch5.ElapsedMilliseconds}ms.");
+
//var stopwatch3 = Stopwatch.StartNew();
//UniverseLibConfig uvlconfig = new UniverseLibConfig
//{
diff --git a/HAModHelper.GamePlugin/Gui/Gui.Events.cs b/HAModHelper.GamePlugin/Gui/Gui.Events.cs
new file mode 100644
index 0000000..6a4b065
--- /dev/null
+++ b/HAModHelper.GamePlugin/Gui/Gui.Events.cs
@@ -0,0 +1,15 @@
+using HAModHelper.GamePlugin.Base.Events;
+
+namespace HAModHelper.GamePlugin.Gui.Events;
+
+public class CustomScreenOpenedEvent : BaseEvent
+{
+ public string ScreenId { get; }
+ public CustomScreenOpenedEvent(string screenId) => ScreenId = screenId;
+}
+
+public class CustomScreenClosedEvent : BaseEvent
+{
+ public string ScreenId { get; }
+ public CustomScreenClosedEvent(string screenId) => ScreenId = screenId;
+}
diff --git a/HAModHelper.GamePlugin/Gui/Gui.Interfaces.cs b/HAModHelper.GamePlugin/Gui/Gui.Interfaces.cs
new file mode 100644
index 0000000..1f353e4
--- /dev/null
+++ b/HAModHelper.GamePlugin/Gui/Gui.Interfaces.cs
@@ -0,0 +1,57 @@
+using UnityEngine;
+
+namespace HAModHelper.GamePlugin.Gui.Interfaces;
+
+public interface IWindowPrefabsControl
+{
+ Transform? GuiCanvasTransform { get; }
+
+ GameObject? GetScreen(string screenId);
+
+ GameObject? GetObject(string screenId, string objName);
+
+ bool HasScreen(string screenId);
+
+ /// Register an already-instantiated screen so the game's own GetScreen/GetObject calls find it too.
+ void RegisterInstantiated(string screenId, GameObject instance);
+
+ void DestroyScreen(string screenId);
+}
+
+// runtime adapter that wraps the real game WindowPrefabsControl/WindowControl
+public class UnityWindowPrefabsControl : IWindowPrefabsControl
+{
+ private readonly WindowPrefabsControl _wpc;
+ private readonly WindowControl _wc;
+
+ public UnityWindowPrefabsControl(WindowPrefabsControl wpc, WindowControl wc)
+ {
+ _wpc = wpc;
+ _wc = wc;
+ }
+
+ public Transform? GuiCanvasTransform => _wc.gui_canvas != null ? _wc.gui_canvas.transform : null;
+
+ public GameObject? GetScreen(string screenId) => _wpc.GetScreen(screenId);
+
+ public GameObject? GetObject(string screenId, string objName) => _wpc.GetObject(screenId, objName);
+
+ public bool HasScreen(string screenId) => _wpc.prefab_screens_instantiated.ContainsKey(screenId);
+
+ public void RegisterInstantiated(string screenId, GameObject instance)
+ {
+ _wpc.prefab_screens_instantiated[screenId] = instance;
+ }
+
+ public void DestroyScreen(string screenId) => _wpc.DestroyScreen(screenId);
+}
+
+public class DebugNoLoadWindowPrefabsControl : IWindowPrefabsControl
+{
+ public Transform? GuiCanvasTransform => null;
+ public GameObject? GetScreen(string screenId) => null;
+ public GameObject? GetObject(string screenId, string objName) => null;
+ public bool HasScreen(string screenId) => false;
+ public void RegisterInstantiated(string screenId, GameObject instance) { }
+ public void DestroyScreen(string screenId) { }
+}
diff --git a/HAModHelper.GamePlugin/Gui/Gui.Systems.cs b/HAModHelper.GamePlugin/Gui/Gui.Systems.cs
new file mode 100644
index 0000000..b6d4971
--- /dev/null
+++ b/HAModHelper.GamePlugin/Gui/Gui.Systems.cs
@@ -0,0 +1,148 @@
+using HAModHelper.GamePlugin.Core;
+using HAModHelper.GamePlugin.Base.Events;
+using HAModHelper.GamePlugin.Gui.Events;
+using HAModHelper.GamePlugin.Gui.Interfaces;
+using UnityEngine;
+using Object = UnityEngine.Object;
+
+namespace HAModHelper.GamePlugin.Gui.Systems;
+
+///
+/// Lets mods register their own AssetBundle-backed UI screens and show/hide them under
+/// the game's main GUI canvas. Registered screens are written into
+/// WindowPrefabsControl.prefab_screens_instantiated so the game's own
+/// GetScreen/GetObject/DestroyScreen calls keep working on them too —
+/// mirrors how injects custom world prefabs.
+///
+public sealed class WindowManager
+{
+ public static WindowManager Instance { get; } = new();
+
+ private sealed class ScreenEntry
+ {
+ public required AssetBundle Bundle;
+ public required string AssetName;
+ public GameObject? Cache;
+ }
+
+ private readonly Dictionary _screens = new();
+
+ // TEST-ONLY: Spoof a fake WindowPrefabsControl for tests
+ public IWindowPrefabsControl? DebugWindowPrefabsControlSource { get; set; }
+
+ private WindowManager() { }
+
+ private IWindowPrefabsControl? GetControl()
+ {
+ if (DebugWindowPrefabsControlSource?.GetType() == typeof(DebugNoLoadWindowPrefabsControl))
+ return null;
+
+ if (DebugWindowPrefabsControlSource != null)
+ return DebugWindowPrefabsControlSource;
+
+ try
+ {
+ var wpc = WindowPrefabsControl.Instance;
+ var wc = WindowControl.Instance;
+ if (wpc == null || wc == null) return null;
+ var proxy = new UnityWindowPrefabsControl(wpc, wc);
+ return proxy.GuiCanvasTransform == null ? null : proxy;
+ }
+ catch (Exception)
+ {
+ return null;
+ }
+ }
+
+ /// TEST-ONLY: Reset system state.
+ public void Reset()
+ {
+ _screens.Clear();
+ DebugWindowPrefabsControlSource = null;
+ }
+
+ /// Initialize the window manager (called on game start).
+ public void Initialize()
+ {
+ }
+
+ ///
+ /// Register a custom screen prefab.
+ ///
+ /// Unique id for the screen, used with etc.
+ /// AssetBundle that contains the prefab. Keep a static reference
+ /// in your plugin so the native backing is never freed.
+ /// Name of the prefab asset inside the bundle.
+ public void RegisterScreen(string screenId, AssetBundle bundle, string assetName)
+ {
+ _screens[screenId] = new ScreenEntry { Bundle = bundle, AssetName = assetName };
+ }
+
+ ///
+ /// Instantiate (first call) or re-show a registered custom screen, parented under the
+ /// game's main GUI canvas. Returns null if the screen isn't registered or the game's
+ /// window system isn't loaded yet.
+ ///
+ public GameObject? ShowScreen(string screenId)
+ {
+ var control = GetControl();
+ if (control == null)
+ {
+ try { HAMHMod.Logger.LogInfo($"[HAMH] WindowPrefabsControl not ready, can't show screen {screenId}"); } catch { }
+ return null;
+ }
+
+ var existing = control.GetScreen(screenId);
+ if (existing != null)
+ {
+ existing.SetActive(true);
+ EventBus.Instance.Fire(new CustomScreenOpenedEvent(screenId));
+ return existing;
+ }
+
+ if (!_screens.TryGetValue(screenId, out var entry))
+ {
+ try { HAMHMod.Logger.LogInfo($"[HAMH] No custom screen registered for {screenId}"); } catch { }
+ return null;
+ }
+
+ if (entry.Cache == null)
+ {
+ var asset = entry.Bundle.LoadAsset(entry.AssetName);
+ if (asset == null) return null;
+ entry.Cache = asset.Cast();
+ }
+
+ var instance = Object.Instantiate(entry.Cache, control.GuiCanvasTransform);
+ instance.SetActive(true);
+ control.RegisterInstantiated(screenId, instance);
+
+ EventBus.Instance.Fire(new CustomScreenOpenedEvent(screenId));
+ return instance;
+ }
+
+ /// Hide a shown custom (or base-game) screen without destroying it.
+ public bool HideScreen(string screenId)
+ {
+ var existing = GetControl()?.GetScreen(screenId);
+ if (existing == null) return false;
+
+ existing.SetActive(false);
+ EventBus.Instance.Fire(new CustomScreenClosedEvent(screenId));
+ return true;
+ }
+
+ /// Destroy a shown custom (or base-game) screen entirely.
+ public bool DestroyScreen(string screenId)
+ {
+ var control = GetControl();
+ if (control == null || !control.HasScreen(screenId)) return false;
+
+ control.DestroyScreen(screenId);
+ EventBus.Instance.Fire(new CustomScreenClosedEvent(screenId));
+ return true;
+ }
+
+ /// Look up a child object inside a screen (custom or base-game) by name.
+ public GameObject? GetObject(string screenId, string objName) => GetControl()?.GetObject(screenId, objName);
+}