diff --git a/.gitignore b/.gitignore
index c301ca8..bb88804 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,37 +1,37 @@
-# Godot 4.x
-.godot/
-.idea/
-sdk/
-bin/
-obj/
-
-#ai
-.omo/
-.opencode/
-
-gen/
-obj/
-bin/
-
-*.tres~
-*.tscn~
-
-# GDScript LSP
-.godot/
-
-# Export
-*.exe
-*.dmg
-*.apk
-*.aab
-*.pck
-*.zip
-*.log
-
-# OS
-.DS_Store
-Thumbs.db
-
-# Editor
-*.import
-*.godot.uid
+# Godot 4.x
+.godot/
+.idea/
+sdk/
+bin/
+obj/
+
+#ai
+.omo/
+.opencode/
+
+gen/
+obj/
+bin/
+
+*.tres~
+*.tscn~
+
+# GDScript LSP
+.godot/
+
+# Export
+*.exe
+*.dmg
+*.apk
+*.aab
+*.pck
+*.zip
+*.log
+
+# OS
+.DS_Store
+Thumbs.db
+
+# Editor
+*.import
+*.godot.uid
diff --git a/manifest.json b/manifest.json
index f361c92..6341570 100644
--- a/manifest.json
+++ b/manifest.json
@@ -8,6 +8,9 @@
"sources": [
"src/**/*.cs"
],
+ "test_sources": [
+ "tests/*.cs"
+ ],
"depends": ["shell2d_base>=1", "interactive_base>=1", "effects_base>=1", "time_base>=1", "map_base>=1", "ui_base>=1"],
"ui": [],
"test_suites": [
diff --git a/src/DialogScriptRunner.cs b/src/DialogScriptRunner.cs
index bb03586..cd984ab 100644
--- a/src/DialogScriptRunner.cs
+++ b/src/DialogScriptRunner.cs
@@ -48,8 +48,7 @@ namespace Cthangover.Mods.Dialog
if (dlg == null || dlg.Queue.Count == 0)
return false;
- dialogBox.SetDialogQueueAndRun(dlg, null, 0, null);
- return true;
+ return dialogBox.SetDialogQueueAndRun(dlg, null, 0, null);
}
/// Runs inline DSL commands (hover effects and similar) through a standalone dialog runtime.
diff --git a/src/Factories/ScenarioCommandStrategyFactory.cs b/src/Factories/ScenarioCommandStrategyFactory.cs
index 118157e..bb724f2 100644
--- a/src/Factories/ScenarioCommandStrategyFactory.cs
+++ b/src/Factories/ScenarioCommandStrategyFactory.cs
@@ -1,78 +1,78 @@
-using System.Collections.Generic;
-using Cthangover.Mods.Dialog.Scenarios;
-using Cthangover.Core.Utils;
-using Cthangover.Core.UI;
-using Cthangover.Mods.Interface.Base.Lists;
-using Cthangover.Core.UI.Dialog;
-using Cthangover.Core.UI.Event;
-using Cthangover.Core.UI.Executable;
-using Cthangover.Core.Scenarios;
-using Cthangover.Core.Scenes;
-using Cthangover.Core.Interactive;
-
-namespace Cthangover.Mods.Dialog.Scenarios
-{
- ///
- /// Registry of implementations,
- /// indexed by their name.
- /// It discovers nothing itself:
- /// delivers the strategies of every mod as the kernel registers its
- /// assembly and takes them back when the mod unloads, so the table never
- /// holds a strategy of a dead assembly (which would pin it and keep running
- /// its old code after a hot reload).
- ///
- public static class ScenarioCommandStrategyFactory
- {
- private static readonly Dictionary Strategies = new();
-
- ///
- /// Looks up the registered for .
- /// Returns null if no strategy exists for the given command name.
- ///
- public static IScenarioCommandStrategy Get(string command)
- {
- Strategies.TryGetValue(command, out var strategy);
- return strategy;
- }
-
- ///
- /// Enumerates all registered DSL command names (lowercase).
- ///
- public static IEnumerable GetAllCommandNames()
- {
- return Strategies.Keys;
- }
-
- ///
- /// Returns the for a command if its
- /// strategy implements that interface, or null otherwise. Used by the
- /// build pipeline to collect asset dependencies from scenario scripts.
- ///
- public static ICommandReferenceMetadata GetReferenceMetadata(string command)
- {
- if (Strategies.TryGetValue(command, out var strategy) && strategy is ICommandReferenceMetadata meta)
- return meta;
- return null;
- }
-
- ///
- /// Registers a strategy under its command name. The first strategy of a
- /// command wins: a mod cannot silently take over a built-in command.
- ///
- public static void Register(IScenarioCommandStrategy strategy)
- {
- if (strategy != null && !Strategies.ContainsKey(strategy.Command))
- Strategies[strategy.Command] = strategy;
- }
-
- ///
- /// Removes (its mod is being unloaded). A
- /// command owned by another strategy instance is left alone.
- ///
- public static void Unregister(IScenarioCommandStrategy strategy)
- {
- if (strategy != null && Strategies.TryGetValue(strategy.Command, out var registered) && registered == strategy)
- Strategies.Remove(strategy.Command);
- }
- }
-}
+using System.Collections.Generic;
+using Cthangover.Mods.Dialog.Scenarios;
+using Cthangover.Core.Utils;
+using Cthangover.Core.UI;
+using Cthangover.Mods.Interface.Base.Lists;
+using Cthangover.Core.UI.Dialog;
+using Cthangover.Core.UI.Event;
+using Cthangover.Core.UI.Executable;
+using Cthangover.Core.Scenarios;
+using Cthangover.Core.Scenes;
+using Cthangover.Core.Interactive;
+
+namespace Cthangover.Mods.Dialog.Scenarios
+{
+ ///
+ /// Registry of implementations,
+ /// indexed by their name.
+ /// It discovers nothing itself:
+ /// delivers the strategies of every mod as the kernel registers its
+ /// assembly and takes them back when the mod unloads, so the table never
+ /// holds a strategy of a dead assembly (which would pin it and keep running
+ /// its old code after a hot reload).
+ ///
+ public static class ScenarioCommandStrategyFactory
+ {
+ private static readonly Dictionary Strategies = new();
+
+ ///
+ /// Looks up the registered for .
+ /// Returns null if no strategy exists for the given command name.
+ ///
+ public static IScenarioCommandStrategy Get(string command)
+ {
+ Strategies.TryGetValue(command, out var strategy);
+ return strategy;
+ }
+
+ ///
+ /// Enumerates all registered DSL command names (lowercase).
+ ///
+ public static IEnumerable GetAllCommandNames()
+ {
+ return Strategies.Keys;
+ }
+
+ ///
+ /// Returns the for a command if its
+ /// strategy implements that interface, or null otherwise. Used by the
+ /// build pipeline to collect asset dependencies from scenario scripts.
+ ///
+ public static ICommandReferenceMetadata GetReferenceMetadata(string command)
+ {
+ if (Strategies.TryGetValue(command, out var strategy) && strategy is ICommandReferenceMetadata meta)
+ return meta;
+ return null;
+ }
+
+ ///
+ /// Registers a strategy under its command name. The first strategy of a
+ /// command wins: a mod cannot silently take over a built-in command.
+ ///
+ public static void Register(IScenarioCommandStrategy strategy)
+ {
+ if (strategy != null && !Strategies.ContainsKey(strategy.Command))
+ Strategies[strategy.Command] = strategy;
+ }
+
+ ///
+ /// Removes (its mod is being unloaded). A
+ /// command owned by another strategy instance is left alone.
+ ///
+ public static void Unregister(IScenarioCommandStrategy strategy)
+ {
+ if (strategy != null && Strategies.TryGetValue(strategy.Command, out var registered) && registered == strategy)
+ Strategies.Remove(strategy.Command);
+ }
+ }
+}
diff --git a/src/Scenarios/ICommandReferenceMetadata.cs b/src/Scenarios/ICommandReferenceMetadata.cs
index 774db35..661849d 100644
--- a/src/Scenarios/ICommandReferenceMetadata.cs
+++ b/src/Scenarios/ICommandReferenceMetadata.cs
@@ -1,40 +1,40 @@
-namespace Cthangover.Mods.Dialog.Scenarios
-{
- ///
- /// Classifies the type of resource that the first positional argument of a
- /// scenario command refers to. Used for dependency tracking — the build tool
- /// can scan commands by their to
- /// discover which assets (backgrounds, scenes, music, etc.) are referenced
- /// and need to be packed into the final build.
- ///
- public enum PositionalReferenceKind
- {
- /// No resource reference in the first positional argument.
- None,
- /// A background image resource path.
- Background,
- /// A scene resource path (for scene switching).
- Scene,
- /// A music/audio resource path.
- Music,
- /// A sound effect resource path.
- Sound,
- /// An effect/shader resource path.
- Effect,
- /// An interactive action definition ID.
- Action
- }
-
- ///
- /// Optional interface for command strategies whose first positional argument
- /// references an external resource. Strategies implementing this interface
- /// are inspected by the build pipeline to collect asset dependencies.
- ///
- public interface ICommandReferenceMetadata
- {
- ///
- /// The kind of resource referenced by the first positional argument of this command.
- ///
- PositionalReferenceKind Positional0Kind { get; }
- }
-}
+namespace Cthangover.Mods.Dialog.Scenarios
+{
+ ///
+ /// Classifies the type of resource that the first positional argument of a
+ /// scenario command refers to. Used for dependency tracking — the build tool
+ /// can scan commands by their to
+ /// discover which assets (backgrounds, scenes, music, etc.) are referenced
+ /// and need to be packed into the final build.
+ ///
+ public enum PositionalReferenceKind
+ {
+ /// No resource reference in the first positional argument.
+ None,
+ /// A background image resource path.
+ Background,
+ /// A scene resource path (for scene switching).
+ Scene,
+ /// A music/audio resource path.
+ Music,
+ /// A sound effect resource path.
+ Sound,
+ /// An effect/shader resource path.
+ Effect,
+ /// An interactive action definition ID.
+ Action
+ }
+
+ ///
+ /// Optional interface for command strategies whose first positional argument
+ /// references an external resource. Strategies implementing this interface
+ /// are inspected by the build pipeline to collect asset dependencies.
+ ///
+ public interface ICommandReferenceMetadata
+ {
+ ///
+ /// The kind of resource referenced by the first positional argument of this command.
+ ///
+ PositionalReferenceKind Positional0Kind { get; }
+ }
+}
diff --git a/src/Scenarios/IScenarioCommandStrategy.cs b/src/Scenarios/IScenarioCommandStrategy.cs
index 9c1a782..79e1a01 100644
--- a/src/Scenarios/IScenarioCommandStrategy.cs
+++ b/src/Scenarios/IScenarioCommandStrategy.cs
@@ -1,6 +1,6 @@
-using System.Collections.Generic;
-using Cthangover.Core.Localization;
-using Cthangover.Mods.Dialog;
+using System.Collections.Generic;
+using Cthangover.Core.Localization;
+using Cthangover.Mods.Dialog;
using Cthangover.Core.UI;
using Cthangover.Mods.Interface.Base.Lists;
using Cthangover.Core.UI.Dialog;
@@ -9,31 +9,31 @@ using Cthangover.Core.UI.Executable;
using Cthangover.Core.Scenarios;
using Cthangover.Core.Scenes;
using Cthangover.Core.Interactive;
-
-namespace Cthangover.Mods.Dialog.Scenarios
-{
- ///
- /// Strategy interface for handling a single DSL command type in scenario scripts.
- /// Each command word (e.g. text, music, goto) maps to one
- /// strategy implementation. The
- /// auto-discovers all implementations via reflection and dispatches commands
- /// to the matching strategy during parsing.
- ///
- public interface IScenarioCommandStrategy
- {
- ///
- /// The lowercase DSL command word this strategy handles (e.g. "text", "music").
- ///
- string Command { get; }
-
- ///
- /// Executes the command by appending one or more dialog actions to .
- ///
- /// The dialog queue to push actions onto.
- /// Unnamed tokens following the command word in order.
- /// Key-value pairs parsed from name=value tokens (keys are lowercased).
- /// The target label following a -> arrow token, if present.
- /// Optional localization provider for resolving key= lookups.
- void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale);
- }
-}
+
+namespace Cthangover.Mods.Dialog.Scenarios
+{
+ ///
+ /// Strategy interface for handling a single DSL command type in scenario scripts.
+ /// Each command word (e.g. text, music, goto) maps to one
+ /// strategy implementation. The
+ /// auto-discovers all implementations via reflection and dispatches commands
+ /// to the matching strategy during parsing.
+ ///
+ public interface IScenarioCommandStrategy
+ {
+ ///
+ /// The lowercase DSL command word this strategy handles (e.g. "text", "music").
+ ///
+ string Command { get; }
+
+ ///
+ /// Executes the command by appending one or more dialog actions to .
+ ///
+ /// The dialog queue to push actions onto.
+ /// Unnamed tokens following the command word in order.
+ /// Key-value pairs parsed from name=value tokens (keys are lowercased).
+ /// The target label following a -> arrow token, if present.
+ /// Optional localization provider for resolving key= lookups.
+ void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale);
+ }
+}
diff --git a/src/Scenarios/Impls/ActionCommandStrategy.cs b/src/Scenarios/Impls/ActionCommandStrategy.cs
index 9a963cc..e78ecc3 100644
--- a/src/Scenarios/Impls/ActionCommandStrategy.cs
+++ b/src/Scenarios/Impls/ActionCommandStrategy.cs
@@ -1,6 +1,6 @@
-using System.Collections.Generic;
-using Cthangover.Core.Localization;
-using Cthangover.Mods.Dialog;
+using System.Collections.Generic;
+using Cthangover.Core.Localization;
+using Cthangover.Mods.Dialog;
using Cthangover.Core.UI;
using Cthangover.Mods.Interface.Base.Lists;
using Cthangover.Core.UI.Dialog;
@@ -9,39 +9,39 @@ using Cthangover.Core.UI.Executable;
using Cthangover.Core.Scenarios;
using Cthangover.Core.Scenes;
using Cthangover.Core.Interactive;
-
-namespace Cthangover.Mods.Dialog.Scenarios
-{
- ///
- /// Handles the action DSL command. Triggers a named interactive action
- /// (defined in the action system) and forwards any extra named parameters as
- /// context variables via .
- /// Implements to signal that the
- /// first positional argument is an reference.
- ///
- public class ActionCommandStrategy : IScenarioCommandStrategy, ICommandReferenceMetadata, IScenarioCommandSpec
- {
- ///
- public string Command => "action";
- ///
- public PositionalReferenceKind Positional0Kind => PositionalReferenceKind.Action;
- ///
- public int MinPositional => 1;
- ///
- public int MaxPositional => 1;
-
- ///
- public void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale)
- {
- var actionName = positional.Count > 0 ? positional[0] : "";
-
- foreach (var kv in named)
- {
- if (kv.Key is "name" or "key") continue;
- dlg.Set(kv.Key, kv.Value);
- }
-
- dlg.Action(actionName);
- }
- }
-}
+
+namespace Cthangover.Mods.Dialog.Scenarios
+{
+ ///
+ /// Handles the action DSL command. Triggers a named interactive action
+ /// (defined in the action system) and forwards any extra named parameters as
+ /// context variables via .
+ /// Implements to signal that the
+ /// first positional argument is an reference.
+ ///
+ public class ActionCommandStrategy : IScenarioCommandStrategy, ICommandReferenceMetadata, IScenarioCommandSpec
+ {
+ ///
+ public string Command => "action";
+ ///
+ public PositionalReferenceKind Positional0Kind => PositionalReferenceKind.Action;
+ ///
+ public int MinPositional => 1;
+ ///
+ public int MaxPositional => 1;
+
+ ///
+ public void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale)
+ {
+ var actionName = positional.Count > 0 ? positional[0] : "";
+
+ foreach (var kv in named)
+ {
+ if (kv.Key is "name" or "key") continue;
+ dlg.Set(kv.Key, kv.Value);
+ }
+
+ dlg.Action(actionName);
+ }
+ }
+}
diff --git a/src/Scenarios/Impls/AnimationCommandStrategy.cs b/src/Scenarios/Impls/AnimationCommandStrategy.cs
index 0c3b82b..64065f0 100644
--- a/src/Scenarios/Impls/AnimationCommandStrategy.cs
+++ b/src/Scenarios/Impls/AnimationCommandStrategy.cs
@@ -1,7 +1,7 @@
-using System.Collections.Generic;
-using System.Globalization;
-using Cthangover.Core.Localization;
-using Cthangover.Mods.Dialog;
+using System.Collections.Generic;
+using System.Globalization;
+using Cthangover.Core.Localization;
+using Cthangover.Mods.Dialog;
using Cthangover.Core.UI;
using Cthangover.Mods.Interface.Base.Lists;
using Cthangover.Core.UI.Dialog;
@@ -10,55 +10,55 @@ using Cthangover.Core.UI.Executable;
using Cthangover.Core.Scenarios;
using Cthangover.Core.Scenes;
using Cthangover.Core.Interactive;
-
-namespace Cthangover.Mods.Dialog.Scenarios
-{
- ///
- /// Handles the animation DSL command. Supports two modes:
- /// (a) multiple sprites via sprites= named parameter, or
- /// (b) a single sprite base with start, count, speed, and loop controls.
- /// Delegates to .
- ///
- public class AnimationCommandStrategy : IScenarioCommandStrategy, IScenarioCommandSpec
- {
- ///
- public string Command => "animation";
- ///
- public int MinArguments => 1;
- ///
- public int MaxPositional => 1;
- ///
- public IReadOnlyList AllowedNamed => new[] { "sprites", "speed", "loop", "start", "count" };
-
- ///
- public void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale)
- {
- if (named.TryGetValue("sprites", out var spritesStr))
- {
- var sprites = new List(spritesStr.Split(','));
- var speed = named.TryGetValue("speed", out var sp) ? ParseFloat(sp) : 1f;
- var loop = !named.TryGetValue("loop", out var lp) || lp == "true";
- dlg.Animation(sprites, speed, 1f, loop);
- }
- else if (positional.Count > 0)
- {
- var sprite = positional[0];
- var start = named.TryGetValue("start", out var st) ? ParseInt(st) : 0;
- var count = named.TryGetValue("count", out var cn) ? ParseInt(cn) : 1;
- var speed = named.TryGetValue("speed", out var sp) ? ParseFloat(sp) : 1f;
- var loop = !named.TryGetValue("loop", out var lp) || lp == "true";
- dlg.Animation(sprite, start, count, speed, 1f, loop);
- }
- }
-
- private static float ParseFloat(string s)
- {
- return float.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out var v) ? v : 0f;
- }
-
- private static int ParseInt(string s)
- {
- return int.TryParse(s, NumberStyles.Integer, CultureInfo.InvariantCulture, out var v) ? v : 0;
- }
- }
-}
+
+namespace Cthangover.Mods.Dialog.Scenarios
+{
+ ///
+ /// Handles the animation DSL command. Supports two modes:
+ /// (a) multiple sprites via sprites= named parameter, or
+ /// (b) a single sprite base with start, count, speed, and loop controls.
+ /// Delegates to .
+ ///
+ public class AnimationCommandStrategy : IScenarioCommandStrategy, IScenarioCommandSpec
+ {
+ ///
+ public string Command => "animation";
+ ///
+ public int MinArguments => 1;
+ ///
+ public int MaxPositional => 1;
+ ///
+ public IReadOnlyList AllowedNamed => new[] { "sprites", "speed", "loop", "start", "count" };
+
+ ///
+ public void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale)
+ {
+ if (named.TryGetValue("sprites", out var spritesStr))
+ {
+ var sprites = new List(spritesStr.Split(','));
+ var speed = named.TryGetValue("speed", out var sp) ? ParseFloat(sp) : 1f;
+ var loop = !named.TryGetValue("loop", out var lp) || lp == "true";
+ dlg.Animation(sprites, speed, 1f, loop);
+ }
+ else if (positional.Count > 0)
+ {
+ var sprite = positional[0];
+ var start = named.TryGetValue("start", out var st) ? ParseInt(st) : 0;
+ var count = named.TryGetValue("count", out var cn) ? ParseInt(cn) : 1;
+ var speed = named.TryGetValue("speed", out var sp) ? ParseFloat(sp) : 1f;
+ var loop = !named.TryGetValue("loop", out var lp) || lp == "true";
+ dlg.Animation(sprite, start, count, speed, 1f, loop);
+ }
+ }
+
+ private static float ParseFloat(string s)
+ {
+ return float.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out var v) ? v : 0f;
+ }
+
+ private static int ParseInt(string s)
+ {
+ return int.TryParse(s, NumberStyles.Integer, CultureInfo.InvariantCulture, out var v) ? v : 0;
+ }
+ }
+}
diff --git a/src/Scenarios/Impls/BackgroundColorCommandStrategy.cs b/src/Scenarios/Impls/BackgroundColorCommandStrategy.cs
index a4217d8..1607d96 100644
--- a/src/Scenarios/Impls/BackgroundColorCommandStrategy.cs
+++ b/src/Scenarios/Impls/BackgroundColorCommandStrategy.cs
@@ -1,7 +1,7 @@
-using System.Collections.Generic;
-using Cthangover.Core.Localization;
-using Cthangover.Mods.Dialog;
-using Godot;
+using System.Collections.Generic;
+using Cthangover.Core.Localization;
+using Cthangover.Mods.Dialog;
+using Godot;
using Cthangover.Core.UI;
using Cthangover.Mods.Interface.Base.Lists;
using Cthangover.Core.UI.Dialog;
@@ -10,47 +10,47 @@ using Cthangover.Core.UI.Executable;
using Cthangover.Core.Scenarios;
using Cthangover.Core.Scenes;
using Cthangover.Core.Interactive;
-
-namespace Cthangover.Mods.Dialog.Scenarios
-{
- ///
- /// Handles the background_color DSL command. Sets the dialog scene's
- /// background color from a named color string (e.g. black, red)
- /// or an HTML hex color (e.g. #FF0000). Delegates to .
- ///
- public class BackgroundColorCommandStrategy : IScenarioCommandStrategy, IScenarioCommandSpec
- {
- ///
- public string Command => "background_color";
- ///
- public int MinPositional => 1;
- ///
- public int MaxPositional => 1;
-
- ///
- public void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale)
- {
- if (positional.Count > 0)
- dlg.BackgroundColor(ColorFromString(positional[0]));
- }
-
- private static Color ColorFromString(string s)
- {
- if (s.StartsWith('#'))
- return Color.FromHtml(s.Substring(1));
-
- var named = s.ToLowerInvariant() switch
- {
- "black" => Colors.Black,
- "white" => Colors.White,
- "red" => Colors.Red,
- "green" => Colors.Green,
- "blue" => Colors.Blue,
- "yellow" => Colors.Yellow,
- "gray" or "grey" => Colors.Gray,
- _ => Colors.Black,
- };
- return named;
- }
- }
-}
+
+namespace Cthangover.Mods.Dialog.Scenarios
+{
+ ///
+ /// Handles the background_color DSL command. Sets the dialog scene's
+ /// background color from a named color string (e.g. black, red)
+ /// or an HTML hex color (e.g. #FF0000). Delegates to .
+ ///
+ public class BackgroundColorCommandStrategy : IScenarioCommandStrategy, IScenarioCommandSpec
+ {
+ ///
+ public string Command => "background_color";
+ ///
+ public int MinPositional => 1;
+ ///
+ public int MaxPositional => 1;
+
+ ///
+ public void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale)
+ {
+ if (positional.Count > 0)
+ dlg.BackgroundColor(ColorFromString(positional[0]));
+ }
+
+ private static Color ColorFromString(string s)
+ {
+ if (s.StartsWith('#'))
+ return Color.FromHtml(s.Substring(1));
+
+ var named = s.ToLowerInvariant() switch
+ {
+ "black" => Colors.Black,
+ "white" => Colors.White,
+ "red" => Colors.Red,
+ "green" => Colors.Green,
+ "blue" => Colors.Blue,
+ "yellow" => Colors.Yellow,
+ "gray" or "grey" => Colors.Gray,
+ _ => Colors.Black,
+ };
+ return named;
+ }
+ }
+}
diff --git a/src/Scenarios/Impls/BackgroundCommandStrategy.cs b/src/Scenarios/Impls/BackgroundCommandStrategy.cs
index 56f9088..0ce4163 100644
--- a/src/Scenarios/Impls/BackgroundCommandStrategy.cs
+++ b/src/Scenarios/Impls/BackgroundCommandStrategy.cs
@@ -1,6 +1,6 @@
-using System.Collections.Generic;
-using Cthangover.Core.Localization;
-using Cthangover.Mods.Dialog;
+using System.Collections.Generic;
+using Cthangover.Core.Localization;
+using Cthangover.Mods.Dialog;
using Cthangover.Core.UI;
using Cthangover.Mods.Interface.Base.Lists;
using Cthangover.Core.UI.Dialog;
@@ -9,30 +9,30 @@ using Cthangover.Core.UI.Executable;
using Cthangover.Core.Scenarios;
using Cthangover.Core.Scenes;
using Cthangover.Core.Interactive;
-
-namespace Cthangover.Mods.Dialog.Scenarios
-{
- ///
- /// Handles the background DSL command. Sets the dialog scene background
- /// image from a resource path. Implements
- /// with so the build pipeline
- /// can track referenced background assets.
- ///
- public class BackgroundCommandStrategy : IScenarioCommandStrategy, ICommandReferenceMetadata, IScenarioCommandSpec
- {
- ///
- public string Command => "background";
- ///
- public PositionalReferenceKind Positional0Kind => PositionalReferenceKind.Background;
- ///
- public int MinPositional => 1;
- ///
- public int MaxPositional => 1;
-
- ///
- public void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale)
- {
- dlg.Background(positional.Count > 0 ? positional[0] : "");
- }
- }
-}
+
+namespace Cthangover.Mods.Dialog.Scenarios
+{
+ ///
+ /// Handles the background DSL command. Sets the dialog scene background
+ /// image from a resource path. Implements
+ /// with so the build pipeline
+ /// can track referenced background assets.
+ ///
+ public class BackgroundCommandStrategy : IScenarioCommandStrategy, ICommandReferenceMetadata, IScenarioCommandSpec
+ {
+ ///
+ public string Command => "background";
+ ///
+ public PositionalReferenceKind Positional0Kind => PositionalReferenceKind.Background;
+ ///
+ public int MinPositional => 1;
+ ///
+ public int MaxPositional => 1;
+
+ ///
+ public void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale)
+ {
+ dlg.Background(positional.Count > 0 ? positional[0] : "");
+ }
+ }
+}
diff --git a/src/Scenarios/Impls/BackgroundShowHideCommandStrategy.cs b/src/Scenarios/Impls/BackgroundShowHideCommandStrategy.cs
index cd2ee67..b7af899 100644
--- a/src/Scenarios/Impls/BackgroundShowHideCommandStrategy.cs
+++ b/src/Scenarios/Impls/BackgroundShowHideCommandStrategy.cs
@@ -1,8 +1,8 @@
-using System.Collections.Generic;
-using System.Globalization;
-using Cthangover.Core.Localization;
-using Cthangover.Mods.Dialog;
-using Cthangover.Mods.Dialog.Action.Impls;
+using System.Collections.Generic;
+using System.Globalization;
+using Cthangover.Core.Localization;
+using Cthangover.Mods.Dialog;
+using Cthangover.Mods.Dialog.Action.Impls;
using Cthangover.Core.UI;
using Cthangover.Mods.Interface.Base.Lists;
using Cthangover.Core.UI.Dialog;
@@ -11,38 +11,38 @@ using Cthangover.Core.UI.Executable;
using Cthangover.Core.Scenarios;
using Cthangover.Core.Scenes;
using Cthangover.Core.Interactive;
-
-namespace Cthangover.Mods.Dialog.Scenarios
-{
- ///
- /// Handles the background_show_hide DSL command. Animates the background
- /// in (show) or out (hide) with configurable duration and
- /// wait flag. Delegates to .
- ///
- public class BackgroundShowHideCommandStrategy : IScenarioCommandStrategy, IScenarioCommandSpec
- {
- ///
- public string Command => "background_show_hide";
- ///
- public int MaxPositional => 1;
- ///
- public IReadOnlyList AllowedNamed => new[] { "duration", "wait" };
-
- ///
- public void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale)
- {
- var typeStr = positional.Count > 0 ? positional[0] : "show";
- var type = typeStr.ToLowerInvariant() == "show"
- ? BackgroundActionType.Show
- : BackgroundActionType.Hide;
- var duration = named.TryGetValue("duration", out var dur) ? ParseFloat(dur) : 1f;
- var wait = named.TryGetValue("wait", out var w) && w == "true";
- dlg.BackgroundShowHide(type, duration, wait);
- }
-
- private static float ParseFloat(string s)
- {
- return float.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out var v) ? v : 0f;
- }
- }
-}
+
+namespace Cthangover.Mods.Dialog.Scenarios
+{
+ ///
+ /// Handles the background_show_hide DSL command. Animates the background
+ /// in (show) or out (hide) with configurable duration and
+ /// wait flag. Delegates to .
+ ///
+ public class BackgroundShowHideCommandStrategy : IScenarioCommandStrategy, IScenarioCommandSpec
+ {
+ ///
+ public string Command => "background_show_hide";
+ ///
+ public int MaxPositional => 1;
+ ///
+ public IReadOnlyList AllowedNamed => new[] { "duration", "wait" };
+
+ ///
+ public void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale)
+ {
+ var typeStr = positional.Count > 0 ? positional[0] : "show";
+ var type = typeStr.ToLowerInvariant() == "show"
+ ? BackgroundActionType.Show
+ : BackgroundActionType.Hide;
+ var duration = named.TryGetValue("duration", out var dur) ? ParseFloat(dur) : 1f;
+ var wait = named.TryGetValue("wait", out var w) && w == "true";
+ dlg.BackgroundShowHide(type, duration, wait);
+ }
+
+ private static float ParseFloat(string s)
+ {
+ return float.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out var v) ? v : 0f;
+ }
+ }
+}
diff --git a/src/Scenarios/Impls/DelayCommandStrategy.cs b/src/Scenarios/Impls/DelayCommandStrategy.cs
index fc8788d..0d87389 100644
--- a/src/Scenarios/Impls/DelayCommandStrategy.cs
+++ b/src/Scenarios/Impls/DelayCommandStrategy.cs
@@ -1,7 +1,7 @@
-using System.Collections.Generic;
-using System.Globalization;
-using Cthangover.Core.Localization;
-using Cthangover.Mods.Dialog;
+using System.Collections.Generic;
+using System.Globalization;
+using Cthangover.Core.Localization;
+using Cthangover.Mods.Dialog;
using Cthangover.Core.UI;
using Cthangover.Mods.Interface.Base.Lists;
using Cthangover.Core.UI.Dialog;
@@ -10,49 +10,49 @@ using Cthangover.Core.UI.Executable;
using Cthangover.Core.Scenarios;
using Cthangover.Core.Scenes;
using Cthangover.Core.Interactive;
-
-namespace Cthangover.Mods.Dialog.Scenarios
-{
- ///
- /// Handles the delay DSL command. Pauses dialog execution for a
- /// number of seconds, optionally displaying a text label during the pause.
- /// If the hidden flag is set, the delay runs without visible text.
- /// Supports localization via the key= named parameter.
- ///
- public class DelayCommandStrategy : IScenarioCommandStrategy, IScenarioCommandSpec
- {
- ///
- public string Command => "delay";
- ///
- public int MaxPositional => 2;
- ///
- public IReadOnlyList AllowedNamed => new[] { "key", "hidden" };
-
- ///
- public void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale)
- {
- var time = positional.Count > 0 ? ParseFloat(positional[0]) : 1f;
- var hidden = positional.Count > 1 && positional[1] == "hidden"
- || named.TryGetValue("hidden", out var h) && h == "true";
- var showText = hidden ? null
- : positional.Count > 1 ? positional[1]
- : null;
-
- if (named.TryGetValue("key", out var key) && locale != null && showText != null)
- {
- var loc = locale.Get(key);
- if (loc != null) showText = loc;
- }
-
- if (showText != null)
- dlg.Delay(time, showText);
- else
- dlg.Delay(time, hidden);
- }
-
- private static float ParseFloat(string s)
- {
- return float.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out var v) ? v : 0f;
- }
- }
-}
+
+namespace Cthangover.Mods.Dialog.Scenarios
+{
+ ///
+ /// Handles the delay DSL command. Pauses dialog execution for a
+ /// number of seconds, optionally displaying a text label during the pause.
+ /// If the hidden flag is set, the delay runs without visible text.
+ /// Supports localization via the key= named parameter.
+ ///
+ public class DelayCommandStrategy : IScenarioCommandStrategy, IScenarioCommandSpec
+ {
+ ///
+ public string Command => "delay";
+ ///
+ public int MaxPositional => 2;
+ ///
+ public IReadOnlyList AllowedNamed => new[] { "key", "hidden" };
+
+ ///
+ public void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale)
+ {
+ var time = positional.Count > 0 ? ParseFloat(positional[0]) : 1f;
+ var hidden = positional.Count > 1 && positional[1] == "hidden"
+ || named.TryGetValue("hidden", out var h) && h == "true";
+ var showText = hidden ? null
+ : positional.Count > 1 ? positional[1]
+ : null;
+
+ if (named.TryGetValue("key", out var key) && locale != null && showText != null)
+ {
+ var loc = locale.Get(key);
+ if (loc != null) showText = loc;
+ }
+
+ if (showText != null)
+ dlg.Delay(time, showText);
+ else
+ dlg.Delay(time, hidden);
+ }
+
+ private static float ParseFloat(string s)
+ {
+ return float.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out var v) ? v : 0f;
+ }
+ }
+}
diff --git a/src/Scenarios/Impls/EffectCommandStrategy.cs b/src/Scenarios/Impls/EffectCommandStrategy.cs
index 6f08048..3748613 100644
--- a/src/Scenarios/Impls/EffectCommandStrategy.cs
+++ b/src/Scenarios/Impls/EffectCommandStrategy.cs
@@ -1,6 +1,6 @@
-using System.Collections.Generic;
-using Cthangover.Core.Localization;
-using Cthangover.Mods.Dialog;
+using System.Collections.Generic;
+using Cthangover.Core.Localization;
+using Cthangover.Mods.Dialog;
using Cthangover.Core.UI;
using Cthangover.Mods.Interface.Base.Lists;
using Cthangover.Core.UI.Dialog;
@@ -9,30 +9,30 @@ using Cthangover.Core.UI.Executable;
using Cthangover.Core.Scenarios;
using Cthangover.Core.Scenes;
using Cthangover.Core.Interactive;
-
-namespace Cthangover.Mods.Dialog.Scenarios
-{
- ///
- /// Handles the effect DSL command. Applies a named visual effect/shader
- /// to the scene. Implements with
- /// for build-time dependency tracking.
- ///
- public class EffectCommandStrategy : IScenarioCommandStrategy, ICommandReferenceMetadata, IScenarioCommandSpec
- {
- ///
- public string Command => "effect";
- ///
- public PositionalReferenceKind Positional0Kind => PositionalReferenceKind.Effect;
- ///
- public int MinPositional => 1;
- ///
- public int MaxPositional => 1;
-
- ///
- public void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale)
- {
- if (positional.Count > 0)
- dlg.Effect(positional[0]);
- }
- }
-}
+
+namespace Cthangover.Mods.Dialog.Scenarios
+{
+ ///
+ /// Handles the effect DSL command. Applies a named visual effect/shader
+ /// to the scene. Implements with
+ /// for build-time dependency tracking.
+ ///
+ public class EffectCommandStrategy : IScenarioCommandStrategy, ICommandReferenceMetadata, IScenarioCommandSpec
+ {
+ ///
+ public string Command => "effect";
+ ///
+ public PositionalReferenceKind Positional0Kind => PositionalReferenceKind.Effect;
+ ///
+ public int MinPositional => 1;
+ ///
+ public int MaxPositional => 1;
+
+ ///
+ public void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale)
+ {
+ if (positional.Count > 0)
+ dlg.Effect(positional[0]);
+ }
+ }
+}
diff --git a/src/Scenarios/Impls/EmptyCommandStrategy.cs b/src/Scenarios/Impls/EmptyCommandStrategy.cs
index 96aa3cc..6ecea0d 100644
--- a/src/Scenarios/Impls/EmptyCommandStrategy.cs
+++ b/src/Scenarios/Impls/EmptyCommandStrategy.cs
@@ -1,6 +1,6 @@
-using System.Collections.Generic;
-using Cthangover.Core.Localization;
-using Cthangover.Mods.Dialog;
+using System.Collections.Generic;
+using Cthangover.Core.Localization;
+using Cthangover.Mods.Dialog;
using Cthangover.Core.UI;
using Cthangover.Mods.Interface.Base.Lists;
using Cthangover.Core.UI.Dialog;
@@ -9,26 +9,26 @@ using Cthangover.Core.UI.Executable;
using Cthangover.Core.Scenarios;
using Cthangover.Core.Scenes;
using Cthangover.Core.Interactive;
-
-namespace Cthangover.Mods.Dialog.Scenarios
-{
- ///
- /// Handles the empty DSL command. Queues a no-op placeholder action
- /// () that is skipped instantly — the DSL-level label
- /// mechanism (:label via ) is built on the
- /// same action type, and a bare empty line works as a placeholder marker.
- ///
- public class EmptyCommandStrategy : IScenarioCommandStrategy, IScenarioCommandSpec
- {
- ///
- public string Command => "empty";
- ///
- public int MaxPositional => 0;
-
- ///
- public void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale)
- {
- dlg.Empty();
- }
- }
-}
+
+namespace Cthangover.Mods.Dialog.Scenarios
+{
+ ///
+ /// Handles the empty DSL command. Queues a no-op placeholder action
+ /// () that is skipped instantly — the DSL-level label
+ /// mechanism (:label via ) is built on the
+ /// same action type, and a bare empty line works as a placeholder marker.
+ ///
+ public class EmptyCommandStrategy : IScenarioCommandStrategy, IScenarioCommandSpec
+ {
+ ///
+ public string Command => "empty";
+ ///
+ public int MaxPositional => 0;
+
+ ///
+ public void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale)
+ {
+ dlg.Empty();
+ }
+ }
+}
diff --git a/src/Scenarios/Impls/EndCommandStrategy.cs b/src/Scenarios/Impls/EndCommandStrategy.cs
index 36675c9..68bbf61 100644
--- a/src/Scenarios/Impls/EndCommandStrategy.cs
+++ b/src/Scenarios/Impls/EndCommandStrategy.cs
@@ -1,6 +1,6 @@
-using System.Collections.Generic;
-using Cthangover.Core.Localization;
-using Cthangover.Mods.Dialog;
+using System.Collections.Generic;
+using Cthangover.Core.Localization;
+using Cthangover.Mods.Dialog;
using Cthangover.Core.UI;
using Cthangover.Mods.Interface.Base.Lists;
using Cthangover.Core.UI.Dialog;
@@ -9,25 +9,25 @@ using Cthangover.Core.UI.Executable;
using Cthangover.Core.Scenarios;
using Cthangover.Core.Scenes;
using Cthangover.Core.Interactive;
-
-namespace Cthangover.Mods.Dialog.Scenarios
-{
- ///
- /// Handles the end DSL command. Marks the end of the dialog sequence,
- /// terminating execution of the current scenario. Typically used at the final
- /// line of a scenario script to signal the dialog engine to close or transition.
- ///
- public class EndCommandStrategy : IScenarioCommandStrategy, IScenarioCommandSpec
- {
- ///
- public string Command => "end";
- ///
- public int MaxPositional => 0;
-
- ///
- public void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale)
- {
- dlg.End();
- }
- }
-}
+
+namespace Cthangover.Mods.Dialog.Scenarios
+{
+ ///
+ /// Handles the end DSL command. Marks the end of the dialog sequence,
+ /// terminating execution of the current scenario. Typically used at the final
+ /// line of a scenario script to signal the dialog engine to close or transition.
+ ///
+ public class EndCommandStrategy : IScenarioCommandStrategy, IScenarioCommandSpec
+ {
+ ///
+ public string Command => "end";
+ ///
+ public int MaxPositional => 0;
+
+ ///
+ public void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale)
+ {
+ dlg.End();
+ }
+ }
+}
diff --git a/src/Scenarios/Impls/FlagCommandStrategy.cs b/src/Scenarios/Impls/FlagCommandStrategy.cs
new file mode 100644
index 0000000..2fcc26e
--- /dev/null
+++ b/src/Scenarios/Impls/FlagCommandStrategy.cs
@@ -0,0 +1,25 @@
+using System.Collections.Generic;
+using Cthangover.Core.Localization;
+
+namespace Cthangover.Mods.Dialog.Scenarios
+{
+ ///
+ /// Handles the flag name value DSL command: sets a story flag of the
+ /// game through . Flags outlive the dialog
+ /// (saved, reset by a new game) and are what condition: headers read;
+ /// set is the dialog-local counterpart for ${var} substitution.
+ ///
+ public class FlagCommandStrategy : IScenarioCommandStrategy, IScenarioCommandSpec
+ {
+ ///
+ public string Command => "flag";
+ ///
+ public int MinPositional => 2;
+ ///
+ public int MaxPositional => 2;
+
+ ///
+ public void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale)
+ => dlg.Flag(positional[0], positional[1]);
+ }
+}
diff --git a/src/Scenarios/Impls/ForegroundCommandStrategy.cs b/src/Scenarios/Impls/ForegroundCommandStrategy.cs
index c65629d..c9c492d 100644
--- a/src/Scenarios/Impls/ForegroundCommandStrategy.cs
+++ b/src/Scenarios/Impls/ForegroundCommandStrategy.cs
@@ -1,6 +1,6 @@
-using System.Collections.Generic;
-using Cthangover.Core.Localization;
-using Cthangover.Mods.Dialog;
+using System.Collections.Generic;
+using Cthangover.Core.Localization;
+using Cthangover.Mods.Dialog;
using Cthangover.Core.UI;
using Cthangover.Mods.Interface.Base.Lists;
using Cthangover.Core.UI.Dialog;
@@ -9,27 +9,27 @@ using Cthangover.Core.UI.Executable;
using Cthangover.Core.Scenarios;
using Cthangover.Core.Scenes;
using Cthangover.Core.Interactive;
-
-namespace Cthangover.Mods.Dialog.Scenarios
-{
- ///
- /// Handles the foreground DSL command. Places a foreground overlay
- /// image on top of the scene (above background and characters). The first
- /// positional argument specifies the resource path to the image asset.
- ///
- public class ForegroundCommandStrategy : IScenarioCommandStrategy, IScenarioCommandSpec
- {
- ///
- public string Command => "foreground";
- ///
- public int MinPositional => 1;
- ///
- public int MaxPositional => 1;
-
- ///
- public void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale)
- {
- dlg.Foreground(positional.Count > 0 ? positional[0] : "");
- }
- }
-}
+
+namespace Cthangover.Mods.Dialog.Scenarios
+{
+ ///
+ /// Handles the foreground DSL command. Places a foreground overlay
+ /// image on top of the scene (above background and characters). The first
+ /// positional argument specifies the resource path to the image asset.
+ ///
+ public class ForegroundCommandStrategy : IScenarioCommandStrategy, IScenarioCommandSpec
+ {
+ ///
+ public string Command => "foreground";
+ ///
+ public int MinPositional => 1;
+ ///
+ public int MaxPositional => 1;
+
+ ///
+ public void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale)
+ {
+ dlg.Foreground(positional.Count > 0 ? positional[0] : "");
+ }
+ }
+}
diff --git a/src/Scenarios/Impls/GotoCommandStrategy.cs b/src/Scenarios/Impls/GotoCommandStrategy.cs
index b2d8e71..f452458 100644
--- a/src/Scenarios/Impls/GotoCommandStrategy.cs
+++ b/src/Scenarios/Impls/GotoCommandStrategy.cs
@@ -1,6 +1,6 @@
-using System.Collections.Generic;
-using Cthangover.Core.Localization;
-using Cthangover.Mods.Dialog;
+using System.Collections.Generic;
+using Cthangover.Core.Localization;
+using Cthangover.Mods.Dialog;
using Cthangover.Core.UI;
using Cthangover.Mods.Interface.Base.Lists;
using Cthangover.Core.UI.Dialog;
@@ -9,35 +9,35 @@ using Cthangover.Core.UI.Executable;
using Cthangover.Core.Scenarios;
using Cthangover.Core.Scenes;
using Cthangover.Core.Interactive;
-
-namespace Cthangover.Mods.Dialog.Scenarios
-{
- ///
- /// Handles the goto DSL command. Jumps execution to a labeled anchor
- /// point (:labelname) in the scenario script. Supports two forms:
- /// goto target_name via target= named parameter, or
- /// goto -> target_name via the arrow target syntax.
- ///
- public class GotoCommandStrategy : IScenarioCommandStrategy, IScenarioCommandSpec
- {
- ///
- public string Command => "goto";
- ///
- public int MinArguments => 1;
- ///
- public int MaxPositional => 0;
- ///
- public IReadOnlyList AllowedNamed => new[] { "target" };
- ///
- public string JumpTargetNamedKey => "target";
-
- ///
- public void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale)
- {
- if (!string.IsNullOrEmpty(arrowTarget))
- dlg.GoTo(arrowTarget);
- else if (named.TryGetValue("target", out var target))
- dlg.GoTo(target);
- }
- }
-}
+
+namespace Cthangover.Mods.Dialog.Scenarios
+{
+ ///
+ /// Handles the goto DSL command. Jumps execution to a labeled anchor
+ /// point (:labelname) in the scenario script. Supports two forms:
+ /// goto target_name via target= named parameter, or
+ /// goto -> target_name via the arrow target syntax.
+ ///
+ public class GotoCommandStrategy : IScenarioCommandStrategy, IScenarioCommandSpec
+ {
+ ///
+ public string Command => "goto";
+ ///
+ public int MinArguments => 1;
+ ///
+ public int MaxPositional => 0;
+ ///
+ public IReadOnlyList AllowedNamed => new[] { "target" };
+ ///
+ public string JumpTargetNamedKey => "target";
+
+ ///
+ public void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale)
+ {
+ if (!string.IsNullOrEmpty(arrowTarget))
+ dlg.GoTo(arrowTarget);
+ else if (named.TryGetValue("target", out var target))
+ dlg.GoTo(target);
+ }
+ }
+}
diff --git a/src/Scenarios/Impls/HideDialogCommandStrategy.cs b/src/Scenarios/Impls/HideDialogCommandStrategy.cs
index ca061f8..0c84c11 100644
--- a/src/Scenarios/Impls/HideDialogCommandStrategy.cs
+++ b/src/Scenarios/Impls/HideDialogCommandStrategy.cs
@@ -1,6 +1,6 @@
-using System.Collections.Generic;
-using Cthangover.Core.Localization;
-using Cthangover.Mods.Dialog;
+using System.Collections.Generic;
+using Cthangover.Core.Localization;
+using Cthangover.Mods.Dialog;
using Cthangover.Core.UI;
using Cthangover.Mods.Interface.Base.Lists;
using Cthangover.Core.UI.Dialog;
@@ -9,25 +9,25 @@ using Cthangover.Core.UI.Executable;
using Cthangover.Core.Scenarios;
using Cthangover.Core.Scenes;
using Cthangover.Core.Interactive;
-
-namespace Cthangover.Mods.Dialog.Scenarios
-{
- ///
- /// Handles the hide_dialog DSL command. Hides the dialog UI panel
- /// (text box, avatars, and UI chrome) from the screen. The inverse of
- /// .
- ///
- public class HideDialogCommandStrategy : IScenarioCommandStrategy, IScenarioCommandSpec
- {
- ///
- public string Command => "hide_dialog";
- ///
- public int MaxPositional => 0;
-
- ///
- public void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale)
- {
- dlg.HideDialog();
- }
- }
-}
+
+namespace Cthangover.Mods.Dialog.Scenarios
+{
+ ///
+ /// Handles the hide_dialog DSL command. Hides the dialog UI panel
+ /// (text box, avatars, and UI chrome) from the screen. The inverse of
+ /// .
+ ///
+ public class HideDialogCommandStrategy : IScenarioCommandStrategy, IScenarioCommandSpec
+ {
+ ///
+ public string Command => "hide_dialog";
+ ///
+ public int MaxPositional => 0;
+
+ ///
+ public void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale)
+ {
+ dlg.HideDialog();
+ }
+ }
+}
diff --git a/src/Scenarios/Impls/LightSetCommandStrategy.cs b/src/Scenarios/Impls/LightSetCommandStrategy.cs
index 5ba3c45..70a1ce3 100644
--- a/src/Scenarios/Impls/LightSetCommandStrategy.cs
+++ b/src/Scenarios/Impls/LightSetCommandStrategy.cs
@@ -1,6 +1,6 @@
-using System.Collections.Generic;
-using Cthangover.Core.Localization;
-using Cthangover.Mods.Dialog;
+using System.Collections.Generic;
+using Cthangover.Core.Localization;
+using Cthangover.Mods.Dialog;
using Cthangover.Core.UI;
using Cthangover.Mods.Interface.Base.Lists;
using Cthangover.Core.UI.Dialog;
@@ -9,29 +9,29 @@ using Cthangover.Core.UI.Executable;
using Cthangover.Core.Scenarios;
using Cthangover.Core.Scenes;
using Cthangover.Core.Interactive;
-
-namespace Cthangover.Mods.Dialog.Scenarios.Impls
-{
- ///
- /// Handles the light_set DSL command. Passes a JSON string that
- /// describes lighting configuration to the dialog engine via
- /// . The JSON structure is forwarded
- /// as-is to the rendering layer for point-light setup.
- ///
- public class LightSetCommandStrategy : IScenarioCommandStrategy, IScenarioCommandSpec
- {
- ///
- public string Command => "light_set";
-
- ///
- public int MaxPositional => 1;
-
- ///
- public void Execute(DialogQueue dlg, List positional,
- Dictionary named, string arrowTarget, ILocalizationProvider locale)
- {
- var json = positional.Count > 0 ? positional[0] : null;
- dlg.LightSet(json);
- }
- }
-}
+
+namespace Cthangover.Mods.Dialog.Scenarios.Impls
+{
+ ///
+ /// Handles the light_set DSL command. Passes a JSON string that
+ /// describes lighting configuration to the dialog engine via
+ /// . The JSON structure is forwarded
+ /// as-is to the rendering layer for point-light setup.
+ ///
+ public class LightSetCommandStrategy : IScenarioCommandStrategy, IScenarioCommandSpec
+ {
+ ///
+ public string Command => "light_set";
+
+ ///
+ public int MaxPositional => 1;
+
+ ///
+ public void Execute(DialogQueue dlg, List positional,
+ Dictionary named, string arrowTarget, ILocalizationProvider locale)
+ {
+ var json = positional.Count > 0 ? positional[0] : null;
+ dlg.LightSet(json);
+ }
+ }
+}
diff --git a/src/Scenarios/Impls/LightUseTimeCommandStrategy.cs b/src/Scenarios/Impls/LightUseTimeCommandStrategy.cs
index 8685634..5b2a79b 100644
--- a/src/Scenarios/Impls/LightUseTimeCommandStrategy.cs
+++ b/src/Scenarios/Impls/LightUseTimeCommandStrategy.cs
@@ -1,6 +1,6 @@
-using System.Collections.Generic;
-using Cthangover.Core.Localization;
-using Cthangover.Mods.Dialog;
+using System.Collections.Generic;
+using Cthangover.Core.Localization;
+using Cthangover.Mods.Dialog;
using Cthangover.Core.UI;
using Cthangover.Mods.Interface.Base.Lists;
using Cthangover.Core.UI.Dialog;
@@ -9,26 +9,26 @@ using Cthangover.Core.UI.Executable;
using Cthangover.Core.Scenarios;
using Cthangover.Core.Scenes;
using Cthangover.Core.Interactive;
-
-namespace Cthangover.Mods.Dialog.Scenarios
-{
- ///
- /// Handles the light_use_time DSL command. Toggles whether the
- /// scene lighting should be driven by the simulated time-of-day system.
- /// Passes a boolean flag to .
- ///
- public class LightUseTimeCommandStrategy : IScenarioCommandStrategy, IScenarioCommandSpec
- {
- ///
- public string Command => "light_use_time";
- ///
- public int MaxPositional => 1;
-
- ///
- public void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale)
- {
- var val = positional.Count > 0 ? positional[0] : "true";
- dlg.LightUseTime(val == "true");
- }
- }
-}
+
+namespace Cthangover.Mods.Dialog.Scenarios
+{
+ ///
+ /// Handles the light_use_time DSL command. Toggles whether the
+ /// scene lighting should be driven by the simulated time-of-day system.
+ /// Passes a boolean flag to .
+ ///
+ public class LightUseTimeCommandStrategy : IScenarioCommandStrategy, IScenarioCommandSpec
+ {
+ ///
+ public string Command => "light_use_time";
+ ///
+ public int MaxPositional => 1;
+
+ ///
+ public void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale)
+ {
+ var val = positional.Count > 0 ? positional[0] : "true";
+ dlg.LightUseTime(val == "true");
+ }
+ }
+}
diff --git a/src/Scenarios/Impls/MusicCommandStrategy.cs b/src/Scenarios/Impls/MusicCommandStrategy.cs
index 896ed51..fafd221 100644
--- a/src/Scenarios/Impls/MusicCommandStrategy.cs
+++ b/src/Scenarios/Impls/MusicCommandStrategy.cs
@@ -1,6 +1,6 @@
-using System.Collections.Generic;
-using Cthangover.Core.Localization;
-using Cthangover.Mods.Dialog;
+using System.Collections.Generic;
+using Cthangover.Core.Localization;
+using Cthangover.Mods.Dialog;
using Cthangover.Core.UI;
using Cthangover.Mods.Interface.Base.Lists;
using Cthangover.Core.UI.Dialog;
@@ -9,31 +9,31 @@ using Cthangover.Core.UI.Executable;
using Cthangover.Core.Scenarios;
using Cthangover.Core.Scenes;
using Cthangover.Core.Interactive;
-
-namespace Cthangover.Mods.Dialog.Scenarios
-{
- ///
- /// Handles the music DSL command. Starts playing a background music
- /// track from the given resource path. Implements
- /// with so the build pipeline
- /// can track referenced audio assets.
- ///
- public class MusicCommandStrategy : IScenarioCommandStrategy, ICommandReferenceMetadata, IScenarioCommandSpec
- {
- ///
- public string Command => "music";
- ///
- public PositionalReferenceKind Positional0Kind => PositionalReferenceKind.Music;
- ///
- public int MinPositional => 1;
- ///
- public int MaxPositional => 1;
-
- ///
- public void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale)
- {
- if (positional.Count > 0)
- dlg.Music(positional[0]);
- }
- }
-}
+
+namespace Cthangover.Mods.Dialog.Scenarios
+{
+ ///
+ /// Handles the music DSL command. Starts playing a background music
+ /// track from the given resource path. Implements
+ /// with so the build pipeline
+ /// can track referenced audio assets.
+ ///
+ public class MusicCommandStrategy : IScenarioCommandStrategy, ICommandReferenceMetadata, IScenarioCommandSpec
+ {
+ ///
+ public string Command => "music";
+ ///
+ public PositionalReferenceKind Positional0Kind => PositionalReferenceKind.Music;
+ ///
+ public int MinPositional => 1;
+ ///
+ public int MaxPositional => 1;
+
+ ///
+ public void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale)
+ {
+ if (positional.Count > 0)
+ dlg.Music(positional[0]);
+ }
+ }
+}
diff --git a/src/Scenarios/Impls/MusicPauseCommandStrategy.cs b/src/Scenarios/Impls/MusicPauseCommandStrategy.cs
index c774100..57d9bb9 100644
--- a/src/Scenarios/Impls/MusicPauseCommandStrategy.cs
+++ b/src/Scenarios/Impls/MusicPauseCommandStrategy.cs
@@ -1,6 +1,6 @@
-using System.Collections.Generic;
-using Cthangover.Core.Localization;
-using Cthangover.Mods.Dialog;
+using System.Collections.Generic;
+using Cthangover.Core.Localization;
+using Cthangover.Mods.Dialog;
using Cthangover.Core.UI;
using Cthangover.Mods.Interface.Base.Lists;
using Cthangover.Core.UI.Dialog;
@@ -9,25 +9,25 @@ using Cthangover.Core.UI.Executable;
using Cthangover.Core.Scenarios;
using Cthangover.Core.Scenes;
using Cthangover.Core.Interactive;
-
-namespace Cthangover.Mods.Dialog.Scenarios
-{
- ///
- /// Handles the music_pause DSL command. Pauses the currently
- /// playing background music stream. Playback can be resumed later with
- /// .
- ///
- public class MusicPauseCommandStrategy : IScenarioCommandStrategy, IScenarioCommandSpec
- {
- ///
- public string Command => "music_pause";
- ///
- public int MaxPositional => 0;
-
- ///
- public void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale)
- {
- dlg.MusicPause();
- }
- }
-}
+
+namespace Cthangover.Mods.Dialog.Scenarios
+{
+ ///
+ /// Handles the music_pause DSL command. Pauses the currently
+ /// playing background music stream. Playback can be resumed later with
+ /// .
+ ///
+ public class MusicPauseCommandStrategy : IScenarioCommandStrategy, IScenarioCommandSpec
+ {
+ ///
+ public string Command => "music_pause";
+ ///
+ public int MaxPositional => 0;
+
+ ///
+ public void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale)
+ {
+ dlg.MusicPause();
+ }
+ }
+}
diff --git a/src/Scenarios/Impls/MusicPlayCommandStrategy.cs b/src/Scenarios/Impls/MusicPlayCommandStrategy.cs
index 952a291..28a8253 100644
--- a/src/Scenarios/Impls/MusicPlayCommandStrategy.cs
+++ b/src/Scenarios/Impls/MusicPlayCommandStrategy.cs
@@ -1,6 +1,6 @@
-using System.Collections.Generic;
-using Cthangover.Core.Localization;
-using Cthangover.Mods.Dialog;
+using System.Collections.Generic;
+using Cthangover.Core.Localization;
+using Cthangover.Mods.Dialog;
using Cthangover.Core.UI;
using Cthangover.Mods.Interface.Base.Lists;
using Cthangover.Core.UI.Dialog;
@@ -9,24 +9,24 @@ using Cthangover.Core.UI.Executable;
using Cthangover.Core.Scenarios;
using Cthangover.Core.Scenes;
using Cthangover.Core.Interactive;
-
-namespace Cthangover.Mods.Dialog.Scenarios
-{
- ///
- /// Handles the music_play DSL command. Resumes the currently
- /// paused background music stream. Has no effect if music is already playing.
- ///
- public class MusicPlayCommandStrategy : IScenarioCommandStrategy, IScenarioCommandSpec
- {
- ///
- public string Command => "music_play";
- ///
- public int MaxPositional => 0;
-
- ///
- public void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale)
- {
- dlg.MusicPlay();
- }
- }
-}
+
+namespace Cthangover.Mods.Dialog.Scenarios
+{
+ ///
+ /// Handles the music_play DSL command. Resumes the currently
+ /// paused background music stream. Has no effect if music is already playing.
+ ///
+ public class MusicPlayCommandStrategy : IScenarioCommandStrategy, IScenarioCommandSpec
+ {
+ ///
+ public string Command => "music_play";
+ ///
+ public int MaxPositional => 0;
+
+ ///
+ public void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale)
+ {
+ dlg.MusicPlay();
+ }
+ }
+}
diff --git a/src/Scenarios/Impls/PTextCommandStrategy.cs b/src/Scenarios/Impls/PTextCommandStrategy.cs
index fff8331..44ee4c5 100644
--- a/src/Scenarios/Impls/PTextCommandStrategy.cs
+++ b/src/Scenarios/Impls/PTextCommandStrategy.cs
@@ -1,7 +1,7 @@
-using System.Collections.Generic;
-using Cthangover.Core.Localization;
-using Cthangover.Mods.Dialog;
-using Cthangover.Mods.Dialog.Action;
+using System.Collections.Generic;
+using Cthangover.Core.Localization;
+using Cthangover.Mods.Dialog;
+using Cthangover.Mods.Dialog.Action;
using Cthangover.Core.UI;
using Cthangover.Mods.Interface.Base.Lists;
using Cthangover.Core.UI.Dialog;
@@ -10,45 +10,45 @@ using Cthangover.Core.UI.Executable;
using Cthangover.Core.Scenarios;
using Cthangover.Core.Scenes;
using Cthangover.Core.Interactive;
-
-namespace Cthangover.Mods.Dialog.Scenarios
-{
- ///
- /// Handles the ptext DSL command. A "plain text" variant of
- /// that skips speaker attribution
- /// but still supports first/second avatar faces and
- /// hide_color and wait behavior flags. Text content
- /// can be provided directly or resolved via a key= localization key.
- ///
- public class PTextCommandStrategy : IScenarioCommandStrategy, IScenarioCommandSpec
- {
- ///
- public string Command => "ptext";
- ///
- public int MinArguments => 1;
- ///
- public int MaxPositional => 1;
- ///
- public IReadOnlyList AllowedNamed => new[] { "key", "first", "second", "hide_color", "wait" };
-
- ///
- public void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale)
- {
- var textVal = positional.Count > 0 ? positional[0] : "";
- if (named.TryGetValue("key", out var key) && locale != null)
- {
- var loc = locale.Get(key);
- if (loc != null) textVal = loc;
- }
-
- var first = named.TryGetValue("first", out var f) ? f : null;
- var second = named.TryGetValue("second", out var s) ? s : null;
- var hideColor = named.TryGetValue("hide_color", out var hc) && hc == "true";
-
- var action = dlg.PText(textVal, first, second, hideColor);
-
- if (named.TryGetValue("wait", out var wait) && wait == "false")
- action.WaitType = WaitType.NoWait;
- }
- }
-}
+
+namespace Cthangover.Mods.Dialog.Scenarios
+{
+ ///
+ /// Handles the ptext DSL command. A "plain text" variant of
+ /// that skips speaker attribution
+ /// but still supports first/second avatar faces and
+ /// hide_color and wait behavior flags. Text content
+ /// can be provided directly or resolved via a key= localization key.
+ ///
+ public class PTextCommandStrategy : IScenarioCommandStrategy, IScenarioCommandSpec
+ {
+ ///
+ public string Command => "ptext";
+ ///
+ public int MinArguments => 1;
+ ///
+ public int MaxPositional => 1;
+ ///
+ public IReadOnlyList AllowedNamed => new[] { "key", "first", "second", "hide_color", "wait" };
+
+ ///
+ public void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale)
+ {
+ var textVal = positional.Count > 0 ? positional[0] : "";
+ if (named.TryGetValue("key", out var key) && locale != null)
+ {
+ var loc = locale.Get(key);
+ if (loc != null) textVal = loc;
+ }
+
+ var first = named.TryGetValue("first", out var f) ? f : null;
+ var second = named.TryGetValue("second", out var s) ? s : null;
+ var hideColor = named.TryGetValue("hide_color", out var hc) && hc == "true";
+
+ var action = dlg.PText(textVal, first, second, hideColor);
+
+ if (named.TryGetValue("wait", out var wait) && wait == "false")
+ action.WaitType = WaitType.NoWait;
+ }
+ }
+}
diff --git a/src/Scenarios/Impls/SetCommandStrategy.cs b/src/Scenarios/Impls/SetCommandStrategy.cs
index 9da25e7..3a4668b 100644
--- a/src/Scenarios/Impls/SetCommandStrategy.cs
+++ b/src/Scenarios/Impls/SetCommandStrategy.cs
@@ -1,6 +1,6 @@
-using System.Collections.Generic;
-using Cthangover.Core.Localization;
-using Cthangover.Mods.Dialog;
+using System.Collections.Generic;
+using Cthangover.Core.Localization;
+using Cthangover.Mods.Dialog;
using Cthangover.Core.UI;
using Cthangover.Mods.Interface.Base.Lists;
using Cthangover.Core.UI.Dialog;
@@ -9,31 +9,31 @@ using Cthangover.Core.UI.Executable;
using Cthangover.Core.Scenarios;
using Cthangover.Core.Scenes;
using Cthangover.Core.Interactive;
-
-namespace Cthangover.Mods.Dialog.Scenarios
-{
- ///
- /// Handles the set DSL command. Stores an arbitrary key-value pair
- /// into the dialog session's variable state via .
- /// Values are read back later through ${var} substitution in
- /// ptext lines and via
- /// in scenario actions.
- ///
- public class SetCommandStrategy : IScenarioCommandStrategy, IScenarioCommandSpec
- {
- ///
- public string Command => "set";
- ///
- public int MinPositional => 2;
- ///
- public int MaxPositional => 2;
-
- ///
- public void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale)
- {
- var name = positional.Count > 0 ? positional[0] : "";
- var value = positional.Count > 1 ? positional[1] : "";
- dlg.Set(name, value);
- }
- }
-}
+
+namespace Cthangover.Mods.Dialog.Scenarios
+{
+ ///
+ /// Handles the set DSL command. Stores an arbitrary key-value pair
+ /// into the dialog session's variable state via .
+ /// Values are read back later through ${var} substitution in
+ /// ptext lines and via
+ /// in scenario actions.
+ ///
+ public class SetCommandStrategy : IScenarioCommandStrategy, IScenarioCommandSpec
+ {
+ ///
+ public string Command => "set";
+ ///
+ public int MinPositional => 2;
+ ///
+ public int MaxPositional => 2;
+
+ ///
+ public void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale)
+ {
+ var name = positional.Count > 0 ? positional[0] : "";
+ var value = positional.Count > 1 ? positional[1] : "";
+ dlg.Set(name, value);
+ }
+ }
+}
diff --git a/src/Scenarios/Impls/ShowDialogCommandStrategy.cs b/src/Scenarios/Impls/ShowDialogCommandStrategy.cs
index 73645ff..7eb91ea 100644
--- a/src/Scenarios/Impls/ShowDialogCommandStrategy.cs
+++ b/src/Scenarios/Impls/ShowDialogCommandStrategy.cs
@@ -1,6 +1,6 @@
-using System.Collections.Generic;
-using Cthangover.Core.Localization;
-using Cthangover.Mods.Dialog;
+using System.Collections.Generic;
+using Cthangover.Core.Localization;
+using Cthangover.Mods.Dialog;
using Cthangover.Core.UI;
using Cthangover.Mods.Interface.Base.Lists;
using Cthangover.Core.UI.Dialog;
@@ -9,25 +9,25 @@ using Cthangover.Core.UI.Executable;
using Cthangover.Core.Scenarios;
using Cthangover.Core.Scenes;
using Cthangover.Core.Interactive;
-
-namespace Cthangover.Mods.Dialog.Scenarios
-{
- ///
- /// Handles the show_dialog DSL command. Makes the dialog UI panel
- /// (text box, avatars, and UI chrome) visible on screen. The inverse of
- /// .
- ///
- public class ShowDialogCommandStrategy : IScenarioCommandStrategy, IScenarioCommandSpec
- {
- ///
- public string Command => "show_dialog";
- ///
- public int MaxPositional => 0;
-
- ///
- public void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale)
- {
- dlg.ShowDialog();
- }
- }
-}
+
+namespace Cthangover.Mods.Dialog.Scenarios
+{
+ ///
+ /// Handles the show_dialog DSL command. Makes the dialog UI panel
+ /// (text box, avatars, and UI chrome) visible on screen. The inverse of
+ /// .
+ ///
+ public class ShowDialogCommandStrategy : IScenarioCommandStrategy, IScenarioCommandSpec
+ {
+ ///
+ public string Command => "show_dialog";
+ ///
+ public int MaxPositional => 0;
+
+ ///
+ public void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale)
+ {
+ dlg.ShowDialog();
+ }
+ }
+}
diff --git a/src/Scenarios/Impls/SoundCommandStrategy.cs b/src/Scenarios/Impls/SoundCommandStrategy.cs
index 8fa2d0a..676ffc8 100644
--- a/src/Scenarios/Impls/SoundCommandStrategy.cs
+++ b/src/Scenarios/Impls/SoundCommandStrategy.cs
@@ -1,6 +1,6 @@
-using System.Collections.Generic;
-using Cthangover.Core.Localization;
-using Cthangover.Mods.Dialog;
+using System.Collections.Generic;
+using Cthangover.Core.Localization;
+using Cthangover.Mods.Dialog;
using Cthangover.Core.UI;
using Cthangover.Mods.Interface.Base.Lists;
using Cthangover.Core.UI.Dialog;
@@ -9,31 +9,31 @@ using Cthangover.Core.UI.Executable;
using Cthangover.Core.Scenarios;
using Cthangover.Core.Scenes;
using Cthangover.Core.Interactive;
-
-namespace Cthangover.Mods.Dialog.Scenarios
-{
- ///
- /// Handles the sound DSL command. Plays a one-shot sound effect
- /// from the given resource path. Implements
- /// with so the build pipeline
- /// can track referenced audio assets.
- ///
- public class SoundCommandStrategy : IScenarioCommandStrategy, ICommandReferenceMetadata, IScenarioCommandSpec
- {
- ///
- public string Command => "sound";
- ///
- public PositionalReferenceKind Positional0Kind => PositionalReferenceKind.Sound;
- ///
- public int MinPositional => 1;
- ///
- public int MaxPositional => 1;
-
- ///
- public void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale)
- {
- if (positional.Count > 0)
- dlg.Sound(positional[0]);
- }
- }
-}
+
+namespace Cthangover.Mods.Dialog.Scenarios
+{
+ ///
+ /// Handles the sound DSL command. Plays a one-shot sound effect
+ /// from the given resource path. Implements
+ /// with so the build pipeline
+ /// can track referenced audio assets.
+ ///
+ public class SoundCommandStrategy : IScenarioCommandStrategy, ICommandReferenceMetadata, IScenarioCommandSpec
+ {
+ ///
+ public string Command => "sound";
+ ///
+ public PositionalReferenceKind Positional0Kind => PositionalReferenceKind.Sound;
+ ///
+ public int MinPositional => 1;
+ ///
+ public int MaxPositional => 1;
+
+ ///
+ public void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale)
+ {
+ if (positional.Count > 0)
+ dlg.Sound(positional[0]);
+ }
+ }
+}
diff --git a/src/Scenarios/Impls/SwitchSceneCommandStrategy.cs b/src/Scenarios/Impls/SwitchSceneCommandStrategy.cs
index c9ea5dd..474107b 100644
--- a/src/Scenarios/Impls/SwitchSceneCommandStrategy.cs
+++ b/src/Scenarios/Impls/SwitchSceneCommandStrategy.cs
@@ -1,7 +1,7 @@
-using System.Collections.Generic;
-using System.Globalization;
-using Cthangover.Core.Localization;
-using Cthangover.Mods.Dialog;
+using System.Collections.Generic;
+using System.Globalization;
+using Cthangover.Core.Localization;
+using Cthangover.Mods.Dialog;
using Cthangover.Core.UI;
using Cthangover.Mods.Interface.Base.Lists;
using Cthangover.Core.UI.Dialog;
@@ -10,41 +10,41 @@ using Cthangover.Core.UI.Executable;
using Cthangover.Core.Scenarios;
using Cthangover.Core.Scenes;
using Cthangover.Core.Interactive;
-
-namespace Cthangover.Mods.Dialog.Scenarios
-{
- ///
- /// Handles the switch_scene DSL command. Transitions to a new Godot
- /// scene (PackedScene) with configurable transition speed and an
- /// optional hidden mode that shows only the scene background during
- /// the transition. Implements with
- /// for build-time dependency tracking.
- ///
- public class SwitchSceneCommandStrategy : IScenarioCommandStrategy, ICommandReferenceMetadata, IScenarioCommandSpec
- {
- ///
- public string Command => "switch_scene";
- ///
- public PositionalReferenceKind Positional0Kind => PositionalReferenceKind.Scene;
- ///
- public int MinPositional => 1;
- ///
- public int MaxPositional => 1;
- ///
- public IReadOnlyList AllowedNamed => new[] { "speed", "hidden" };
-
- ///
- public void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale)
- {
- var scene = positional.Count > 0 ? positional[0] : "";
- var speed = named.TryGetValue("speed", out var sp) ? ParseFloat(sp) : 4f;
- var hidden = named.TryGetValue("hidden", out var h) && h == "true";
- dlg.SwitchScene(scene, speed, hidden);
- }
-
- private static float ParseFloat(string s)
- {
- return float.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out var v) ? v : 0f;
- }
- }
-}
+
+namespace Cthangover.Mods.Dialog.Scenarios
+{
+ ///
+ /// Handles the switch_scene DSL command. Transitions to a new Godot
+ /// scene (PackedScene) with configurable transition speed and an
+ /// optional hidden mode that shows only the scene background during
+ /// the transition. Implements with
+ /// for build-time dependency tracking.
+ ///
+ public class SwitchSceneCommandStrategy : IScenarioCommandStrategy, ICommandReferenceMetadata, IScenarioCommandSpec
+ {
+ ///
+ public string Command => "switch_scene";
+ ///
+ public PositionalReferenceKind Positional0Kind => PositionalReferenceKind.Scene;
+ ///
+ public int MinPositional => 1;
+ ///
+ public int MaxPositional => 1;
+ ///
+ public IReadOnlyList AllowedNamed => new[] { "speed", "hidden" };
+
+ ///
+ public void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale)
+ {
+ var scene = positional.Count > 0 ? positional[0] : "";
+ var speed = named.TryGetValue("speed", out var sp) ? ParseFloat(sp) : 4f;
+ var hidden = named.TryGetValue("hidden", out var h) && h == "true";
+ dlg.SwitchScene(scene, speed, hidden);
+ }
+
+ private static float ParseFloat(string s)
+ {
+ return float.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out var v) ? v : 0f;
+ }
+ }
+}
diff --git a/src/Scenarios/Impls/TextCommandStrategy.cs b/src/Scenarios/Impls/TextCommandStrategy.cs
index 4cd6375..40c2eed 100644
--- a/src/Scenarios/Impls/TextCommandStrategy.cs
+++ b/src/Scenarios/Impls/TextCommandStrategy.cs
@@ -1,7 +1,7 @@
-using System.Collections.Generic;
-using Cthangover.Core.Localization;
-using Cthangover.Mods.Dialog;
-using Cthangover.Mods.Dialog.Action;
+using System.Collections.Generic;
+using Cthangover.Core.Localization;
+using Cthangover.Mods.Dialog;
+using Cthangover.Mods.Dialog.Action;
using Cthangover.Core.UI;
using Cthangover.Mods.Interface.Base.Lists;
using Cthangover.Core.UI.Dialog;
@@ -10,45 +10,45 @@ using Cthangover.Core.UI.Executable;
using Cthangover.Core.Scenarios;
using Cthangover.Core.Scenes;
using Cthangover.Core.Interactive;
-
-namespace Cthangover.Mods.Dialog.Scenarios
-{
- ///
- /// Handles the text DSL command, the primary dialog display instruction.
- /// Shows character text with optional first/second speaker
- /// avatar faces, hide_color tint suppression, and a wait flag
- /// that controls whether the dialog waits for user input before continuing.
- /// Text can be literal or resolved from a key= localization entry.
- ///
- public class TextCommandStrategy : IScenarioCommandStrategy, IScenarioCommandSpec
- {
- ///
- public string Command => "text";
- ///
- public int MinArguments => 1;
- ///
- public int MaxPositional => 1;
- ///
- public IReadOnlyList AllowedNamed => new[] { "key", "first", "second", "hide_color", "wait" };
-
- ///
- public void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale)
- {
- var textVal = positional.Count > 0 ? positional[0] : "";
- if (named.TryGetValue("key", out var key) && locale != null)
- {
- var loc = locale.Get(key);
- if (loc != null) textVal = loc;
- }
-
- var first = named.TryGetValue("first", out var f) ? f : null;
- var second = named.TryGetValue("second", out var s) ? s : null;
- var hideColor = named.TryGetValue("hide_color", out var hc) && hc == "true";
-
- var action = dlg.Text(textVal, first, second, hideColor);
-
- if (named.TryGetValue("wait", out var wait) && wait == "false")
- action.WaitType = WaitType.NoWait;
- }
- }
-}
+
+namespace Cthangover.Mods.Dialog.Scenarios
+{
+ ///
+ /// Handles the text DSL command, the primary dialog display instruction.
+ /// Shows character text with optional first/second speaker
+ /// avatar faces, hide_color tint suppression, and a wait flag
+ /// that controls whether the dialog waits for user input before continuing.
+ /// Text can be literal or resolved from a key= localization entry.
+ ///
+ public class TextCommandStrategy : IScenarioCommandStrategy, IScenarioCommandSpec
+ {
+ ///
+ public string Command => "text";
+ ///
+ public int MinArguments => 1;
+ ///
+ public int MaxPositional => 1;
+ ///
+ public IReadOnlyList AllowedNamed => new[] { "key", "first", "second", "hide_color", "wait" };
+
+ ///
+ public void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale)
+ {
+ var textVal = positional.Count > 0 ? positional[0] : "";
+ if (named.TryGetValue("key", out var key) && locale != null)
+ {
+ var loc = locale.Get(key);
+ if (loc != null) textVal = loc;
+ }
+
+ var first = named.TryGetValue("first", out var f) ? f : null;
+ var second = named.TryGetValue("second", out var s) ? s : null;
+ var hideColor = named.TryGetValue("hide_color", out var hc) && hc == "true";
+
+ var action = dlg.Text(textVal, first, second, hideColor);
+
+ if (named.TryGetValue("wait", out var wait) && wait == "false")
+ action.WaitType = WaitType.NoWait;
+ }
+ }
+}
diff --git a/src/Scenarios/Impls/TitleCommandStrategy.cs b/src/Scenarios/Impls/TitleCommandStrategy.cs
index c1679da..a16ac3c 100644
--- a/src/Scenarios/Impls/TitleCommandStrategy.cs
+++ b/src/Scenarios/Impls/TitleCommandStrategy.cs
@@ -1,6 +1,6 @@
-using System.Collections.Generic;
-using Cthangover.Core.Localization;
-using Cthangover.Mods.Dialog;
+using System.Collections.Generic;
+using Cthangover.Core.Localization;
+using Cthangover.Mods.Dialog;
using Cthangover.Core.UI;
using Cthangover.Mods.Interface.Base.Lists;
using Cthangover.Core.UI.Dialog;
@@ -9,37 +9,37 @@ using Cthangover.Core.UI.Executable;
using Cthangover.Core.Scenarios;
using Cthangover.Core.Scenes;
using Cthangover.Core.Interactive;
-
-namespace Cthangover.Mods.Dialog.Scenarios
-{
- ///
- /// Handles the title DSL command. Displays a centered title/caption
- /// text on screen, typically used for chapter headings or location labels.
- /// Text can be literal or resolved from a key= localization entry.
- /// Silently does nothing if no text value is provided or resolved.
- ///
- public class TitleCommandStrategy : IScenarioCommandStrategy, IScenarioCommandSpec
- {
- ///
- public string Command => "title";
- ///
- public int MinArguments => 1;
- ///
- public int MaxPositional => 1;
- ///
- public IReadOnlyList AllowedNamed => new[] { "key" };
-
- ///
- public void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale)
- {
- var textVal = positional.Count > 0 ? positional[0] : null;
- if (named.TryGetValue("key", out var key) && locale != null)
- {
- var loc = locale.Get(key);
- if (loc != null) textVal = loc;
- }
- if (textVal != null)
- dlg.Title(textVal);
- }
- }
-}
+
+namespace Cthangover.Mods.Dialog.Scenarios
+{
+ ///
+ /// Handles the title DSL command. Displays a centered title/caption
+ /// text on screen, typically used for chapter headings or location labels.
+ /// Text can be literal or resolved from a key= localization entry.
+ /// Silently does nothing if no text value is provided or resolved.
+ ///
+ public class TitleCommandStrategy : IScenarioCommandStrategy, IScenarioCommandSpec
+ {
+ ///
+ public string Command => "title";
+ ///
+ public int MinArguments => 1;
+ ///
+ public int MaxPositional => 1;
+ ///
+ public IReadOnlyList AllowedNamed => new[] { "key" };
+
+ ///
+ public void Execute(DialogQueue dlg, List positional, Dictionary named, string arrowTarget, ILocalizationProvider locale)
+ {
+ var textVal = positional.Count > 0 ? positional[0] : null;
+ if (named.TryGetValue("key", out var key) && locale != null)
+ {
+ var loc = locale.Get(key);
+ if (loc != null) textVal = loc;
+ }
+ if (textVal != null)
+ dlg.Title(textVal);
+ }
+ }
+}
diff --git a/src/Scenarios/ScenarioConverter.cs b/src/Scenarios/ScenarioConverter.cs
index b663fe4..4ef8a39 100644
--- a/src/Scenarios/ScenarioConverter.cs
+++ b/src/Scenarios/ScenarioConverter.cs
@@ -1,12 +1,12 @@
-using System;
-using System.Collections.Generic;
-using Cthangover.Core.Localization;
-using Cthangover.Core.Mods;
-using Cthangover.Mods.Dialog;
-using Cthangover.Mods.Dialog.Action;
-using Cthangover.Mods.Dialog.Action.Impls;
-using Cthangover.Core.Utils;
-using Godot;
+using System;
+using System.Collections.Generic;
+using Cthangover.Core.Localization;
+using Cthangover.Core.Mods;
+using Cthangover.Mods.Dialog;
+using Cthangover.Mods.Dialog.Action;
+using Cthangover.Mods.Dialog.Action.Impls;
+using Cthangover.Core.Utils;
+using Godot;
using Cthangover.Core.UI;
using Cthangover.Mods.Interface.Base.Lists;
using Cthangover.Core.UI.Dialog;
@@ -15,60 +15,60 @@ using Cthangover.Core.UI.Executable;
using Cthangover.Core.Scenarios;
using Cthangover.Core.Scenes;
using Cthangover.Core.Interactive;
-
-namespace Cthangover.Mods.Dialog.Scenarios
-{
- ///
- /// Converts scenario DSL text or files into instances.
- /// Wraps with a configurable
- /// that defaults to when none is supplied.
- ///
- public class ScenarioConverter
- {
- private readonly ILocalizationProvider locale;
-
- ///
- /// Creates a converter with an optional localization provider.
- ///
- /// Localization provider for key= text lookups. Defaults to Godot translations.
- public ScenarioConverter(ILocalizationProvider locale = null)
- {
- this.locale = locale ?? GodotTranslationProvider.Instance;
- }
-
- ///
- /// Parses raw scenario script text into a dialog queue using the configured locale.
- ///
- public DialogQueue Convert(string text)
- {
- return ScenarioParser.Parse(text, locale);
- }
-
- ///
- /// Reads a scenario file from disk and parses it into a dialog queue.
- ///
- /// Filesystem path to the .scenario file.
- /// The parsed , or null if the file could not be opened.
- public DialogQueue ConvertFile(string path)
- {
- foreach (var kvp in ModRegistry.Instance.Mods)
- {
- var text = kvp.Value.FileProvider.ReadFileText(path);
- if (text != null)
- return Convert(text);
- }
-
- var file = FileAccess.Open(path, FileAccess.ModeFlags.Read);
- if (file == null)
- {
- GameLogger.Log("CONVERTER", $"Failed to open '{path}'", LogLevel.Error);
- return null;
- }
-
- var fileText = file.GetAsText();
- file.Close();
-
- return Convert(fileText);
- }
- }
-}
+
+namespace Cthangover.Mods.Dialog.Scenarios
+{
+ ///
+ /// Converts scenario DSL text or files into instances.
+ /// Wraps with a configurable
+ /// that defaults to when none is supplied.
+ ///
+ public class ScenarioConverter
+ {
+ private readonly ILocalizationProvider locale;
+
+ ///
+ /// Creates a converter with an optional localization provider.
+ ///
+ /// Localization provider for key= text lookups. Defaults to Godot translations.
+ public ScenarioConverter(ILocalizationProvider locale = null)
+ {
+ this.locale = locale ?? GodotTranslationProvider.Instance;
+ }
+
+ ///
+ /// Parses raw scenario script text into a dialog queue using the configured locale.
+ ///
+ public DialogQueue Convert(string text)
+ {
+ return ScenarioParser.Parse(text, locale);
+ }
+
+ ///
+ /// Reads a scenario file from disk and parses it into a dialog queue.
+ ///
+ /// Filesystem path to the .scenario file.
+ /// The parsed , or null if the file could not be opened.
+ public DialogQueue ConvertFile(string path)
+ {
+ foreach (var kvp in ModRegistry.Instance.Mods)
+ {
+ var text = kvp.Value.FileProvider.ReadFileText(path);
+ if (text != null)
+ return Convert(text);
+ }
+
+ var file = FileAccess.Open(path, FileAccess.ModeFlags.Read);
+ if (file == null)
+ {
+ GameLogger.Log("CONVERTER", $"Failed to open '{path}'", LogLevel.Error);
+ return null;
+ }
+
+ var fileText = file.GetAsText();
+ file.Close();
+
+ return Convert(fileText);
+ }
+ }
+}
diff --git a/src/Scenarios/ScenarioLoader.cs b/src/Scenarios/ScenarioLoader.cs
index b0fbec4..e3bc3fc 100644
--- a/src/Scenarios/ScenarioLoader.cs
+++ b/src/Scenarios/ScenarioLoader.cs
@@ -1,7 +1,7 @@
-using Cthangover.Core.Localization;
-using Cthangover.Mods.Dialog;
-using Cthangover.Core.Utils;
-using Godot;
+using Cthangover.Core.Localization;
+using Cthangover.Mods.Dialog;
+using Cthangover.Core.Utils;
+using Godot;
using Cthangover.Core.UI;
using Cthangover.Mods.Interface.Base.Lists;
using Cthangover.Core.UI.Dialog;
@@ -10,45 +10,45 @@ using Cthangover.Core.UI.Executable;
using Cthangover.Core.Scenarios;
using Cthangover.Core.Scenes;
using Cthangover.Core.Interactive;
-
-namespace Cthangover.Mods.Dialog.Scenarios
-{
- ///
- /// Loads a .scenario file from disk, reads its text content, and
- /// parses it via into a .
- /// Uses as the default localization source.
- ///
- public static class ScenarioLoader
- {
- ///
- /// Loads and parses a scenario file using the default Godot translation provider.
- ///
- /// Filesystem path to the .scenario file.
- /// The parsed , or null if the file could not be opened.
- public static DialogQueue Load(string path)
- {
- return Load(path, GodotTranslationProvider.Instance);
- }
-
- ///
- /// Loads and parses a scenario file with a custom localization provider for text lookups.
- ///
- /// Filesystem path to the .scenario file.
- /// Localization provider to resolve key= references during parsing.
- /// The parsed , or null if the file could not be opened.
- public static DialogQueue Load(string path, ILocalizationProvider provider)
- {
- var file = FileAccess.Open(path, FileAccess.ModeFlags.Read);
- if (file == null)
- {
- GameLogger.Log("SCENE", $"ScenarioLoader: failed to open '{path}'", LogLevel.Error);
- return null;
- }
-
- var text = file.GetAsText();
- file.Close();
-
- return ScenarioParser.Parse(text, provider);
- }
- }
-}
+
+namespace Cthangover.Mods.Dialog.Scenarios
+{
+ ///
+ /// Loads a .scenario file from disk, reads its text content, and
+ /// parses it via into a .
+ /// Uses as the default localization source.
+ ///
+ public static class ScenarioLoader
+ {
+ ///
+ /// Loads and parses a scenario file using the default Godot translation provider.
+ ///
+ /// Filesystem path to the .scenario file.
+ /// The parsed , or null if the file could not be opened.
+ public static DialogQueue Load(string path)
+ {
+ return Load(path, GodotTranslationProvider.Instance);
+ }
+
+ ///
+ /// Loads and parses a scenario file with a custom localization provider for text lookups.
+ ///
+ /// Filesystem path to the .scenario file.
+ /// Localization provider to resolve key= references during parsing.
+ /// The parsed , or null if the file could not be opened.
+ public static DialogQueue Load(string path, ILocalizationProvider provider)
+ {
+ var file = FileAccess.Open(path, FileAccess.ModeFlags.Read);
+ if (file == null)
+ {
+ GameLogger.Log("SCENE", $"ScenarioLoader: failed to open '{path}'", LogLevel.Error);
+ return null;
+ }
+
+ var text = file.GetAsText();
+ file.Close();
+
+ return ScenarioParser.Parse(text, provider);
+ }
+ }
+}
diff --git a/src/UI/Dialog/Action/ActionCommand.cs b/src/UI/Dialog/Action/ActionCommand.cs
index 15c650d..d61ac41 100644
--- a/src/UI/Dialog/Action/ActionCommand.cs
+++ b/src/UI/Dialog/Action/ActionCommand.cs
@@ -63,12 +63,17 @@ namespace Cthangover.Mods.Dialog.Action
ID = Guid.NewGuid().ToString();
}
- /// Idempotent construction: calls once and sets to true.
+ ///
+ /// Idempotent construction: calls once and sets to true.
+ /// Construction also clears : an action reached again through a looping goto is a
+ /// new life of the same object, so its next must release what this construction took.
+ ///
public void Construct()
{
if(IsConstructed) return;
DoConstruct();
IsConstructed = true;
+ IsDestructed = false;
}
/// Idempotent desctruction: calls once, then sets false and true.
@@ -83,7 +88,7 @@ namespace Cthangover.Mods.Dialog.Action
/// Constructs if not already done, records start time, and delegates execution to .
public void Run(DialogRuntime runtime)
{
- if (!IsConstructed) DoConstruct();
+ Construct();
StartTime = (float)(Godot.Time.GetTicksUsec() / 1_000_000.0);
DoRun(runtime);
}
diff --git a/src/UI/Dialog/Action/ActionType.cs b/src/UI/Dialog/Action/ActionType.cs
index d812639..a2d94ea 100644
--- a/src/UI/Dialog/Action/ActionType.cs
+++ b/src/UI/Dialog/Action/ActionType.cs
@@ -1,14 +1,14 @@
-namespace Cthangover.Mods.Dialog.Action
-{
- ///
- /// High-level action category discriminator. Currently minimal (Text, End)
- /// — may expand as additional action families are added.
- ///
- public enum ActionType
- {
- /// A dialog text display action that pauses for click input.
- Text,
- /// Terminates the dialog queue.
- End
- }
-}
+namespace Cthangover.Mods.Dialog.Action
+{
+ ///
+ /// High-level action category discriminator. Currently minimal (Text, End)
+ /// — may expand as additional action families are added.
+ ///
+ public enum ActionType
+ {
+ /// A dialog text display action that pauses for click input.
+ Text,
+ /// Terminates the dialog queue.
+ End
+ }
+}
diff --git a/src/UI/Dialog/Action/ConstructType.cs b/src/UI/Dialog/Action/ConstructType.cs
index 9b125a5..7ff6504 100644
--- a/src/UI/Dialog/Action/ConstructType.cs
+++ b/src/UI/Dialog/Action/ConstructType.cs
@@ -1,17 +1,17 @@
-namespace Cthangover.Mods.Dialog.Action
-{
- ///
- /// Controls when an action's Construct() fires. OnStartQueue — construct
- /// when the dialog queue is first loaded (good for preloading); OnStartAction —
- /// lazy-construct just before the action runs (saves resources for unreached
- /// branches). Default for most actions is OnStartAction.
- ///
- public enum ConstructType
- {
- /// Construct when the dialog queue is first loaded. Useful for actions that need to preload resources.
- OnStartQueue,
- /// Construct lazily just before the action runs. Default for most actions — saves resources for unreached branches.
- OnStartAction
- }
-
-}
+namespace Cthangover.Mods.Dialog.Action
+{
+ ///
+ /// Controls when an action's Construct() fires. OnStartQueue — construct
+ /// when the dialog queue is first loaded (good for preloading); OnStartAction —
+ /// lazy-construct just before the action runs (saves resources for unreached
+ /// branches). Default for most actions is OnStartAction.
+ ///
+ public enum ConstructType
+ {
+ /// Construct when the dialog queue is first loaded. Useful for actions that need to preload resources.
+ OnStartQueue,
+ /// Construct lazily just before the action runs. Default for most actions — saves resources for unreached branches.
+ OnStartAction
+ }
+
+}
diff --git a/src/UI/Dialog/Action/DestructType.cs b/src/UI/Dialog/Action/DestructType.cs
index 6289cbd..432f303 100644
--- a/src/UI/Dialog/Action/DestructType.cs
+++ b/src/UI/Dialog/Action/DestructType.cs
@@ -1,20 +1,20 @@
-namespace Cthangover.Mods.Dialog.Action
-{
- ///
- /// Controls when an action's Destruct() fires. OnEndAction — immediately after
- /// the action advances (typical for most commands). OnDelayed — deferred until
- /// specifically cleaned up (for visual state that must persist across actions).
- /// OnEndQueue — destruct when the entire dialog queue finishes (for resources
- /// that must survive the whole dialog but not beyond).
- ///
- public enum DestructType
- {
- /// Destruct immediately when the action is advanced past. Default for most commands.
- OnEndAction,
- /// Destruct is deferred — must be manually triggered or triggered via .
- OnDelayed,
- /// Destruct when the entire dialog queue finishes. Use for resources that must survive the whole dialog but not beyond.
- OnEndQueue,
- }
-
-}
+namespace Cthangover.Mods.Dialog.Action
+{
+ ///
+ /// Controls when an action's Destruct() fires. OnEndAction — immediately after
+ /// the action advances (typical for most commands). OnDelayed — deferred until
+ /// specifically cleaned up (for visual state that must persist across actions).
+ /// OnEndQueue — destruct when the entire dialog queue finishes (for resources
+ /// that must survive the whole dialog but not beyond).
+ ///
+ public enum DestructType
+ {
+ /// Destruct immediately when the action is advanced past. Default for most commands.
+ OnEndAction,
+ /// Destruct is deferred — must be manually triggered or triggered via .
+ OnDelayed,
+ /// Destruct when the entire dialog queue finishes. Use for resources that must survive the whole dialog but not beyond.
+ OnEndQueue,
+ }
+
+}
diff --git a/src/UI/Dialog/Action/IActionDestruct.cs b/src/UI/Dialog/Action/IActionDestruct.cs
index c84c747..bdeffff 100644
--- a/src/UI/Dialog/Action/IActionDestruct.cs
+++ b/src/UI/Dialog/Action/IActionDestruct.cs
@@ -1,13 +1,13 @@
-namespace Cthangover.Mods.Dialog.Action
-{
- ///
- /// Minimal destructible contract — anything in the dialog runtime's object list
- /// that must be cleaned up when a dialog ends.
- ///
- public interface IActionDestruct
- {
- /// Called to release resources, disconnect signals, and mark the object as finished. Must be idempotent.
- void Destruct();
- }
-
-}
+namespace Cthangover.Mods.Dialog.Action
+{
+ ///
+ /// Minimal destructible contract — anything in the dialog runtime's object list
+ /// that must be cleaned up when a dialog ends.
+ ///
+ public interface IActionDestruct
+ {
+ /// Called to release resources, disconnect signals, and mark the object as finished. Must be idempotent.
+ void Destruct();
+ }
+
+}
diff --git a/src/UI/Dialog/Action/Impls/ActionAnimatedForeground.cs b/src/UI/Dialog/Action/Impls/ActionAnimatedForeground.cs
index d727bd5..7f65e3b 100644
--- a/src/UI/Dialog/Action/Impls/ActionAnimatedForeground.cs
+++ b/src/UI/Dialog/Action/Impls/ActionAnimatedForeground.cs
@@ -1,7 +1,6 @@
using System.Collections.Generic;
-using Cthangover.Mods.Effects.Animation;
+using Cthangover.Core.Factories.Impls;
using Cthangover.Mods.Dialog.Action;
-using Cthangover.Core.UI.Event;
using Cthangover.Mods.Interface.View;
using Godot;
using Cthangover.Core.UI;
@@ -15,106 +14,150 @@ namespace Cthangover.Mods.Dialog.Action.Impls
{
///
/// Frame-animates the scene foreground by swapping ViewBox textures at a
- /// configurable speed. Loads frames from SpriteIDs on construction
- /// (OnStartQueue). Unlike AnimationController which crossfades, this does
- /// hard cuts — simpler but acceptable for foreground effects. The speed
- /// switches to NextFrameSpeed after the first frame so first-frame delay
- /// can differ from subsequent frames. Registers as IOnUpdateEvent for
- /// per-frame advancement. Non-looping mode auto-stops at last frame.
+ /// configurable speed: the first frame lasts 1/ seconds,
+ /// every later one 1/. Hard cuts, no crossfade.
+ /// Frames come from or are resolved from
+ /// through (the same
+ /// texture names a foreground command takes) on construction
+ /// (OnStartQueue).
+ ///
+ /// Event-driven: each frame schedules one for the
+ /// next cut — no per-frame update. The dialog does not wait (NoWait); the
+ /// animation keeps playing while the dialog goes on and stops when the queue
+ /// ends (OnEndQueue), when another animation starts or when a
+ /// foreground command replaces the picture ().
///
- public class ActionAnimatedForeground : ActionCommand, IOnUpdateEvent
+ public class ActionAnimatedForeground : ActionCommand
{
- /// Update priority. Runs after higher-priority listeners. Value 0.
- public int Priority => 0;
+ private static ActionAnimatedForeground active;
- /// List of sprite resource paths to display as animation frames. Loaded in .
+ /// Texture names resolved through ; ignored when is set.
public List SpriteIDs { get; set; }
- /// Initial frame rate multiplier for the first frame.
+ /// Ready frames used instead of resolving (bypasses the factory like ActionForeground.Texture).
+ public List Textures { get; set; }
+ /// Frames per second of the first frame.
public float Speed { get; set; } = 1f;
- /// Frame rate multiplier for subsequent frames after the first. Allows different pacing for the initial frame.
+ /// Frames per second of every frame after the first one.
public float NextFrameSpeed { get; set; } = 1f;
- /// Whether the animation wraps after the last frame. When false, playback stops at the final frame.
+ /// Whether playback restarts from the first frame after the last one; otherwise it stops on the last frame.
public bool IsLoop { get; set; } = true;
- private int currentFrame;
- private float elapsed;
- private bool isAnimating;
- private List frames;
+ /// Whether the animation is still cutting frames.
+ public bool IsAnimating { get; private set; }
- /// Preloads frames when the dialog queue is first loaded, so textures are ready before playback.
+ private int currentFrame;
+ private List frames;
+ private SceneTreeTimer frameTimer;
+
+ /// Frames are resolved when the queue starts, so the first cut does not stall on loading.
public override ConstructType ConstructType { get; set; } = ConstructType.OnStartQueue;
- /// Animation runs in the background — the dialog queue continues immediately.
+ /// The animation lives until the dialog ends, not until the next action.
+ public override DestructType DestructType { get; set; } = DestructType.OnEndQueue;
+ /// The dialog goes on while the foreground animates.
public override WaitType WaitType { get; set; } = WaitType.NoWait;
- /// Preloads every sprite in into memory so playback never stalls on I/O.
+ /// Stops the animation that currently drives the foreground, if any; the shown frame stays.
+ public static void StopActive()
+ {
+ active?.Stop();
+ }
+
+ /// Resolves the frames.
public override void DoConstruct()
{
base.DoConstruct();
LoadFrames();
}
- /// Stops the animation loop; the last shown frame stays on screen.
+ /// Stops the frame timer; the last shown frame stays on the ViewBox.
public override void DoDestruct()
{
base.DoDestruct();
- isAnimating = false;
+ Stop();
}
private void LoadFrames()
{
frames = new List();
+ var source = Textures;
+ if (source != null)
+ {
+ foreach (var tex in source)
+ if (tex != null)
+ frames.Add(tex);
+ return;
+ }
+
if (SpriteIDs == null)
return;
foreach (var id in SpriteIDs)
{
- var tex = GD.Load(id);
+ var tex = BackgroundFactory.Instance.Get(id);
if (tex != null)
frames.Add(tex);
}
}
- /// Resets playback to the first frame and shows it on the ViewBox; per-frame stepping happens through .
+ /// Stops a previous animation, shows the first frame on the ViewBox and schedules the next cut.
public override void DoRun(DialogRuntime runtime)
{
+ StopActive();
currentFrame = 0;
- elapsed = 0f;
- isAnimating = frames != null && frames.Count > 0;
+ IsAnimating = frames != null && frames.Count > 0;
+ if (!IsAnimating)
+ return;
- if (isAnimating)
- {
- var viewBox = Scene.Find("ViewBox");
- viewBox?.SetForegroundTexture(frames[0]);
- }
+ active = this;
+ ShowFrame();
+ ScheduleNextFrame(Speed);
}
- void IOnUpdateEvent.OnUpdate()
+ private void ShowFrame()
{
- if (!isAnimating || frames == null || frames.Count == 0)
- return;
-
var viewBox = Scene.Find("ViewBox");
- if (viewBox == null)
+ viewBox?.SetForegroundTexture(frames[currentFrame]);
+ }
+
+ private void ScheduleNextFrame(float speed)
+ {
+ if (Godot.Engine.GetMainLoop() is not SceneTree tree)
return;
- elapsed += 1f / 60f;
- var frameDuration = 1f / Mathf.Max(Speed, 0.01f);
- if (elapsed >= frameDuration)
+ frameTimer = tree.CreateTimer(1f / Mathf.Max(speed, 0.01f));
+ frameTimer.Timeout += OnFrameTimeout;
+ }
+
+ private void OnFrameTimeout()
+ {
+ frameTimer = null;
+ if (!IsAnimating)
+ return;
+
+ currentFrame++;
+ if (currentFrame >= frames.Count)
{
- elapsed -= frameDuration;
- currentFrame++;
- if (currentFrame >= frames.Count)
+ if (!IsLoop)
{
- if (IsLoop)
- currentFrame = 0;
- else
- {
- isAnimating = false;
- return;
- }
+ Stop();
+ return;
}
- viewBox.SetForegroundTexture(frames[currentFrame]);
- Speed = NextFrameSpeed;
+ currentFrame = 0;
}
+
+ ShowFrame();
+ ScheduleNextFrame(NextFrameSpeed);
+ }
+
+ private void Stop()
+ {
+ IsAnimating = false;
+ if (frameTimer != null)
+ {
+ frameTimer.Timeout -= OnFrameTimeout;
+ frameTimer = null;
+ }
+ if (active == this)
+ active = null;
}
}
}
diff --git a/src/UI/Dialog/Action/Impls/ActionBackground.cs b/src/UI/Dialog/Action/Impls/ActionBackground.cs
index 97d0b68..de31022 100644
--- a/src/UI/Dialog/Action/Impls/ActionBackground.cs
+++ b/src/UI/Dialog/Action/Impls/ActionBackground.cs
@@ -1,81 +1,81 @@
-using Cthangover.Core.Factories.Impls;
-using Cthangover.Core.Scenes;
-using Cthangover.Mods.Dialog.Action;
-using Cthangover.Mods.Time;
-using Cthangover.Mods.Interface.View;
-using Godot;
-using Cthangover.Core.UI;
-using Cthangover.Mods.Interface.Base.Lists;
-using Cthangover.Core.UI.Dialog;
-using Cthangover.Core.UI.Event;
-using Cthangover.Core.UI.Executable;
-using Cthangover.Core.Scenarios;
-using Cthangover.Core.Interactive;
-
-namespace Cthangover.Mods.Dialog.Action.Impls
-{
- ///
- /// Sets the scene background on the ViewBox, including depth and albedo maps
- /// for the lighting system. Resolves textures through BackgroundFactory on
- /// construction (OnStartQueue) — depth and albedo are loaded from the same
- /// sprite ID with "_depth" / "_albedo" suffixes. Kills any in-progress
- /// background transition before setting the new texture. Textures are nulled
- /// after application to prevent stale reuse across dialog restarts.
- /// Sets SceneContextNode.LastBackgroundID so the scene manager can restore
- /// the correct background on scene revisit.
- ///
- public class ActionBackground : ActionCommand
- {
- /// Sprite resource ID resolved through on construction. Also loads depth (_depth) and albedo (_albedo) variants.
- public string SpriteID { get; set; }
- /// Direct texture override. When non-null, bypasses factory resolution. Set to null after application to prevent stale reuse.
- public Texture2D Texture { get; set; }
- /// Depth map for the lighting system, loaded from + "_depth".
- public Texture2D DepthTexture { get; set; }
- /// Albedo color map for the lighting system, loaded from + "_albedo".
- public Texture2D AlbedoTexture { get; set; }
-
- /// Preloads textures when the dialog queue is first created, resolving SpriteID through the factory.
- public override ConstructType ConstructType { get; set; } = ConstructType.OnStartQueue;
- /// Background is set imperatively — the dialog continues immediately.
- public override WaitType WaitType { get; set; } = WaitType.NoWait;
-
- /// Resolves plus its _depth/_albedo variants through the background factory.
- public override void DoConstruct()
- {
- base.DoConstruct();
- if (Texture == null && !string.IsNullOrEmpty(SpriteID))
- Texture = BackgroundFactory.Instance.Get(SpriteID);
- if (!string.IsNullOrEmpty(SpriteID))
- {
- DepthTexture = BackgroundFactory.Instance.Get(SpriteID + "_depth");
- AlbedoTexture = BackgroundFactory.Instance.Get(SpriteID + "_albedo");
- }
- }
-
- ///
- /// Applies the background to the ViewBox, kills any in-flight background
- /// transition and feeds the depth/albedo maps to the light controller.
- /// Consumed textures are nulled so a reused action cannot re-apply stale data.
- ///
- public override void DoRun(DialogRuntime runtime)
- {
- if (Texture == null)
- return;
-
- SceneContextNode.LastBackgroundID = SpriteID;
-
- var viewBox = Scene.Find("ViewBox");
- viewBox?.KillTransitionTween();
- viewBox?.SetBackgroundTexture(Texture);
-
- var controller = UiLightController.Instance;
- controller?.SetupDepthMap(DepthTexture);
- controller?.SetupAlbedoMap(AlbedoTexture);
-
- Texture = null;
- DepthTexture = null;
- AlbedoTexture = null;
- }
- }
-}
+using Cthangover.Core.Factories.Impls;
+using Cthangover.Core.Scenes;
+using Cthangover.Mods.Dialog.Action;
+using Cthangover.Mods.Time;
+using Cthangover.Mods.Interface.View;
+using Godot;
+using Cthangover.Core.UI;
+using Cthangover.Mods.Interface.Base.Lists;
+using Cthangover.Core.UI.Dialog;
+using Cthangover.Core.UI.Event;
+using Cthangover.Core.UI.Executable;
+using Cthangover.Core.Scenarios;
+using Cthangover.Core.Interactive;
+
+namespace Cthangover.Mods.Dialog.Action.Impls
+{
+ ///
+ /// Sets the scene background on the ViewBox, including depth and albedo maps
+ /// for the lighting system. Resolves textures through BackgroundFactory on
+ /// construction (OnStartQueue) — depth and albedo are loaded from the same
+ /// sprite ID with "_depth" / "_albedo" suffixes. Kills any in-progress
+ /// background transition before setting the new texture. Textures are nulled
+ /// after application to prevent stale reuse across dialog restarts.
+ /// Sets SceneContextNode.LastBackgroundID so the scene manager can restore
+ /// the correct background on scene revisit.
+ ///
+ public class ActionBackground : ActionCommand
+ {
+ /// Sprite resource ID resolved through on construction. Also loads depth (_depth) and albedo (_albedo) variants.
+ public string SpriteID { get; set; }
+ /// Direct texture override. When non-null, bypasses factory resolution. Set to null after application to prevent stale reuse.
+ public Texture2D Texture { get; set; }
+ /// Depth map for the lighting system, loaded from + "_depth".
+ public Texture2D DepthTexture { get; set; }
+ /// Albedo color map for the lighting system, loaded from + "_albedo".
+ public Texture2D AlbedoTexture { get; set; }
+
+ /// Preloads textures when the dialog queue is first created, resolving SpriteID through the factory.
+ public override ConstructType ConstructType { get; set; } = ConstructType.OnStartQueue;
+ /// Background is set imperatively — the dialog continues immediately.
+ public override WaitType WaitType { get; set; } = WaitType.NoWait;
+
+ /// Resolves plus its _depth/_albedo variants through the background factory.
+ public override void DoConstruct()
+ {
+ base.DoConstruct();
+ if (Texture == null && !string.IsNullOrEmpty(SpriteID))
+ Texture = BackgroundFactory.Instance.Get(SpriteID);
+ if (!string.IsNullOrEmpty(SpriteID))
+ {
+ DepthTexture = BackgroundFactory.Instance.Get(SpriteID + "_depth");
+ AlbedoTexture = BackgroundFactory.Instance.Get(SpriteID + "_albedo");
+ }
+ }
+
+ ///
+ /// Applies the background to the ViewBox, kills any in-flight background
+ /// transition and feeds the depth/albedo maps to the light controller.
+ /// Consumed textures are nulled so a reused action cannot re-apply stale data.
+ ///
+ public override void DoRun(DialogRuntime runtime)
+ {
+ if (Texture == null)
+ return;
+
+ SceneContextNode.LastBackgroundID = SpriteID;
+
+ var viewBox = Scene.Find("ViewBox");
+ viewBox?.KillTransitionTween();
+ viewBox?.SetBackgroundTexture(Texture);
+
+ var controller = UiLightController.Instance;
+ controller?.SetupDepthMap(DepthTexture);
+ controller?.SetupAlbedoMap(AlbedoTexture);
+
+ Texture = null;
+ DepthTexture = null;
+ AlbedoTexture = null;
+ }
+ }
+}
diff --git a/src/UI/Dialog/Action/Impls/ActionBackgroundColor.cs b/src/UI/Dialog/Action/Impls/ActionBackgroundColor.cs
index 559c9db..0bc9193 100644
--- a/src/UI/Dialog/Action/Impls/ActionBackgroundColor.cs
+++ b/src/UI/Dialog/Action/Impls/ActionBackgroundColor.cs
@@ -1,5 +1,5 @@
-using Cthangover.Mods.Dialog.Action;
-using Godot;
+using Cthangover.Mods.Dialog.Action;
+using Godot;
using Cthangover.Core.UI;
using Cthangover.Mods.Interface.Base.Lists;
using Cthangover.Core.UI.Dialog;
@@ -8,26 +8,26 @@ using Cthangover.Core.UI.Executable;
using Cthangover.Core.Scenarios;
using Cthangover.Core.Scenes;
using Cthangover.Core.Interactive;
-
-namespace Cthangover.Mods.Dialog.Action.Impls
-{
- ///
- /// Sets the background tint color. DoRun is currently a no-op — the Color
- /// property is stored for external systems to read. Likely intended as a
- /// declarative instruction consumed by a background-rendering node rather
- /// than applied imperatively.
- ///
- public class ActionBackgroundColor : ActionCommand
- {
- /// Target tint color. Stored declaratively — consumed by background-rendering nodes rather than applied imperatively here.
- public Color Color { get; set; } = Colors.Black;
-
- /// Imperative — the dialog continues immediately after this action is processed.
- public override WaitType WaitType { get; set; } = WaitType.NoWait;
-
- /// Deliberate no-op: the tint is declarative data for background-rendering nodes, not applied here.
- public override void DoRun(DialogRuntime runtime)
- {
- }
- }
-}
+
+namespace Cthangover.Mods.Dialog.Action.Impls
+{
+ ///
+ /// Sets the background tint color. DoRun is currently a no-op — the Color
+ /// property is stored for external systems to read. Likely intended as a
+ /// declarative instruction consumed by a background-rendering node rather
+ /// than applied imperatively.
+ ///
+ public class ActionBackgroundColor : ActionCommand
+ {
+ /// Target tint color. Stored declaratively — consumed by background-rendering nodes rather than applied imperatively here.
+ public Color Color { get; set; } = Colors.Black;
+
+ /// Imperative — the dialog continues immediately after this action is processed.
+ public override WaitType WaitType { get; set; } = WaitType.NoWait;
+
+ /// Deliberate no-op: the tint is declarative data for background-rendering nodes, not applied here.
+ public override void DoRun(DialogRuntime runtime)
+ {
+ }
+ }
+}
diff --git a/src/UI/Dialog/Action/Impls/ActionBackgroundShowHide.cs b/src/UI/Dialog/Action/Impls/ActionBackgroundShowHide.cs
index 99a46ca..c1918b2 100644
--- a/src/UI/Dialog/Action/Impls/ActionBackgroundShowHide.cs
+++ b/src/UI/Dialog/Action/Impls/ActionBackgroundShowHide.cs
@@ -13,10 +13,13 @@ namespace Cthangover.Mods.Dialog.Action.Impls
{
///
/// Tween-driven background fade in/out. Animates the modulate alpha of the
- /// dialog box's parent Control over Duration seconds. Creates and manages
- /// its own Tween internally, killing any previous tween before starting a
- /// new one to prevent overlap. Can optionally set WaitType to WaitTime with
- /// a matching duration so the dialog pauses during the transition.
+ /// dialog box's parent Control over Duration seconds. The fade belongs to the
+ /// background, not to the action: by default (NoWait) the runtime destructs the
+ /// action right after it runs, and the fade must go on. So the tween is shared
+ /// by all instances — a new fade kills the previous one to prevent overlap —
+ /// and destructing the action leaves it running; the tween is bound to the
+ /// background node and dies with it. Can optionally set WaitType to WaitTime
+ /// with a matching duration so the dialog pauses during the transition.
///
public class ActionBackgroundShowHide : ActionCommand
{
@@ -25,7 +28,7 @@ namespace Cthangover.Mods.Dialog.Action.Impls
/// Duration of the fade tween in seconds.
public float Duration { get; set; } = 1f;
- private Tween activeTween;
+ private static Tween activeTween;
/// Default NoWait — overridden by the DialogQueue builder if the wait flag is set, changing to WaitTime to pause during transition.
public override WaitType WaitType { get; set; } = WaitType.NoWait;
@@ -47,18 +50,12 @@ namespace Cthangover.Mods.Dialog.Action.Impls
}
}
- /// Kills the fade tween so a destructed action cannot keep animating.
- public override void DoDestruct()
- {
- base.DoDestruct();
- StopTween();
- }
-
- private void StopTween()
+ private static void StopTween()
{
if (activeTween != null)
{
- activeTween.Kill();
+ if (GodotObject.IsInstanceValid(activeTween))
+ activeTween.Kill();
activeTween = null;
}
}
diff --git a/src/UI/Dialog/Action/Impls/ActionDelay.cs b/src/UI/Dialog/Action/Impls/ActionDelay.cs
index 5e4018c..1111d1d 100644
--- a/src/UI/Dialog/Action/Impls/ActionDelay.cs
+++ b/src/UI/Dialog/Action/Impls/ActionDelay.cs
@@ -1,5 +1,5 @@
-using Cthangover.Mods.Dialog.Action;
-using Godot;
+using Cthangover.Mods.Dialog.Action;
+using Godot;
using Cthangover.Core.UI;
using Cthangover.Mods.Interface.Base.Lists;
using Cthangover.Core.UI.Dialog;
@@ -8,49 +8,49 @@ using Cthangover.Core.UI.Executable;
using Cthangover.Core.Scenarios;
using Cthangover.Core.Scenes;
using Cthangover.Core.Interactive;
-
-namespace Cthangover.Mods.Dialog.Action.Impls
-{
- ///
- /// Waits for a fixed duration. When HiddenMode is true, hides the dialog
- /// body for the duration (for dramatic pauses or cutscene beats). ShowText,
- /// if set, displays a temporary message during the wait. DoDestruct is a
- /// no-op because body visibility is implicitly restored on next Show.
- ///
- public class ActionDelay : ActionCommand
- {
- /// When true, hides the dialog body for the duration of the wait (dramatic pauses, cutscene beats).
- public bool HiddenMode { get; set; }
- /// Optional temporary text displayed while waiting. Set to null to keep the current dialog text.
- public string ShowText { get; set; }
-
- /// Pauses the dialog until elapses.
- public override WaitType WaitType { get; set; } = WaitType.WaitTime;
- /// Duration of the pause in seconds. Default 1.0.
- public override float WaitTime { get; set; } = 1f;
-
- /// Shows the optional and, in , hides the dialog body for the pause duration.
- public override void DoRun(DialogRuntime runtime)
- {
- var dialogBox = runtime.DialogBox;
- if (dialogBox == null)
- return;
-
- if (ShowText != null)
- dialogBox.SetText(ShowText);
-
- if (HiddenMode)
- dialogBox.Body.SetDeferred("visible", false);
- }
-
- /// No-op: the dialog body hidden by is restored implicitly by the next show.
- public override void DoDestruct()
- {
- base.DoDestruct();
- if (HiddenMode)
- {
- // Body restored on next Show
- }
- }
- }
-}
+
+namespace Cthangover.Mods.Dialog.Action.Impls
+{
+ ///
+ /// Waits for a fixed duration. When HiddenMode is true, hides the dialog
+ /// body for the duration (for dramatic pauses or cutscene beats). ShowText,
+ /// if set, displays a temporary message during the wait. DoDestruct is a
+ /// no-op because body visibility is implicitly restored on next Show.
+ ///
+ public class ActionDelay : ActionCommand
+ {
+ /// When true, hides the dialog body for the duration of the wait (dramatic pauses, cutscene beats).
+ public bool HiddenMode { get; set; }
+ /// Optional temporary text displayed while waiting. Set to null to keep the current dialog text.
+ public string ShowText { get; set; }
+
+ /// Pauses the dialog until elapses.
+ public override WaitType WaitType { get; set; } = WaitType.WaitTime;
+ /// Duration of the pause in seconds. Default 1.0.
+ public override float WaitTime { get; set; } = 1f;
+
+ /// Shows the optional and, in , hides the dialog body for the pause duration.
+ public override void DoRun(DialogRuntime runtime)
+ {
+ var dialogBox = runtime.DialogBox;
+ if (dialogBox == null)
+ return;
+
+ if (ShowText != null)
+ dialogBox.SetText(ShowText);
+
+ if (HiddenMode)
+ dialogBox.Body.SetDeferred("visible", false);
+ }
+
+ /// No-op: the dialog body hidden by is restored implicitly by the next show.
+ public override void DoDestruct()
+ {
+ base.DoDestruct();
+ if (HiddenMode)
+ {
+ // Body restored on next Show
+ }
+ }
+ }
+}
diff --git a/src/UI/Dialog/Action/Impls/ActionEffect.cs b/src/UI/Dialog/Action/Impls/ActionEffect.cs
index 06aac5a..49db142 100644
--- a/src/UI/Dialog/Action/Impls/ActionEffect.cs
+++ b/src/UI/Dialog/Action/Impls/ActionEffect.cs
@@ -9,57 +9,83 @@ using Cthangover.Core.UI.Executable;
using Cthangover.Core.Scenarios;
using Cthangover.Core.Scenes;
using Cthangover.Core.Interactive;
+using Cthangover.Mods.Interface.View;
namespace Cthangover.Mods.Dialog.Action.Impls
{
///
- /// Spawns a visual effect from the EffectFactory by ID. The spawned node
- /// persists until the action is destructed. Uses DestructType.OnDelayed
- /// because the effect must outlive the action's run — the effect node's own
- /// animation or lifetime controls when it disappears. DoRun is a no-op
- /// because construction handles spawning.
+ /// Spawns a visual effect from the EffectFactory by ID (or a ready
+ /// , bypassing the factory like ActionBackground.Texture).
+ /// The scene is instantiated on construction and put into the ViewBox's UI
+ /// layer (above all scene visuals, below the dialog) when the action runs; the
+ /// node persists until the action is destructed. Uses DestructType.OnDelayed
+ /// because the effect must outlive the action's run: the runtime frees it when
+ /// the dialog ends (End destructs every constructed action), unless the effect
+ /// node frees itself first.
///
public class ActionEffect : ActionCommand
{
/// Identifier resolved through to a PackedScene. The spawned node persists until destructed.
public string EffectID { get; set; }
+ /// Effect scene to spawn instead of resolving ; null resolves through the factory.
+ public PackedScene Prefab { get; set; }
+
private Node spawnedEffect;
/// The dialog continues immediately — the spawned effect plays independently.
public override WaitType WaitType { get; set; } = WaitType.NoWait;
- /// OnDelayed ensures the effect node outlives the action's run phase. Destruct is called manually when the effect finishes.
+ /// OnDelayed keeps the effect node alive past the action's run; it is freed when the dialog ends.
public override DestructType DestructType { get; set; } = DestructType.OnDelayed;
- /// Resolves through and instantiates the effect scene.
+ /// Resolves the effect scene ( or through ) and instantiates it.
public override void DoConstruct()
{
base.DoConstruct();
- if (string.IsNullOrEmpty(EffectID))
- return;
- var packedScene = EffectFactory.Instance.Get(EffectID);
+ var packedScene = Prefab;
if (packedScene == null)
{
- GameLogger.Log("DIALOG", $"ActionEffect: effect '{EffectID}' not found", LogLevel.Error);
- return;
+ if (string.IsNullOrEmpty(EffectID))
+ return;
+
+ packedScene = EffectFactory.Instance.Get(EffectID);
+ if (packedScene == null)
+ {
+ GameLogger.Log("DIALOG", $"ActionEffect: effect '{EffectID}' not found", LogLevel.Error);
+ return;
+ }
}
spawnedEffect = packedScene.Instantiate();
}
- /// No-op: spawning happens during construction so the effect is ready before the queue reaches this point.
+ /// Shows the instantiated effect: adds it to the ViewBox UI layer. Without a ViewBox the effect is dropped with an error.
public override void DoRun(DialogRuntime runtime)
{
+ if (spawnedEffect == null || spawnedEffect.IsInsideTree())
+ return;
+
+ var layer = Scene.Find("ViewBox")?.GetInteractiveLayer("ui");
+ if (layer == null)
+ {
+ GameLogger.Log("DIALOG", $"ActionEffect: no ViewBox to show effect '{EffectID}'", LogLevel.Error);
+ spawnedEffect.QueueFree();
+ spawnedEffect = null;
+ return;
+ }
+
+ layer.AddChild(spawnedEffect);
}
- /// Frees the spawned effect node; paired with the OnDelayed destruct so effects outlive the action's run.
+ /// Frees the spawned effect node (unless it already freed itself); paired with the OnDelayed destruct so effects outlive the action's run.
public override void DoDestruct()
{
base.DoDestruct();
if (spawnedEffect != null)
{
- spawnedEffect.QueueFree();
+ if (GodotObject.IsInstanceValid(spawnedEffect))
+ spawnedEffect.QueueFree();
spawnedEffect = null;
}
}
diff --git a/src/UI/Dialog/Action/Impls/ActionEmpty.cs b/src/UI/Dialog/Action/Impls/ActionEmpty.cs
index e98c917..e2299f7 100644
--- a/src/UI/Dialog/Action/Impls/ActionEmpty.cs
+++ b/src/UI/Dialog/Action/Impls/ActionEmpty.cs
@@ -1,5 +1,5 @@
-using Cthangover.Mods.Dialog.Action;
-using Godot;
+using Cthangover.Mods.Dialog.Action;
+using Godot;
using Cthangover.Core.UI;
using Cthangover.Mods.Interface.Base.Lists;
using Cthangover.Core.UI.Dialog;
@@ -8,22 +8,22 @@ using Cthangover.Core.UI.Executable;
using Cthangover.Core.Scenarios;
using Cthangover.Core.Scenes;
using Cthangover.Core.Interactive;
-
-namespace Cthangover.Mods.Dialog.Action.Impls
-{
- ///
- /// No-op placeholder action. Used as a labeled jump target (Point) for GoTo
- /// commands — provides an addressable marker in the dialog sequence without
- /// any visible effect. NoWait so the dialog skips through labels instantly.
- ///
- public class ActionEmpty : ActionCommand
- {
- /// Label actions skip immediately so the dialog doesn't pause on markers.
- public override WaitType WaitType { get; set; } = WaitType.NoWait;
-
- /// No-op: the action exists only to carry the jump-target ID.
- public override void DoRun(DialogRuntime runtime)
- {
- }
- }
-}
+
+namespace Cthangover.Mods.Dialog.Action.Impls
+{
+ ///
+ /// No-op placeholder action. Used as a labeled jump target (Point) for GoTo
+ /// commands — provides an addressable marker in the dialog sequence without
+ /// any visible effect. NoWait so the dialog skips through labels instantly.
+ ///
+ public class ActionEmpty : ActionCommand
+ {
+ /// Label actions skip immediately so the dialog doesn't pause on markers.
+ public override WaitType WaitType { get; set; } = WaitType.NoWait;
+
+ /// No-op: the action exists only to carry the jump-target ID.
+ public override void DoRun(DialogRuntime runtime)
+ {
+ }
+ }
+}
diff --git a/src/UI/Dialog/Action/Impls/ActionEnd.cs b/src/UI/Dialog/Action/Impls/ActionEnd.cs
index dfc1f08..0ce8895 100644
--- a/src/UI/Dialog/Action/Impls/ActionEnd.cs
+++ b/src/UI/Dialog/Action/Impls/ActionEnd.cs
@@ -1,5 +1,5 @@
-using Cthangover.Mods.Dialog.Action;
-using Godot;
+using Cthangover.Mods.Dialog.Action;
+using Godot;
using Cthangover.Core.UI;
using Cthangover.Mods.Interface.Base.Lists;
using Cthangover.Core.UI.Dialog;
@@ -8,23 +8,23 @@ using Cthangover.Core.UI.Executable;
using Cthangover.Core.Scenarios;
using Cthangover.Core.Scenes;
using Cthangover.Core.Interactive;
-
-namespace Cthangover.Mods.Dialog.Action.Impls
-{
- ///
- /// Terminates the dialog queue immediately. Calls Runtime.End() which
- /// destructs all remaining actions, processes the cleanup queue, hides
- /// the dialog box, and fires the dialog-end event.
- ///
- public class ActionEnd : ActionCommand
- {
- /// Ends the dialog queue immediately — no pause, no return.
- public override WaitType WaitType { get; set; } = WaitType.NoWait;
-
- /// Terminates the queue via — destructs remainders, runs cleanup, hides the box, fires the end event.
- public override void DoRun(DialogRuntime runtime)
- {
- runtime.End();
- }
- }
-}
+
+namespace Cthangover.Mods.Dialog.Action.Impls
+{
+ ///
+ /// Terminates the dialog queue immediately. Calls Runtime.End() which
+ /// destructs all remaining actions, processes the cleanup queue, hides
+ /// the dialog box, and fires the dialog-end event.
+ ///
+ public class ActionEnd : ActionCommand
+ {
+ /// Ends the dialog queue immediately — no pause, no return.
+ public override WaitType WaitType { get; set; } = WaitType.NoWait;
+
+ /// Terminates the queue via — destructs remainders, runs cleanup, hides the box, fires the end event.
+ public override void DoRun(DialogRuntime runtime)
+ {
+ runtime.End();
+ }
+ }
+}
diff --git a/src/UI/Dialog/Action/Impls/ActionForeground.cs b/src/UI/Dialog/Action/Impls/ActionForeground.cs
index 5c13896..d9d2faa 100644
--- a/src/UI/Dialog/Action/Impls/ActionForeground.cs
+++ b/src/UI/Dialog/Action/Impls/ActionForeground.cs
@@ -38,9 +38,10 @@ namespace Cthangover.Mods.Dialog.Action.Impls
Texture = BackgroundFactory.Instance.Get(SpriteID);
}
- /// Applies the preloaded texture to the ViewBox foreground, then nulls it so a reused action cannot re-apply stale data.
+ /// Stops a running foreground animation (it would overwrite the picture), applies the preloaded texture to the ViewBox foreground, then nulls it so a reused action cannot re-apply stale data.
public override void DoRun(DialogRuntime runtime)
{
+ ActionAnimatedForeground.StopActive();
var viewBox = Scene.Find("ViewBox");
viewBox?.SetForegroundTexture(Texture);
Texture = null;
diff --git a/src/UI/Dialog/Action/Impls/ActionGoTo.cs b/src/UI/Dialog/Action/Impls/ActionGoTo.cs
index 24ac163..4c9e58f 100644
--- a/src/UI/Dialog/Action/Impls/ActionGoTo.cs
+++ b/src/UI/Dialog/Action/Impls/ActionGoTo.cs
@@ -1,5 +1,5 @@
-using Cthangover.Mods.Dialog.Action;
-using Godot;
+using Cthangover.Mods.Dialog.Action;
+using Godot;
using Cthangover.Core.UI;
using Cthangover.Mods.Interface.Base.Lists;
using Cthangover.Core.UI.Dialog;
@@ -8,26 +8,26 @@ using Cthangover.Core.UI.Executable;
using Cthangover.Core.Scenarios;
using Cthangover.Core.Scenes;
using Cthangover.Core.Interactive;
-
-namespace Cthangover.Mods.Dialog.Action.Impls
-{
- ///
- /// Unconditional jump to a labeled action in the dialog queue. NoWait so
- /// the jump happens transparently. The target is resolved by action ID
- /// (set via ActionCommand.SetID or the Point()/Empty() fluent methods).
- ///
- public class ActionGoTo : ActionCommand
- {
- /// Target action ID to jump to. Resolved by linear search through the dialog queue.
- public string GoTo { get; set; }
-
- /// Jump happens transparently — the dialog continues from the target without pausing.
- public override WaitType WaitType { get; set; } = WaitType.NoWait;
-
- /// Jumps the queue index to the action whose ID matches .
- public override void DoRun(DialogRuntime runtime)
- {
- runtime.TryGoTo(GoTo);
- }
- }
-}
+
+namespace Cthangover.Mods.Dialog.Action.Impls
+{
+ ///
+ /// Unconditional jump to a labeled action in the dialog queue. NoWait so
+ /// the jump happens transparently. The target is resolved by action ID
+ /// (set via ActionCommand.SetID or the Point()/Empty() fluent methods).
+ ///
+ public class ActionGoTo : ActionCommand
+ {
+ /// Target action ID to jump to. Resolved by linear search through the dialog queue.
+ public string GoTo { get; set; }
+
+ /// Jump happens transparently — the dialog continues from the target without pausing.
+ public override WaitType WaitType { get; set; } = WaitType.NoWait;
+
+ /// Jumps the queue index to the action whose ID matches .
+ public override void DoRun(DialogRuntime runtime)
+ {
+ runtime.TryGoTo(GoTo);
+ }
+ }
+}
diff --git a/src/UI/Dialog/Action/Impls/ActionHideDialog.cs b/src/UI/Dialog/Action/Impls/ActionHideDialog.cs
index 9c86ae9..255e93d 100644
--- a/src/UI/Dialog/Action/Impls/ActionHideDialog.cs
+++ b/src/UI/Dialog/Action/Impls/ActionHideDialog.cs
@@ -1,24 +1,24 @@
-
-namespace Cthangover.Mods.Dialog.Action.Impls
-{
- ///
- /// Hides the dialog box. Used in cutscenes where UI chrome should be
- /// temporarily hidden while animations or background transitions play.
- /// Paired with ActionShowDialog to restore visibility.
- ///
- public class ActionHideDialog : ActionCommand
- {
- /// Hides the dialog immediately — the queue continues without pause.
- public override WaitType WaitType { get; set; } = WaitType.NoWait;
-
- /// Hides the dialog box chrome while keeping the queue running.
- public override void DoRun(DialogRuntime runtime)
- {
- var dialogBox = runtime.DialogBox;
- if (dialogBox == null)
- return;
-
- dialogBox.Visible = false;
- }
- }
-}
+
+namespace Cthangover.Mods.Dialog.Action.Impls
+{
+ ///
+ /// Hides the dialog box. Used in cutscenes where UI chrome should be
+ /// temporarily hidden while animations or background transitions play.
+ /// Paired with ActionShowDialog to restore visibility.
+ ///
+ public class ActionHideDialog : ActionCommand
+ {
+ /// Hides the dialog immediately — the queue continues without pause.
+ public override WaitType WaitType { get; set; } = WaitType.NoWait;
+
+ /// Hides the dialog box chrome while keeping the queue running.
+ public override void DoRun(DialogRuntime runtime)
+ {
+ var dialogBox = runtime.DialogBox;
+ if (dialogBox == null)
+ return;
+
+ dialogBox.Visible = false;
+ }
+ }
+}
diff --git a/src/UI/Dialog/Action/Impls/ActionIf.cs b/src/UI/Dialog/Action/Impls/ActionIf.cs
index e011ddf..0ed9e1d 100644
--- a/src/UI/Dialog/Action/Impls/ActionIf.cs
+++ b/src/UI/Dialog/Action/Impls/ActionIf.cs
@@ -14,7 +14,10 @@ namespace Cthangover.Mods.Dialog.Action.Impls
///
/// Conditional branch: evaluates a Func<bool> and jumps to TrueGoTo or
/// FalseGoTo based on the result. If the chosen target is null/empty, falls
- /// through to Next(). NoWait so branching is transparent to the player.
+ /// through to the next action. NoWait so branching is transparent to the player:
+ /// the runtime itself advances past a NoWait action, so the branch never calls
+ /// — doing it here as well skipped the action
+ /// after the if without running it.
///
public class ActionIf : ActionCommand
{
@@ -34,7 +37,6 @@ namespace Cthangover.Mods.Dialog.Action.Impls
if (Condition == null)
{
GameLogger.Log("DIALOG", "ActionIf: Condition delegate is null", LogLevel.Error);
- runtime.Next();
return;
}
@@ -43,8 +45,6 @@ namespace Cthangover.Mods.Dialog.Action.Impls
if (!string.IsNullOrEmpty(goTo))
runtime.TryGoTo(goTo);
- else
- runtime.Next();
}
}
}
diff --git a/src/UI/Dialog/Action/Impls/ActionLightSet.cs b/src/UI/Dialog/Action/Impls/ActionLightSet.cs
index 4f34a6a..6c2916e 100644
--- a/src/UI/Dialog/Action/Impls/ActionLightSet.cs
+++ b/src/UI/Dialog/Action/Impls/ActionLightSet.cs
@@ -1,40 +1,40 @@
-using Cthangover.Mods.Time;
-using Cthangover.Core.UI;
-using Cthangover.Mods.Interface.Base.Lists;
-using Cthangover.Core.UI.Dialog;
-using Cthangover.Core.UI.Event;
-using Cthangover.Core.UI.Executable;
-using Cthangover.Core.Scenarios;
-using Cthangover.Core.Interactive;
-
-namespace Cthangover.Mods.Dialog.Action.Impls
-{
- ///
- /// Applies static lights from a JSON string to the UiLightController.
- /// An empty or null string clears all static lights. The JSON is deserialized
- /// into LightDef objects with viewport-relative coordinates, which are then
- /// converted to pixel positions. Supports up to 10 static lights plus the
- /// player's dynamic lamp.
- ///
- public class ActionLightSet : ActionCommand
- {
- /// JSON string defining static light positions and properties. Empty or null clears all static lights.
- public string LightsJson { get; set; }
-
- /// Lights are set imperatively — the dialog continues immediately.
- public override WaitType WaitType { get; set; } = WaitType.NoWait;
-
- /// Clears static lights when is empty, otherwise parses and applies the light definitions to the scene's .
- public override void DoRun(DialogRuntime runtime)
- {
- var controller = Scene.Find("Lights");
- if (controller == null)
- return;
-
- if (string.IsNullOrEmpty(LightsJson))
- controller.ClearStaticLights();
- else
- controller.SetStaticLights(LightsJson);
- }
- }
-}
+using Cthangover.Mods.Time;
+using Cthangover.Core.UI;
+using Cthangover.Mods.Interface.Base.Lists;
+using Cthangover.Core.UI.Dialog;
+using Cthangover.Core.UI.Event;
+using Cthangover.Core.UI.Executable;
+using Cthangover.Core.Scenarios;
+using Cthangover.Core.Interactive;
+
+namespace Cthangover.Mods.Dialog.Action.Impls
+{
+ ///
+ /// Applies static lights from a JSON string to the UiLightController.
+ /// An empty or null string clears all static lights. The JSON is deserialized
+ /// into LightDef objects with viewport-relative coordinates, which are then
+ /// converted to pixel positions. Supports up to 10 static lights plus the
+ /// player's dynamic lamp.
+ ///
+ public class ActionLightSet : ActionCommand
+ {
+ /// JSON string defining static light positions and properties. Empty or null clears all static lights.
+ public string LightsJson { get; set; }
+
+ /// Lights are set imperatively — the dialog continues immediately.
+ public override WaitType WaitType { get; set; } = WaitType.NoWait;
+
+ /// Clears static lights when is empty, otherwise parses and applies the light definitions to the scene's .
+ public override void DoRun(DialogRuntime runtime)
+ {
+ var controller = Scene.Find("Lights");
+ if (controller == null)
+ return;
+
+ if (string.IsNullOrEmpty(LightsJson))
+ controller.ClearStaticLights();
+ else
+ controller.SetStaticLights(LightsJson);
+ }
+ }
+}
diff --git a/src/UI/Dialog/Action/Impls/ActionLightUseTime.cs b/src/UI/Dialog/Action/Impls/ActionLightUseTime.cs
index 31f8cc7..f2e8988 100644
--- a/src/UI/Dialog/Action/Impls/ActionLightUseTime.cs
+++ b/src/UI/Dialog/Action/Impls/ActionLightUseTime.cs
@@ -1,33 +1,33 @@
-using Cthangover.Mods.Dialog.Action;
-using Cthangover.Mods.Time;
-using Cthangover.Core.UI;
-using Cthangover.Mods.Interface.Base.Lists;
-using Cthangover.Core.UI.Dialog;
-using Cthangover.Core.UI.Event;
-using Cthangover.Core.UI.Executable;
-using Cthangover.Core.Scenarios;
-using Cthangover.Core.Interactive;
-
-namespace Cthangover.Mods.Dialog.Action.Impls
-{
- ///
- /// Toggles the light controller's time-of-day mode. When UseTime is false,
- /// static lighting is used; when true, lighting responds to the in-game clock.
- ///
- public class ActionLightUseTime : ActionCommand
- {
- /// True = lights respond to in-game time of day; false = static lighting mode.
- public bool UseTime { get; set; } = true;
-
- /// Time mode toggle is instant — the dialog continues without pause.
- public override WaitType WaitType { get; set; } = WaitType.NoWait;
-
- /// Switches the scene's between time-of-day lighting and static lights.
- public override void DoRun(DialogRuntime runtime)
- {
- var controller = Scene.Find("Lights");
- if (controller != null)
- controller.IsUseLight = UseTime;
- }
- }
-}
+using Cthangover.Mods.Dialog.Action;
+using Cthangover.Mods.Time;
+using Cthangover.Core.UI;
+using Cthangover.Mods.Interface.Base.Lists;
+using Cthangover.Core.UI.Dialog;
+using Cthangover.Core.UI.Event;
+using Cthangover.Core.UI.Executable;
+using Cthangover.Core.Scenarios;
+using Cthangover.Core.Interactive;
+
+namespace Cthangover.Mods.Dialog.Action.Impls
+{
+ ///
+ /// Toggles the light controller's time-of-day mode. When UseTime is false,
+ /// static lighting is used; when true, lighting responds to the in-game clock.
+ ///
+ public class ActionLightUseTime : ActionCommand
+ {
+ /// True = lights respond to in-game time of day; false = static lighting mode.
+ public bool UseTime { get; set; } = true;
+
+ /// Time mode toggle is instant — the dialog continues without pause.
+ public override WaitType WaitType { get; set; } = WaitType.NoWait;
+
+ /// Switches the scene's between time-of-day lighting and static lights.
+ public override void DoRun(DialogRuntime runtime)
+ {
+ var controller = Scene.Find("Lights");
+ if (controller != null)
+ controller.IsUseLight = UseTime;
+ }
+ }
+}
diff --git a/src/UI/Dialog/Action/Impls/ActionMusic.cs b/src/UI/Dialog/Action/Impls/ActionMusic.cs
index 4ada36d..b666403 100644
--- a/src/UI/Dialog/Action/Impls/ActionMusic.cs
+++ b/src/UI/Dialog/Action/Impls/ActionMusic.cs
@@ -1,37 +1,37 @@
-using Cthangover.Core.Audio;
-using Godot;
-using Cthangover.Core.UI;
-using Cthangover.Mods.Interface.Base.Lists;
-using Cthangover.Core.UI.Dialog;
-using Cthangover.Core.UI.Event;
-using Cthangover.Core.UI.Executable;
-using Cthangover.Core.Scenarios;
-using Cthangover.Core.Interactive;
-
-namespace Cthangover.Mods.Dialog.Action.Impls
-{
- ///
- /// Starts background music playback via IAudioService. Uses MusicType.Ambient
- /// so the music layer is treated as atmospheric background rather than UI
- /// feedback. If the same music track is already playing, the audio service handles
- /// deduplication.
- ///
- public class ActionMusic : ActionCommand
- {
- /// Music track identifier resolved through the audio system. Empty strings are silently ignored.
- public string Music { get; set; }
-
- /// Music starts playing in the background — the dialog continues immediately.
- public override WaitType WaitType { get; set; } = WaitType.NoWait;
-
- /// Starts the named track as ambient background music through ; empty names are ignored.
- public override void DoRun(DialogRuntime runtime)
- {
- if (string.IsNullOrEmpty(Music))
- return;
-
- var audioService = Scene.Find("AudioService") as IAudioService;
- audioService?.PlayMusic(Music, MusicType.Ambient);
- }
- }
-}
+using Cthangover.Core.Audio;
+using Godot;
+using Cthangover.Core.UI;
+using Cthangover.Mods.Interface.Base.Lists;
+using Cthangover.Core.UI.Dialog;
+using Cthangover.Core.UI.Event;
+using Cthangover.Core.UI.Executable;
+using Cthangover.Core.Scenarios;
+using Cthangover.Core.Interactive;
+
+namespace Cthangover.Mods.Dialog.Action.Impls
+{
+ ///
+ /// Starts background music playback via IAudioService. Uses MusicType.Ambient
+ /// so the music layer is treated as atmospheric background rather than UI
+ /// feedback. If the same music track is already playing, the audio service handles
+ /// deduplication.
+ ///
+ public class ActionMusic : ActionCommand
+ {
+ /// Music track identifier resolved through the audio system. Empty strings are silently ignored.
+ public string Music { get; set; }
+
+ /// Music starts playing in the background — the dialog continues immediately.
+ public override WaitType WaitType { get; set; } = WaitType.NoWait;
+
+ /// Starts the named track as ambient background music through ; empty names are ignored.
+ public override void DoRun(DialogRuntime runtime)
+ {
+ if (string.IsNullOrEmpty(Music))
+ return;
+
+ var audioService = Scene.Find("AudioService") as IAudioService;
+ audioService?.PlayMusic(Music, MusicType.Ambient);
+ }
+ }
+}
diff --git a/src/UI/Dialog/Action/Impls/ActionMusicPause.cs b/src/UI/Dialog/Action/Impls/ActionMusicPause.cs
index 2b75a55..b1beffd 100644
--- a/src/UI/Dialog/Action/Impls/ActionMusicPause.cs
+++ b/src/UI/Dialog/Action/Impls/ActionMusicPause.cs
@@ -1,29 +1,29 @@
-using Cthangover.Core.Audio;
-using Godot;
-using Cthangover.Core.UI;
-using Cthangover.Mods.Interface.Base.Lists;
-using Cthangover.Core.UI.Dialog;
-using Cthangover.Core.UI.Event;
-using Cthangover.Core.UI.Executable;
-using Cthangover.Core.Scenarios;
-using Cthangover.Core.Interactive;
-
-namespace Cthangover.Mods.Dialog.Action.Impls
-{
- ///
- /// Pauses background music playback via IAudioService without stopping the
- /// track. Can be resumed with ActionMusicPlay.
- ///
- public class ActionMusicPause : ActionCommand
- {
- /// Pause is instant — the dialog continues without waiting for audio acknowledgment.
- public override WaitType WaitType { get; set; } = WaitType.NoWait;
-
- /// Pauses the current music track without stopping it, so can resume it.
- public override void DoRun(DialogRuntime runtime)
- {
- var audioService = Scene.Find("AudioService") as IAudioService;
- audioService?.PauseMusic(true);
- }
- }
-}
+using Cthangover.Core.Audio;
+using Godot;
+using Cthangover.Core.UI;
+using Cthangover.Mods.Interface.Base.Lists;
+using Cthangover.Core.UI.Dialog;
+using Cthangover.Core.UI.Event;
+using Cthangover.Core.UI.Executable;
+using Cthangover.Core.Scenarios;
+using Cthangover.Core.Interactive;
+
+namespace Cthangover.Mods.Dialog.Action.Impls
+{
+ ///
+ /// Pauses background music playback via IAudioService without stopping the
+ /// track. Can be resumed with ActionMusicPlay.
+ ///
+ public class ActionMusicPause : ActionCommand
+ {
+ /// Pause is instant — the dialog continues without waiting for audio acknowledgment.
+ public override WaitType WaitType { get; set; } = WaitType.NoWait;
+
+ /// Pauses the current music track without stopping it, so can resume it.
+ public override void DoRun(DialogRuntime runtime)
+ {
+ var audioService = Scene.Find("AudioService") as IAudioService;
+ audioService?.PauseMusic(true);
+ }
+ }
+}
diff --git a/src/UI/Dialog/Action/Impls/ActionMusicPlay.cs b/src/UI/Dialog/Action/Impls/ActionMusicPlay.cs
index acbbc02..deda6c3 100644
--- a/src/UI/Dialog/Action/Impls/ActionMusicPlay.cs
+++ b/src/UI/Dialog/Action/Impls/ActionMusicPlay.cs
@@ -1,28 +1,28 @@
-using Cthangover.Core.Audio;
-using Godot;
-using Cthangover.Core.UI;
-using Cthangover.Mods.Interface.Base.Lists;
-using Cthangover.Core.UI.Dialog;
-using Cthangover.Core.UI.Event;
-using Cthangover.Core.UI.Executable;
-using Cthangover.Core.Scenarios;
-using Cthangover.Core.Interactive;
-
-namespace Cthangover.Mods.Dialog.Action.Impls
-{
- ///
- /// Resumes paused background music. Paired with ActionMusicPause.
- ///
- public class ActionMusicPlay : ActionCommand
- {
- /// Resume is instant — the dialog continues without waiting for audio.
- public override WaitType WaitType { get; set; } = WaitType.NoWait;
-
- /// Resumes music paused by .
- public override void DoRun(DialogRuntime runtime)
- {
- var audioService = Scene.Find("AudioService") as IAudioService;
- audioService?.PauseMusic(false);
- }
- }
-}
+using Cthangover.Core.Audio;
+using Godot;
+using Cthangover.Core.UI;
+using Cthangover.Mods.Interface.Base.Lists;
+using Cthangover.Core.UI.Dialog;
+using Cthangover.Core.UI.Event;
+using Cthangover.Core.UI.Executable;
+using Cthangover.Core.Scenarios;
+using Cthangover.Core.Interactive;
+
+namespace Cthangover.Mods.Dialog.Action.Impls
+{
+ ///
+ /// Resumes paused background music. Paired with ActionMusicPause.
+ ///
+ public class ActionMusicPlay : ActionCommand
+ {
+ /// Resume is instant — the dialog continues without waiting for audio.
+ public override WaitType WaitType { get; set; } = WaitType.NoWait;
+
+ /// Resumes music paused by .
+ public override void DoRun(DialogRuntime runtime)
+ {
+ var audioService = Scene.Find("AudioService") as IAudioService;
+ audioService?.PauseMusic(false);
+ }
+ }
+}
diff --git a/src/UI/Dialog/Action/Impls/ActionRun.cs b/src/UI/Dialog/Action/Impls/ActionRun.cs
index 681a9aa..730e991 100644
--- a/src/UI/Dialog/Action/Impls/ActionRun.cs
+++ b/src/UI/Dialog/Action/Impls/ActionRun.cs
@@ -1,4 +1,4 @@
-using Cthangover.Core.Utils;
+using Cthangover.Core.Utils;
using Cthangover.Core.UI;
using Cthangover.Mods.Interface.Base.Lists;
using Cthangover.Core.UI.Dialog;
@@ -7,33 +7,33 @@ using Cthangover.Core.UI.Executable;
using Cthangover.Core.Scenarios;
using Cthangover.Core.Scenes;
using Cthangover.Core.Interactive;
-
-namespace Cthangover.Mods.Dialog.Action.Impls
-{
- ///
- /// Executes an arbitrary Action delegate inside the dialog queue.
- /// Allows imperative code injection (e.g. quest state changes, UI toggles)
- /// without creating a dedicated action class. NoWait so the queue advances
- /// immediately after the delegate returns.
- ///
- public class ActionRun : ActionCommand
- {
- /// Arbitrary delegate executed during dialog sequencing. Null delegates are logged and skipped.
- public System.Action Executable { get; set; }
-
- /// The delegate runs and the dialog advances — no pausing.
- public override WaitType WaitType { get; set; } = WaitType.NoWait;
-
- /// Invokes ; a null delegate is logged and skipped so the queue keeps flowing.
- public override void DoRun(DialogRuntime runtime)
- {
- if (Executable == null)
- {
- GameLogger.Log("DIALOG", "ActionRun: Executable delegate is null", LogLevel.Error);
- return;
- }
-
- Executable();
- }
- }
-}
+
+namespace Cthangover.Mods.Dialog.Action.Impls
+{
+ ///
+ /// Executes an arbitrary Action delegate inside the dialog queue.
+ /// Allows imperative code injection (e.g. quest state changes, UI toggles)
+ /// without creating a dedicated action class. NoWait so the queue advances
+ /// immediately after the delegate returns.
+ ///
+ public class ActionRun : ActionCommand
+ {
+ /// Arbitrary delegate executed during dialog sequencing. Null delegates are logged and skipped.
+ public System.Action Executable { get; set; }
+
+ /// The delegate runs and the dialog advances — no pausing.
+ public override WaitType WaitType { get; set; } = WaitType.NoWait;
+
+ /// Invokes ; a null delegate is logged and skipped so the queue keeps flowing.
+ public override void DoRun(DialogRuntime runtime)
+ {
+ if (Executable == null)
+ {
+ GameLogger.Log("DIALOG", "ActionRun: Executable delegate is null", LogLevel.Error);
+ return;
+ }
+
+ Executable();
+ }
+ }
+}
diff --git a/src/UI/Dialog/Action/Impls/ActionSetFlag.cs b/src/UI/Dialog/Action/Impls/ActionSetFlag.cs
new file mode 100644
index 0000000..1ae97fa
--- /dev/null
+++ b/src/UI/Dialog/Action/Impls/ActionSetFlag.cs
@@ -0,0 +1,25 @@
+using Cthangover.Core.Scenes;
+
+namespace Cthangover.Mods.Dialog.Action.Impls
+{
+ ///
+ /// Sets a story flag of the running game ().
+ /// Unlike , whose variables end with the dialog, a
+ /// flag is game state: it is saved, read by scenario conditions
+ /// (condition: door == "open") and its change rescans the scene's
+ /// event chain, so a scenario waiting for it can start right after.
+ ///
+ public class ActionSetFlag : ActionCommand
+ {
+ /// Flag name, as used in conditions.
+ public string Name { get; set; }
+ /// Flag value; numeric values compare as integers in conditions.
+ public string Value { get; set; }
+
+ /// Setting a flag is instant — the dialog continues without pause.
+ public override WaitType WaitType { get; set; } = WaitType.NoWait;
+
+ ///
+ public override void DoRun(DialogRuntime runtime) => ScenarioCondition.SetFlag(Name, Value ?? string.Empty);
+ }
+}
diff --git a/src/UI/Dialog/Action/Impls/ActionSwitchScene.cs b/src/UI/Dialog/Action/Impls/ActionSwitchScene.cs
index d652da3..bfd7e8b 100644
--- a/src/UI/Dialog/Action/Impls/ActionSwitchScene.cs
+++ b/src/UI/Dialog/Action/Impls/ActionSwitchScene.cs
@@ -35,7 +35,7 @@ namespace Cthangover.Mods.Dialog.Action.Impls
/// Scene switch is deferred — the dialog ends immediately with no return.
public override WaitType WaitType { get; set; } = WaitType.NoWait;
- /// Marks the dialog locker as OneRun and defers SceneManager.SwitchScene so the tree is not mutated mid-queue.
+ /// Defers SceneManager.SwitchScene so the tree is not mutated mid-queue; the owning scenario is not forced into OneRun.
public override void DoRun(DialogRuntime runtime)
{
if (string.IsNullOrEmpty(SceneName))
diff --git a/src/UI/Dialog/Action/Impls/ActionTimedUpdate.cs b/src/UI/Dialog/Action/Impls/ActionTimedUpdate.cs
index 812632d..6345d84 100644
--- a/src/UI/Dialog/Action/Impls/ActionTimedUpdate.cs
+++ b/src/UI/Dialog/Action/Impls/ActionTimedUpdate.cs
@@ -14,10 +14,12 @@ namespace Cthangover.Mods.Dialog.Action.Impls
/// subscriber and fires OnUpdateCallback(float t) each frame with a normalized
/// progress value (0→1) over WaitTime seconds. WaitType is WaitEvent so the
/// dialog runtime pauses until elapsed >= WaitTime, at which point the action
- /// advances the runtime directly (event-driven — no DialogBox poll) and
- /// destructs itself. OnStartCallback/OnFinishCallback bracket the animation
- /// for setup/teardown. Fixed 60fps step for deterministic progress regardless
- /// of actual frame rate.
+ /// completes itself through (event-driven —
+ /// no DialogBox poll) and the box advances the queue, scheduling the timer of a
+ /// following delay like any other advance. OnStartCallback/OnFinishCallback
+ /// bracket the animation for setup/teardown. Progress follows the controller's
+ /// process delta, so the timeline lasts WaitTime seconds of game time at any frame
+ /// rate (and scales with Engine.TimeScale exactly like delay timers).
///
public class ActionTimedUpdate : ActionCommand, IOnUpdateEvent
{
@@ -31,7 +33,7 @@ namespace Cthangover.Mods.Dialog.Action.Impls
/// Fires once when the elapsed time exceeds or when destructed.
public System.Action OnFinishCallback { get; set; }
- /// Pauses the dialog until the elapsed time meets WaitTime — the action then advances the runtime itself.
+ /// Pauses the dialog until the elapsed time meets WaitTime — the action then completes itself.
public override WaitType WaitType { get; set; } = WaitType.WaitEvent;
/// Total duration in seconds over which receives normalized progress.
public override float WaitTime { get; set; } = 1f;
@@ -39,12 +41,13 @@ namespace Cthangover.Mods.Dialog.Action.Impls
private float elapsed;
private bool isRunning;
private DialogRuntime runtime;
+ private SceneEventController controller;
/// Registers this action as an listener on the scene's event controller.
public override void DoConstruct()
{
base.DoConstruct();
- var controller = Scene.Find("EventController");
+ controller = Scene.Find("EventController");
controller?.AddUpdateEventListener(this);
}
@@ -63,8 +66,8 @@ namespace Cthangover.Mods.Dialog.Action.Impls
base.DoDestruct();
isRunning = false;
runtime = null;
- var controller = Scene.Find("EventController");
controller?.RemoveUpdateEventListener(this);
+ controller = null;
OnFinishCallback?.Invoke();
}
@@ -73,19 +76,26 @@ namespace Cthangover.Mods.Dialog.Action.Impls
if (!isRunning)
return;
- elapsed += 1f / 60f;
+ elapsed += (float)(controller?.GetProcessDeltaTime() ?? 0d);
var t = WaitTime > 0f ? Mathf.Min(elapsed / WaitTime, 1f) : 1f;
OnUpdateCallback?.Invoke(t);
- if (elapsed >= WaitTime)
+ if (elapsed < WaitTime)
+ return;
+
+ // Advancing destructs this action, which clears the runtime field:
+ // keep the runtime in a local for the whole completion.
+ isRunning = false;
+ var owner = runtime;
+ if (owner?.DialogBox != null)
{
- // Advances the runtime directly instead of relying on a DialogBox poll.
- // Next() destructs this action (firing OnFinishCallback and detaching the
- // update listener), then Run() continues the queue past the WaitEvent point.
- isRunning = false;
- runtime?.Next();
- runtime?.Run();
+ owner.DialogBox.CompleteAction(this);
+ }
+ else if (owner != null)
+ {
+ owner.Next();
+ owner.Run();
}
}
}
diff --git a/src/UI/Dialog/Action/WaitType.cs b/src/UI/Dialog/Action/WaitType.cs
index 37c920d..207beb2 100644
--- a/src/UI/Dialog/Action/WaitType.cs
+++ b/src/UI/Dialog/Action/WaitType.cs
@@ -1,24 +1,24 @@
-namespace Cthangover.Mods.Dialog.Action
-{
- ///
- /// Determines how the DialogRuntime advances past this action. NoWait chains
- /// immediately to the next action (for imperative commands). WaitClick pauses
- /// until user input (click/key). WaitSelect pauses for variant selection.
- /// WaitTime pauses for a fixed duration. WaitEvent pauses until an external
- /// event marks the action as destructed.
- ///
- public enum WaitType
- {
- /// Don't wait — the runtime advances to the next action immediately in the same Run call.
- NoWait,
- /// Pause until the player clicks or presses the accept key. Used by dialog text actions.
- WaitClick,
- /// Pause until the player selects a choice variant. Used by choice menus.
- WaitSelect,
- /// Pause for a fixed duration in seconds ().
- WaitTime,
- /// Pause until an external event marks the action as destructed ().
- WaitEvent,
- }
-
-}
+namespace Cthangover.Mods.Dialog.Action
+{
+ ///
+ /// Determines how the DialogRuntime advances past this action. NoWait chains
+ /// immediately to the next action (for imperative commands). WaitClick pauses
+ /// until user input (click/key). WaitSelect pauses for variant selection.
+ /// WaitTime pauses for a fixed duration. WaitEvent pauses until an external
+ /// event marks the action as destructed.
+ ///
+ public enum WaitType
+ {
+ /// Don't wait — the runtime advances to the next action immediately in the same Run call.
+ NoWait,
+ /// Pause until the player clicks or presses the accept key. Used by dialog text actions.
+ WaitClick,
+ /// Pause until the player selects a choice variant. Used by choice menus.
+ WaitSelect,
+ /// Pause for a fixed duration in seconds ().
+ WaitTime,
+ /// Pause until an external event marks the action as destructed ().
+ WaitEvent,
+ }
+
+}
diff --git a/src/UI/Dialog/AnswerItem.cs b/src/UI/Dialog/AnswerItem.cs
index 84be781..b95ac5f 100644
--- a/src/UI/Dialog/AnswerItem.cs
+++ b/src/UI/Dialog/AnswerItem.cs
@@ -1,70 +1,70 @@
-using Cthangover.Mods.Interface.Base.Lists;
-using Cthangover.Mods.Dialog.Action.Impls;
-using Godot;
-using Cthangover.Core.UI;
-using Cthangover.Core.UI.Dialog;
-using Cthangover.Core.UI.Event;
-using Cthangover.Core.UI.Executable;
-using Cthangover.Core.Scenarios;
-using Cthangover.Core.Scenes;
-using Cthangover.Core.Interactive;
-
-namespace Cthangover.Mods.Dialog
-{
- ///
- /// Renders a single dialog choice option. Walks up the scene tree in OnReady
- /// to find the nearest IDialogBox parent — this avoids coupling to a specific
- /// dialog implementation and lets answer items work regardless of nesting depth.
- /// On click, calls SelectVariant on the found dialog box, passing the bound
- /// SelectVariant model (which carries the GoTo target and display text).
- ///
- public partial class AnswerItem : ListItem
- {
-
- [Export] private Label textField;
-
- private DialogBox dialogBox;
-
- /// Binds the choice button from the answer_item scene prefab and finds the owning dialog box by walking up the tree, so items work at any nesting depth.
- protected override void OnReady()
- {
- textField ??= GetNode