forked from aly/qstbak
249 lines
7.8 KiB
C#
249 lines
7.8 KiB
C#
using System;
|
|
using Dalamud.Game.Addon.Lifecycle;
|
|
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
|
using Dalamud.Game.ClientState.Conditions;
|
|
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 static int _handledConsecutiveCutscenes;
|
|
|
|
private static int _maxConsecutiveSkips = 5;
|
|
|
|
private static long? _lastHandledAt;
|
|
|
|
private const long ReentrancySuppressMs = 500L;
|
|
|
|
private static long _lastHandledAddr;
|
|
|
|
private const long ConsecutiveResetWindowMs = 10000L;
|
|
|
|
private readonly IAddonLifecycle _addonLifecycle;
|
|
|
|
private readonly Configuration _configuration;
|
|
|
|
private readonly ICondition _condition;
|
|
|
|
private readonly ILogger<CreditsController> _logger;
|
|
|
|
private static readonly object _lock = new object();
|
|
|
|
public CreditsController(IAddonLifecycle addonLifecycle, Configuration configuration, ICondition condition, ILogger<CreditsController> logger)
|
|
{
|
|
_addonLifecycle = addonLifecycle;
|
|
_configuration = configuration;
|
|
_condition = condition;
|
|
_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);
|
|
_condition.ConditionChange += OnConditionChange;
|
|
_logger.LogDebug("CreditsController: registered listeners and ready to skip up to {MaxSkips} successive cutscenes.", _maxConsecutiveSkips);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnConditionChange(ConditionFlag flag, bool value)
|
|
{
|
|
if ((flag == ConditionFlag.OccupiedInCutSceneEvent || flag == ConditionFlag.WatchingCutscene || flag == ConditionFlag.WatchingCutscene78) ? true : false)
|
|
{
|
|
if (value)
|
|
{
|
|
DeferDisposeUntilCutsceneEnds();
|
|
}
|
|
else if (!_condition[ConditionFlag.OccupiedInCutSceneEvent] && !_condition[ConditionFlag.WatchingCutscene] && !_condition[ConditionFlag.WatchingCutscene78])
|
|
{
|
|
NotifyCutsceneEnded();
|
|
}
|
|
}
|
|
}
|
|
|
|
private unsafe void CreditScrollPostSetup(AddonEvent type, AddonArgs args)
|
|
{
|
|
HandleCutscene(delegate
|
|
{
|
|
_logger.LogDebug("CreditScrollPostSetup: attempting to close credits sequence (scroll).");
|
|
AtkUnitBase* address = (AtkUnitBase*)args.Addon.Address;
|
|
address->FireCallbackInt(-2);
|
|
}, args);
|
|
}
|
|
|
|
private unsafe void CreditPostSetup(AddonEvent type, AddonArgs args)
|
|
{
|
|
HandleCutscene(delegate
|
|
{
|
|
_logger.LogDebug("CreditPostSetup: attempting to close credits sequence.");
|
|
AtkUnitBase* address = (AtkUnitBase*)args.Addon.Address;
|
|
address->FireCallbackInt(-2);
|
|
}, args);
|
|
}
|
|
|
|
private unsafe void CreditPlayerPostSetup(AddonEvent type, AddonArgs args)
|
|
{
|
|
HandleCutscene(delegate
|
|
{
|
|
_logger.LogDebug("CreditPlayerPostSetup: attempting to close CreditPlayer.");
|
|
AtkUnitBase* address = (AtkUnitBase*)args.Addon.Address;
|
|
address->Close(fireCallback: true);
|
|
}, args);
|
|
}
|
|
|
|
private void HandleCutscene(Action nativeAction, AddonArgs args)
|
|
{
|
|
if (_configuration.General.CinemaMode)
|
|
{
|
|
_logger.LogDebug("HandleCutscene: Cinema Mode enabled, not skipping cutscene.");
|
|
return;
|
|
}
|
|
long num = ((args.Addon.Address == IntPtr.Zero) ? 0 : ((IntPtr)args.Addon.Address).ToInt64());
|
|
if (num == 0L)
|
|
{
|
|
_logger.LogDebug("HandleCutscene: Addon address is zero, skipping.");
|
|
return;
|
|
}
|
|
lock (_lock)
|
|
{
|
|
long? lastHandledAt = _lastHandledAt;
|
|
if (lastHandledAt.HasValue)
|
|
{
|
|
long valueOrDefault = lastHandledAt.GetValueOrDefault();
|
|
if (Environment.TickCount64 - valueOrDefault > 10000)
|
|
{
|
|
if (_handledConsecutiveCutscenes != 0)
|
|
{
|
|
_logger.LogDebug("HandleCutscene: long pause detected ({ElapsedMs}ms), resetting consecutive counter (was {Was}).", Environment.TickCount64 - valueOrDefault, _handledConsecutiveCutscenes);
|
|
}
|
|
_handledConsecutiveCutscenes = 0;
|
|
}
|
|
}
|
|
lastHandledAt = _lastHandledAt;
|
|
if (lastHandledAt.HasValue)
|
|
{
|
|
long valueOrDefault2 = lastHandledAt.GetValueOrDefault();
|
|
if (Environment.TickCount64 - valueOrDefault2 < 500 && num == _lastHandledAddr)
|
|
{
|
|
_logger.LogDebug("HandleCutscene: suppressed re-entrant invocation for same addon (addr=0x{Address:X}).", num);
|
|
return;
|
|
}
|
|
}
|
|
if (_handledConsecutiveCutscenes >= _maxConsecutiveSkips)
|
|
{
|
|
_logger.LogInformation("HandleCutscene: reached max consecutive skips ({MaxSkips}), pausing until cutscenes stop for a while.", _maxConsecutiveSkips);
|
|
return;
|
|
}
|
|
_lastHandledAt = Environment.TickCount64;
|
|
_lastHandledAddr = num;
|
|
_handledConsecutiveCutscenes++;
|
|
_logger.LogDebug("HandleCutscene: handling cutscene #{Count} (addr=0x{Address:X}).", _handledConsecutiveCutscenes, num);
|
|
}
|
|
try
|
|
{
|
|
nativeAction();
|
|
_logger.LogDebug("HandleCutscene: native action executed for addon at 0x{Address:X}.", num);
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
_logger.LogError(exception, "HandleCutscene: exception while executing native action for addon at 0x{Address:X}.", num);
|
|
}
|
|
_logger.LogDebug("HandleCutscene: handled {Count}/{Max} consecutive cutscenes.", _handledConsecutiveCutscenes, _maxConsecutiveSkips);
|
|
}
|
|
|
|
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.LogDebug("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);
|
|
}
|
|
_condition.ConditionChange -= OnConditionChange;
|
|
_registeredLifecycle = null;
|
|
_instance = null;
|
|
_pendingDispose = false;
|
|
_handledConsecutiveCutscenes = 0;
|
|
_lastHandledAddr = 0L;
|
|
_lastHandledAt = null;
|
|
_logger.LogDebug("CreditsController listeners unregistered and disposed (primary instance).");
|
|
}
|
|
}
|
|
}
|