muffin v6.12

This commit is contained in:
alydev 2025-10-09 07:53:51 +10:00
parent cfb4dea47e
commit c8197297b2
58 changed files with 40038 additions and 58059 deletions

View file

@ -9,29 +9,100 @@ 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<CreditsController> _logger;
private static readonly object _lock = new object();
public CreditsController(IAddonLifecycle addonLifecycle, ILogger<CreditsController> logger)
{
_addonLifecycle = addonLifecycle;
_logger = logger;
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "CreditScroll", CreditScrollPostSetup);
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "Credit", CreditPostSetup);
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "CreditPlayer", CreditPlayerPostSetup);
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");
_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");
_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);
}
@ -39,14 +110,78 @@ internal sealed class CreditsController : IDisposable
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()
{
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "CreditPlayer", CreditPlayerPostSetup);
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "Credit", CreditPostSetup);
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "CreditScroll", CreditScrollPostSetup);
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).");
}
}
}

View file

@ -502,14 +502,14 @@ internal sealed class InteractionUiController : IDisposable
_logger.LogInformation("Unexpected excelPrompt: {ExcelPrompt}, actualPrompt: {ActualPrompt}", stringOrRegex, actualPrompt);
continue;
}
for (int num3 = 0; num3 < answers.Count; num3++)
if (dialogueChoice2.AnswerIsRegularExpression && stringOrRegex2 != null)
{
_logger.LogTrace("Checking if {ActualAnswer} == {ExpectedAnswer}", answers[num3], stringOrRegex2);
if (!IsMatch(answers[num3], stringOrRegex2))
int? num3 = FindBestRegexMatch(answers, stringOrRegex2, quest3, dialogueChoice2);
if (!num3.HasValue)
{
continue;
}
_logger.LogInformation("Returning {Index}: '{Answer}' for '{Prompt}'", num3, answers[num3], actualPrompt);
_logger.LogInformation("Returning {Index}: '{Answer}' for '{Prompt}' (best regex match)", num3.Value, answers[num3.Value], actualPrompt);
if (quest3?.Id is SatisfactionSupplyNpcId)
{
if (_questController.GatheringQuest == null || _questController.GatheringQuest.Sequence == byte.MaxValue)
@ -519,13 +519,72 @@ internal sealed class InteractionUiController : IDisposable
_questController.GatheringQuest.SetSequence(1);
_questController.StartGatheringQuest("SatisfactionSupply turn in");
}
return num3;
return num3.Value;
}
for (int num4 = 0; num4 < answers.Count; num4++)
{
_logger.LogTrace("Checking if {ActualAnswer} == {ExpectedAnswer}", answers[num4], stringOrRegex2);
if (!IsMatch(answers[num4], stringOrRegex2))
{
continue;
}
_logger.LogInformation("Returning {Index}: '{Answer}' for '{Prompt}'", num4, answers[num4], actualPrompt);
if (quest3?.Id is SatisfactionSupplyNpcId)
{
if (_questController.GatheringQuest == null || _questController.GatheringQuest.Sequence == byte.MaxValue)
{
return null;
}
_questController.GatheringQuest.SetSequence(1);
_questController.StartGatheringQuest("SatisfactionSupply turn in");
}
return num4;
}
}
_logger.LogInformation("No matching answer found for {Prompt}.", actualPrompt);
return null;
}
private static int? FindBestRegexMatch(List<string?> answers, StringOrRegex expectedAnswer, Questionable.Model.Quest? quest, DialogueChoice dialogueChoice)
{
List<int> list = (from x in answers.Select((string answer, int index) => new { answer, index })
where x.answer != null && expectedAnswer.IsMatch(x.answer)
select x.index).ToList();
return list.Count switch
{
0 => null,
1 => list[0],
_ => GetBestMatchFromQuestPath(answers, list, quest, dialogueChoice),
};
}
private static int GetBestMatchFromQuestPath(List<string?> answers, List<int> matchingIndexes, Questionable.Model.Quest? quest, DialogueChoice dialogueChoice)
{
if (quest != null && dialogueChoice.Answer != null)
{
try
{
string questExpectedAnswer = null;
if (dialogueChoice.Answer.Type == ExcelRef.EType.RawString)
{
questExpectedAnswer = dialogueChoice.Answer.AsRawString();
}
if (!string.IsNullOrEmpty(questExpectedAnswer) && !dialogueChoice.AnswerIsRegularExpression)
{
int num = matchingIndexes.FirstOrDefault((int i) => string.Equals(answers[i], questExpectedAnswer, StringComparison.OrdinalIgnoreCase));
if (num != 0)
{
return num;
}
}
}
catch
{
}
}
return matchingIndexes.OrderBy((int i) => answers[i]?.Length ?? int.MaxValue).First();
}
private static bool IsMatch(string? actualAnswer, StringOrRegex? expectedAnswer)
{
if (actualAnswer == null && expectedAnswer == null)