using Cthangover.Core.Events; using Cthangover.Core.Mods; using Cthangover.Core.Utils; namespace Cthangover.Mods.Audio { /// /// Engine-mod initializer for the audio subsystem. Registers the /// /root/AudioService and /root/MusicPlayer nodes when mod /// resources become ready and subscribes the music player to scene events, /// replacing the former core autoloads. Both operations are idempotent: /// ModManager.Reload() re-runs OnModResourcesReady, so node /// creation and event subscription must tolerate repeat invocations. /// public class AudioModInitializer : IModInitializer { private static bool subscribed; /// No per-mod setup is required; node registration happens on resources-ready. public void OnModLoaded(string modId) { } /// /// Ensures the audio nodes exist under the tree root and that the /// music player listens to scene events. Safe to call again after /// a mod reload. /// public void OnModResourcesReady() { EnsureAudioNodes(); EnsureEventSubscriptions(); } private static bool nodesQueued; private static void EnsureAudioNodes() { var root = ModEventBus.Instance?.GetTree()?.Root; if (root == null) { GameLogger.Log("AUDIO", "AudioModInitializer: tree root not available, skipping node setup", LogLevel.Error); return; } if (root.GetNodeOrNull("AudioService") != null || nodesQueued) return; nodesQueued = true; // Deferred: OnModResourcesReady fires while the root is still // setting up autoload children, so a direct AddChild is rejected. // Deferred calls run in FIFO order, which keeps AudioService ahead // of MusicPlayerBehaviour — its OnReady resolves AudioService. ModRoot.AddDeferred(new AudioService { Name = "AudioService" }); ModRoot.AddDeferred(new MusicPlayerBehaviour { Name = "MusicPlayer" }); } private static void EnsureEventSubscriptions() { if (subscribed) return; subscribed = true; ModEventBus.Subscribe(OnSceneEntered, priority: 10); ModEventBus.Subscribe(OnRouteActivating); } private static void OnSceneEntered(SceneEnteredEvent evt) { NotifyMusicPlayers(evt.SceneName); } private static void OnRouteActivating(SceneRouteActivatingEvent evt) { NotifyMusicPlayers(evt.MusicScene); } private static void NotifyMusicPlayers(string musicScene) { if (string.IsNullOrEmpty(musicScene)) return; var tree = ModEventBus.Instance?.GetTree(); if (tree == null) return; foreach (var node in tree.GetNodesInGroup("music_player")) { if (node is MusicPlayerBehaviour player) { GameLogger.Log("AUDIO", $"MusicPlayer notified about scene '{musicScene}'"); player.UpdateMusicType(musicScene); } } } } }