using Cthangover.Core.Events; using Cthangover.Core.Scenes; using Cthangover.Core.UI.Theming; using Cthangover.Core.Utils; using Godot; using Cthangover.Core.UI.Messages; namespace Cthangover.Mods.Interface.Messages { /// /// Static factory for floating UI messages (like "Quest updated" or item /// pickup notifications). Manages a cycling position index of 0-5 so /// consecutive messages stack vertically without overlapping. Each message /// is instantiated from message.tscn and parented to the Background node /// (found via scene tree search). Without that shell, only the text is buffered for its return; /// no detached native controls are allocated. Visible messages auto-fade and self-destruct /// via UiMessage's OnUpdate. /// public static class MessagesHelper { private const int maxMessageIndex = 5; private static int lastMessageIndex = 0; private static ModScenePrefab prefab; private static int GetNextIndex(int index) { if (index++ >= maxMessageIndex) index = 0; return index; } private static float GetPositionByLastIndex(int index) { return (index - 1) * 60; } /// /// Resets the cycling position index to 0. Call when UI state resets /// to prevent messages from accumulating at high Y positions. /// public static void ClearLastIndex() { lastMessageIndex = 0; } /// /// Shows a message in the default warning colour at the next vertical slot. /// /// The message text to display. /// Fade speed multiplier (higher = faster fade, default 1). public static void AddMessage(string text, float speed = 1f) { AddMessage(text, UiTokens.Warning, speed); } /// /// Shows a message with a custom color at the next vertical slot. /// Messages auto-fade and self-destruct after ~3 seconds of display plus /// a fade duration determined by . /// /// The message text to display. /// The text color. /// Fade speed multiplier (higher = faster fade, default 1). public static void AddMessage(string text, Color color, float speed = 1f) { var nextIndex = GetNextIndex(lastMessageIndex); AddMessage(GetPositionByLastIndex(nextIndex), text, color, speed); lastMessageIndex = nextIndex; } /// /// Shows a message in the default warning colour at a specific vertical /// position, bypassing the cycling slot system. /// /// Y-offset in pixels for this message. /// The message text to display. /// Fade speed multiplier (default 1). public static void AddMessage(float position, string text, float speed = 1f) { AddMessage(position, text, UiTokens.Warning, speed); } /// /// Instantiates a from message.tscn, parents /// it to the Background node, and configures it for display and auto-fade. /// A scene without a background queues the text until the shell returns. /// /// Y-offset in pixels for this message. /// The message text to display. /// The text color. /// Fade speed multiplier (default 1). public static void AddMessage(float position, string text, Color color, float speed = 1f) { // Battle and menu scenes may have no shell background. Resolve the owner before allocating native nodes. var tree = SceneContextNode.Instance?.GetTree(); var background = tree?.Root.FindChild("Background", true, false) as Control ?? tree?.GetFirstNodeInGroup("Background") as Control; if (background == null) { // Keep battle rewards until the shell returns, without creating a detached message tree. NotificationBuffer.Add(text); return; } prefab ??= new ModScenePrefab("scenes/ui/message.tscn"); var instance = prefab.Instantiate(); if (instance != null) { background.AddChild(instance); instance.Setup(position, text, speed, color); } ModEventBus.Publish(new NotificationRequestedEvent { Text = text, Category = "message", }); } } }