using Cthangover.Core.Bootstrap; using Cthangover.Core.Events; using Cthangover.Core.Mods; using Cthangover.Core.Scenarios; using Cthangover.Core.UI; using Cthangover.Core.Utils; using Cthangover.Mods.Dialog.Scenarios; using Godot; namespace Cthangover.Mods.Dialog { /// /// Engine-mod initializer for the dialog subsystem. On resources-ready it /// publishes the scenario event factory used by the core SceneManager and /// parks the /root/DialogRunner node implementing the core /// IInteractiveDialogRunner contract. All operations are idempotent: /// ModManager.Reload() re-runs OnModResourcesReady, so node /// creation must tolerate repeat invocations. Scenario command strategies /// of every mod reach their factory through /// , not through this initializer. /// public class DialogModInitializer : IModInitializer { private static bool initialized; /// No per-mod setup is required; registration happens on resources-ready. public void OnModLoaded(string modId) { } /// /// Publishes the scenario event factory and queues the dialog runner /// node. Safe to call again after a mod reload. /// public void OnModResourcesReady() { if (initialized) return; initialized = true; // Scenario-backed events (scene definitions and test-play) are built // by the core SceneManager through this factory. AppServices.Current.SceneEvents.ScenarioEventFactory = new ScenarioEventFactory(); EnsureDialogRunner(); } private static void EnsureDialogRunner() { var root = ModEventBus.Instance?.GetTree()?.Root; if (root == null) { GameLogger.Log("DIALOG", "DialogModInitializer: tree root not available, skipping runner setup", LogLevel.Error); return; } if (root.GetNodeOrNull("DialogRunner") != null) return; // Deferred: OnModResourcesReady fires while the root is still // setting up autoload children, so a direct AddChild is rejected. ModRoot.AddDeferred(new DialogScriptRunner { Name = "DialogRunner" }); } } }