muffin v7.38

This commit is contained in:
alydev 2025-11-17 11:31:27 +10:00
parent 411c0bbe76
commit e5b98b3d57
35 changed files with 10700 additions and 7610 deletions

View file

@ -38,6 +38,18 @@ internal sealed class CreditsController : IDisposable
private static bool _pendingDispose;
private static int _handledConsecutiveCutscenes;
private static int _maxConsecutiveSkips = 5;
private static DateTime _lastHandledAt = DateTime.MinValue;
private static readonly TimeSpan _reentrancySuppress = TimeSpan.FromMilliseconds(500L, 0L);
private static long _lastHandledAddr;
private static readonly TimeSpan _consecutiveResetWindow = TimeSpan.FromSeconds(10L);
private readonly IAddonLifecycle _addonLifecycle;
private readonly ILogger<CreditsController> _logger;
@ -57,76 +69,119 @@ internal sealed class CreditsController : IDisposable
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, CreditScrollArray, _creditScrollHandler);
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, CreditArray, _creditHandler);
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, CreditPlayerArray, _creditPlayerHandler);
_logger.LogDebug("CreditsController: registered listeners and ready to skip up to {MaxSkips} successive cutscenes.", _maxConsecutiveSkips);
}
}
}
private unsafe void CreditScrollPostSetup(AddonEvent type, AddonArgs args)
{
_logger.LogInformation("Closing Credits sequence scroll post-setup");
if (args.Addon.Address == IntPtr.Zero)
HandleCutscene(delegate
{
_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);
_logger.LogInformation("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)
{
_logger.LogInformation("Closing Credits sequence post-setup");
if (args.Addon.Address == IntPtr.Zero)
HandleCutscene(delegate
{
_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);
_logger.LogInformation("CreditPostSetup: attempting to close credits sequence.");
AtkUnitBase* address = (AtkUnitBase*)args.Addon.Address;
address->FireCallbackInt(-2);
}, args);
}
private unsafe void CreditPlayerPostSetup(AddonEvent type, AddonArgs args)
{
_logger.LogInformation("Closing CreditPlayer");
if (args.Addon.Address == IntPtr.Zero)
HandleCutscene(delegate
{
_logger.LogInformation("CreditPlayerPostSetup: attempting to close CreditPlayer.");
AtkUnitBase* address = (AtkUnitBase*)args.Addon.Address;
address->Close(fireCallback: true);
}, args);
}
private void HandleCutscene(Action nativeAction, AddonArgs args)
{
long num = ((args.Addon.Address == IntPtr.Zero) ? 0 : ((IntPtr)args.Addon.Address).ToInt64());
if (num == 0L)
{
_logger.LogInformation("HandleCutscene: Addon address is zero, skipping.");
return;
}
lock (_lock)
{
if (_registeredLifecycle != null)
if (_lastHandledAt != DateTime.MinValue && DateTime.Now - _lastHandledAt > _consecutiveResetWindow)
{
if (_handledConsecutiveCutscenes != 0)
{
_logger.LogDebug("HandleCutscene: long pause detected ({Elapsed}), resetting consecutive counter (was {Was}).", DateTime.Now - _lastHandledAt, _handledConsecutiveCutscenes);
}
_handledConsecutiveCutscenes = 0;
}
if (DateTime.Now - _lastHandledAt < _reentrancySuppress && 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}), unregistering listeners.", _maxConsecutiveSkips);
TryUnregisterAndClear();
return;
}
_lastHandledAt = DateTime.Now;
_lastHandledAddr = num;
_handledConsecutiveCutscenes++;
_logger.LogDebug("HandleCutscene: handling cutscene #{Count} (addr=0x{Address:X}).", _handledConsecutiveCutscenes, num);
}
try
{
nativeAction();
_logger.LogInformation("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);
}
lock (_lock)
{
if (_handledConsecutiveCutscenes >= _maxConsecutiveSkips && !_deferDisposeUntilCutsceneEnds)
{
_logger.LogDebug("HandleCutscene: max handled reached and no defer active, unregistering now.");
TryUnregisterAndClear();
}
else
{
_logger.LogDebug("HandleCutscene: leaving listeners registered (handled {Count}/{Max}).", _handledConsecutiveCutscenes, _maxConsecutiveSkips);
}
}
}
private void TryUnregisterAndClear()
{
if (_registeredLifecycle != null)
{
try
{
_registeredLifecycle.UnregisterListener(AddonEvent.PostSetup, CreditPlayerArray, _creditPlayerHandler);
_registeredLifecycle.UnregisterListener(AddonEvent.PostSetup, CreditArray, _creditHandler);
_registeredLifecycle.UnregisterListener(AddonEvent.PostSetup, CreditScrollArray, _creditScrollHandler);
_registeredLifecycle = null;
_instance = null;
_logger.LogDebug("TryUnregisterAndClear: listeners unregistered successfully.");
}
catch (Exception exception)
{
_logger.LogError(exception, "TryUnregisterAndClear: exception while unregistering listeners.");
}
}
AtkUnitBase* address = (AtkUnitBase*)args.Addon.Address;
address->Close(fireCallback: true);
_registeredLifecycle = null;
_instance = null;
_handledConsecutiveCutscenes = 0;
_lastHandledAddr = 0L;
_lastHandledAt = DateTime.MinValue;
}
public static void DeferDisposeUntilCutsceneEnds()
@ -181,6 +236,9 @@ internal sealed class CreditsController : IDisposable
_registeredLifecycle = null;
_instance = null;
_pendingDispose = false;
_handledConsecutiveCutscenes = 0;
_lastHandledAddr = 0L;
_lastHandledAt = DateTime.MinValue;
_logger.LogDebug("CreditsController listeners unregistered and disposed (primary instance).");
}
}