diff --git a/HAModHelper.GamePlugin/Base/Base.GamePlugin.cs b/HAModHelper.GamePlugin/Base/Base.GamePlugin.cs index e999a2d..c9252dd 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.Dialogue.Systems; using HAModHelper.GamePlugin.Core.Debug; using System.Net; @@ -84,9 +85,14 @@ public override void Load() Log.LogInfo($"[HAMH] Initialized CraftingInjectionManager in {stopwatch4.ElapsedMilliseconds}ms."); var stopwatch5 = Stopwatch.StartNew(); - DialogueManager.Instance.Initialize(); + WindowManager.Instance.Initialize(); stopwatch5.Stop(); - Log.LogInfo($"[HAMH] Initialized DialogueManager in {stopwatch5.ElapsedMilliseconds}ms."); + Log.LogInfo($"[HAMH] Initialized WindowManager in {stopwatch5.ElapsedMilliseconds}ms."); + + var stopwatch6 = Stopwatch.StartNew(); + DialogueManager.Instance.Initialize(); + stopwatch6.Stop(); + Log.LogInfo($"[HAMH] Initialized DialogueManager in {stopwatch6.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); +}