using System; using Dalamud.Game.Addon.Lifecycle; using Dalamud.Game.Addon.Lifecycle.AddonArgTypes; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Component.GUI; using Microsoft.Extensions.Logging; namespace Questionable.Controller.GameUi; internal sealed class CreditsController : IDisposable { private static CreditsController? _instance; private static IAddonLifecycle? _registeredLifecycle; private static readonly IAddonLifecycle.AddonEventDelegate _creditScrollHandler = delegate(AddonEvent t, AddonArgs a) { _instance?.CreditScrollPostSetup(t, a); }; private static readonly IAddonLifecycle.AddonEventDelegate _creditHandler = delegate(AddonEvent t, AddonArgs a) { _instance?.CreditPostSetup(t, a); }; private static readonly IAddonLifecycle.AddonEventDelegate _creditPlayerHandler = delegate(AddonEvent t, AddonArgs a) { _instance?.CreditPlayerPostSetup(t, a); }; private static readonly string[] CreditScrollArray = new string[1] { "CreditScroll" }; private static readonly string[] CreditArray = new string[1] { "Credit" }; private static readonly string[] CreditPlayerArray = new string[1] { "CreditPlayer" }; private static bool _deferDisposeUntilCutsceneEnds; private static bool _pendingDispose; private readonly IAddonLifecycle _addonLifecycle; private readonly ILogger _logger; private static readonly object _lock = new object(); public CreditsController(IAddonLifecycle addonLifecycle, ILogger logger) { _addonLifecycle = addonLifecycle; _logger = logger; lock (_lock) { if (_instance == null) { _instance = this; _registeredLifecycle = _addonLifecycle; _addonLifecycle.RegisterListener(AddonEvent.PostSetup, CreditScrollArray, _creditScrollHandler); _addonLifecycle.RegisterListener(AddonEvent.PostSetup, CreditArray, _creditHandler); _addonLifecycle.RegisterListener(AddonEvent.PostSetup, CreditPlayerArray, _creditPlayerHandler); } } } private unsafe void CreditScrollPostSetup(AddonEvent type, AddonArgs args) { _logger.LogInformation("Closing Credits sequence scroll post-setup"); if (args.Addon.Address == IntPtr.Zero) { _logger.LogInformation("CreditScrollPostSetup: Addon address is zero, skipping."); return; } lock (_lock) { if (_registeredLifecycle != null) { _registeredLifecycle.UnregisterListener(AddonEvent.PostSetup, CreditPlayerArray, _creditPlayerHandler); _registeredLifecycle.UnregisterListener(AddonEvent.PostSetup, CreditArray, _creditHandler); _registeredLifecycle.UnregisterListener(AddonEvent.PostSetup, CreditScrollArray, _creditScrollHandler); _registeredLifecycle = null; _instance = null; } } AtkUnitBase* address = (AtkUnitBase*)args.Addon.Address; address->FireCallbackInt(-2); } private unsafe void CreditPostSetup(AddonEvent type, AddonArgs args) { _logger.LogInformation("Closing Credits sequence post-setup"); if (args.Addon.Address == IntPtr.Zero) { _logger.LogInformation("CreditPostSetup: Addon address is zero, skipping."); return; } lock (_lock) { if (_registeredLifecycle != null) { _registeredLifecycle.UnregisterListener(AddonEvent.PostSetup, CreditPlayerArray, _creditPlayerHandler); _registeredLifecycle.UnregisterListener(AddonEvent.PostSetup, CreditArray, _creditHandler); _registeredLifecycle.UnregisterListener(AddonEvent.PostSetup, CreditScrollArray, _creditScrollHandler); _registeredLifecycle = null; _instance = null; } } AtkUnitBase* address = (AtkUnitBase*)args.Addon.Address; address->FireCallbackInt(-2); } private unsafe void CreditPlayerPostSetup(AddonEvent type, AddonArgs args) { _logger.LogInformation("Closing CreditPlayer"); if (args.Addon.Address == IntPtr.Zero) { return; } lock (_lock) { if (_registeredLifecycle != null) { _registeredLifecycle.UnregisterListener(AddonEvent.PostSetup, CreditPlayerArray, _creditPlayerHandler); _registeredLifecycle.UnregisterListener(AddonEvent.PostSetup, CreditArray, _creditHandler); _registeredLifecycle.UnregisterListener(AddonEvent.PostSetup, CreditScrollArray, _creditScrollHandler); _registeredLifecycle = null; _instance = null; } } AtkUnitBase* address = (AtkUnitBase*)args.Addon.Address; address->Close(fireCallback: true); } public static void DeferDisposeUntilCutsceneEnds() { lock (_lock) { _deferDisposeUntilCutsceneEnds = true; if (_instance != null) { _instance._logger.LogDebug("CreditsController: deferring dispose until cutscene ends."); } } } public static void NotifyCutsceneEnded() { lock (_lock) { _deferDisposeUntilCutsceneEnds = false; if (_instance != null) { _instance._logger.LogDebug("CreditsController: cutscene ended, processing pending dispose state."); } if (_pendingDispose && _instance != null) { _instance._logger.LogDebug("CreditsController: pending dispose detected, performing unregister now."); _instance.Dispose(); } } } public void Dispose() { lock (_lock) { if (_instance != this) { return; } if (_deferDisposeUntilCutsceneEnds) { _pendingDispose = true; _logger.LogInformation("CreditsController.Dispose deferred until cutscene end."); return; } if (_registeredLifecycle != null) { _registeredLifecycle.UnregisterListener(AddonEvent.PostSetup, CreditPlayerArray, _creditPlayerHandler); _registeredLifecycle.UnregisterListener(AddonEvent.PostSetup, CreditArray, _creditHandler); _registeredLifecycle.UnregisterListener(AddonEvent.PostSetup, CreditScrollArray, _creditScrollHandler); } _registeredLifecycle = null; _instance = null; _pendingDispose = false; _logger.LogDebug("CreditsController listeners unregistered and disposed (primary instance)."); } } }