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.
This commit is contained in:
1 parent
69d3241a33
commit
51350bf476
3 files changed
+68
-11
No files matched your search
@@ -9,6 +9,9 @@
|
||||
"sources": [
|
||||
"src/**/*.cs"
|
||||
],
|
||||
"test_sources": [
|
||||
"tests/*.cs"
|
||||
],
|
||||
"ui": [
|
||||
{
|
||||
"id": "game_menu",
|
||||
|
||||
@@ -12,7 +12,8 @@ namespace Cthangover.Mods.Interface.Messages
|
||||
/// 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). Messages auto-fade and self-destruct
|
||||
/// (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
|
||||
@@ -85,6 +86,7 @@ namespace Cthangover.Mods.Interface.Messages
|
||||
/// <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>
|
||||
@@ -92,22 +94,23 @@ namespace Cthangover.Mods.Interface.Messages
|
||||
/// <param name="speed">Fade speed multiplier (default 1).</param>
|
||||
public static void AddMessage(float position, string text, Color color, float speed = 1f)
|
||||
{
|
||||
if (prefab == null)
|
||||
prefab = new ModScenePrefab("scenes/ui/message.tscn");
|
||||
|
||||
var instance = prefab.Instantiate<UiMessage>();
|
||||
var background = SceneContextNode.FindNode<Control>("Background");
|
||||
// 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)
|
||||
{
|
||||
background = SceneContextNode.Instance?.GetTree()?.GetFirstNodeInGroup("Background") as Control;
|
||||
if (background == null)
|
||||
{
|
||||
GameLogger.Log("UI", "Background node not found for messages", LogLevel.Error);
|
||||
// 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
|
||||
{
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
namespace Cthangover.Mods.Interface.Messages.Tests
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using Cthangover.Core.Mods.Testing;
|
||||
using Cthangover.Core.Scenes;
|
||||
using Cthangover.Core.UI.Messages;
|
||||
using Godot;
|
||||
|
||||
/// <summary>Battle/menu notifications must survive a missing shell without allocating orphan UI trees.</summary>
|
||||
public class MessagesHelperTests
|
||||
{
|
||||
[ModTest("messages/without_background_preserves_each_notification")]
|
||||
static void WithoutBackground(ModTestContext ctx)
|
||||
{
|
||||
var root = SceneContextNode.Instance.GetTree().Root;
|
||||
var background = root.FindChild("Background", true, false) as Control;
|
||||
var originalName = background?.Name ?? default;
|
||||
var grouped = background?.IsInGroup("Background") == true;
|
||||
var previous = new List<string>();
|
||||
while (NotificationBuffer.TryDequeue(out var pendingText)) previous.Add(pendingText);
|
||||
try
|
||||
{
|
||||
// The shared test scene has a background; model a battle without detaching or freeing borrowed UI.
|
||||
if (background != null)
|
||||
{
|
||||
background.Name = "MessagesContractBackground";
|
||||
if (grouped) background.RemoveFromGroup("Background");
|
||||
}
|
||||
ModAssert.True(root.FindChild("Background", true, false) == null, "fixture has no shell background");
|
||||
for (var i = 0; i < 40; i++) MessagesHelper.AddMessage("reward/" + i);
|
||||
ModAssert.Equal(40, NotificationBuffer.PendingCount, "every late reward is buffered exactly once");
|
||||
for (var i = 0; i < 40; i++)
|
||||
{
|
||||
ModAssert.True(NotificationBuffer.TryDequeue(out var actual), "notification is retained");
|
||||
ModAssert.Equal("reward/" + i, actual, "text and order survive until the shell returns");
|
||||
}
|
||||
ModAssert.Equal(0, NotificationBuffer.PendingCount, "no duplicate banners remain");
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (background != null)
|
||||
{
|
||||
background.Name = originalName;
|
||||
if (grouped) background.AddToGroup("Background");
|
||||
}
|
||||
NotificationBuffer.Clear();
|
||||
foreach (var previousText in previous) NotificationBuffer.Add(previousText);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user