Files
mod_ui_base/src/Messages/MessagesHelper.cs
T
cthangover 51350bf476 fix: retain notifications while the scene background is absent
Buffer reward messages until the shell returns without allocating detached UI nodes. Register mod tests that verify notification ordering and prevent duplicates.
2026-11-04 08:41:19 +01:00

125 lines
5.0 KiB
C#

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
{
/// <summary>
/// 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.
/// </summary>
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;
}
/// <summary>
/// Resets the cycling position index to 0. Call when UI state resets
/// to prevent messages from accumulating at high Y positions.
/// </summary>
public static void ClearLastIndex()
{
lastMessageIndex = 0;
}
/// <summary>
/// Shows a message in the default warning colour at the next vertical slot.
/// </summary>
/// <param name="text">The message text to display.</param>
/// <param name="speed">Fade speed multiplier (higher = faster fade, default 1).</param>
public static void AddMessage(string text, float speed = 1f)
{
AddMessage(text, UiTokens.Warning, speed);
}
/// <summary>
/// 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 <paramref name="speed"/>.
/// </summary>
/// <param name="text">The message text to display.</param>
/// <param name="color">The text color.</param>
/// <param name="speed">Fade speed multiplier (higher = faster fade, default 1).</param>
public static void AddMessage(string text, Color color, float speed = 1f)
{
var nextIndex = GetNextIndex(lastMessageIndex);
AddMessage(GetPositionByLastIndex(nextIndex), text, color, speed);
lastMessageIndex = nextIndex;
}
/// <summary>
/// Shows a message in the default warning colour at a specific vertical
/// position, bypassing the cycling slot system.
/// </summary>
/// <param name="position">Y-offset in pixels for this message.</param>
/// <param name="text">The message text to display.</param>
/// <param name="speed">Fade speed multiplier (default 1).</param>
public static void AddMessage(float position, string text, float speed = 1f)
{
AddMessage(position, text, UiTokens.Warning, speed);
}
/// <summary>
/// Instantiates a <see cref="UiMessage"/> from <c>message.tscn</c>, 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.
/// </summary>
/// <param name="position">Y-offset in pixels for this message.</param>
/// <param name="text">The message text to display.</param>
/// <param name="color">The text color.</param>
/// <param name="speed">Fade speed multiplier (default 1).</param>
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<UiMessage>();
if (instance != null)
{
background.AddChild(instance);
instance.Setup(position, text, speed, color);
}
ModEventBus.Publish(new NotificationRequestedEvent
{
Text = text,
Category = "message",
});
}
}
}