From 93bbaaee66a4074afad9a4d756f9e196ec0a0138 Mon Sep 17 00:00:00 2001 From: Liza Carvelli Date: Sat, 26 Jul 2025 14:52:08 +0200 Subject: [PATCH] Avoid disabling qst in a 'quest complete' loading screen if SE servers are slow (experimental) --- Questionable/Controller/QuestController.cs | 24 +++++-- Questionable/Functions/QuestFunctions.cs | 82 ++++++++++++---------- 2 files changed, 63 insertions(+), 43 deletions(-) diff --git a/Questionable/Controller/QuestController.cs b/Questionable/Controller/QuestController.cs index f8815df1..255aff2b 100644 --- a/Questionable/Controller/QuestController.cs +++ b/Questionable/Controller/QuestController.cs @@ -316,24 +316,34 @@ internal sealed class QuestController : MiniTaskController } else { - (ElementId? currentQuestId, currentSequence, bool msqInformationAvailable) = + (ElementId? currentQuestId, currentSequence, MainScenarioQuestState msqState) = _questFunctions.GetCurrentQuest(allowNewMsq: AutomationType != EAutomationType.SingleQuestB); + (ElementId, byte)? priorityQuestOption = ManualPriorityQuests .Where(x => _questFunctions.IsReadyToAcceptQuest(x.Id) || _questFunctions.IsQuestAccepted(x.Id)) - .Select(x => new QuestReference(x.Id, _questFunctions.GetQuestProgressInfo(x.Id)?.Sequence ?? 0, true)) - .FirstOrDefault() ?? - _questFunctions.GetCurrentQuest(allowNewMsq: AutomationType != EAutomationType.SingleQuestB); + .Select(x => (x.Id, _questFunctions.GetQuestProgressInfo(x.Id)?.Sequence ?? 0)) + .FirstOrDefault(); + if (priorityQuestOption is { Item1: not null } priorityQuest) + { + currentQuestId = priorityQuest.Item1; + currentSequence = priorityQuest.Item2; + } if (currentQuestId == null || currentQuestId.Value == 0) { if (_startedQuest != null) { - if (!msqInformationAvailable) + if (msqState == MainScenarioQuestState.Unavailable) + { + _logger.LogWarning("MSQ information not available, doing nothing"); + return; + } + else if (msqState == MainScenarioQuestState.LoadingScreen) { - _logger.LogTrace("MSQ information not available, doing nothing (complete: {Complete})", _questFunctions.IsMainScenarioQuestComplete()); + _logger.LogWarning("On loading screen, no MSQ - doing nothing"); return; } - _logger.LogInformation("No current quest, resetting data"); + _logger.LogInformation("No current quest, resetting data [CQI: {CurrrentQuestData}], [CQ: {QuestData}], [MSQ: {MsqData}]", _questFunctions.GetCurrentQuestInternal(true), _questFunctions.GetCurrentQuest(), _questFunctions.GetMainScenarioQuest()); _startedQuest = null; Stop("Resetting current quest"); } diff --git a/Questionable/Functions/QuestFunctions.cs b/Questionable/Functions/QuestFunctions.cs index 0fb0bfaf..0bbd041b 100644 --- a/Questionable/Functions/QuestFunctions.cs +++ b/Questionable/Functions/QuestFunctions.cs @@ -59,32 +59,32 @@ internal sealed unsafe class QuestFunctions public QuestReference GetCurrentQuest(bool allowNewMsq = true) { var internalQuest = GetCurrentQuestInternal(allowNewMsq); - var (currentQuest, sequence, msqInformationAvailable) = internalQuest; + var (currentQuest, sequence, questState) = internalQuest; PlayerState* playerState = PlayerState.Instance(); if (currentQuest == null || currentQuest.Value == 0) { if (_clientState.TerritoryType == 181) // Starting in Limsa - return new(new QuestId(107), 0, true); + return new(new QuestId(107), 0, MainScenarioQuestState.Available); if (_clientState.TerritoryType == 182) // Starting in Ul'dah - return new(new QuestId(594), 0, true); + return new(new QuestId(594), 0, MainScenarioQuestState.Available); if (_clientState.TerritoryType == 183) // Starting in Gridania - return new(new QuestId(39), 0, true); - return QuestReference.NoQuest(msqInformationAvailable); + return new(new QuestId(39), 0, MainScenarioQuestState.Available); + return QuestReference.NoQuest(questState); } else if (currentQuest.Value == 681) { // if we have already picked up the GC quest, just return the progress for it if (IsQuestAccepted(currentQuest) || IsQuestComplete(currentQuest)) - return new(currentQuest, sequence, msqInformationAvailable); + return new(currentQuest, sequence, questState); // The company you keep... return _configuration.General.GrandCompany switch { - GrandCompany.TwinAdder => new(new QuestId(680), 0, true), - GrandCompany.Maelstrom => new(new QuestId(681), 0, true), - GrandCompany.ImmortalFlames => new(new QuestId(682), 0, true), - _ => QuestReference.NoQuest(), + GrandCompany.TwinAdder => new(new QuestId(680), 0, questState), + GrandCompany.Maelstrom => new(new QuestId(681), 0, questState), + GrandCompany.ImmortalFlames => new(new QuestId(682), 0, questState), + _ => QuestReference.NoQuest(MainScenarioQuestState.Unavailable), }; } else if (currentQuest.Value == 3856 && !playerState->IsMountUnlocked(1)) // we come in peace @@ -98,14 +98,14 @@ internal sealed unsafe class QuestFunctions }; if (chocoboQuest != 0 && !QuestManager.IsQuestComplete(chocoboQuest)) - return new(new QuestId(chocoboQuest), QuestManager.GetQuestSequence(chocoboQuest), true); + return new(new QuestId(chocoboQuest), QuestManager.GetQuestSequence(chocoboQuest), questState); } else if (currentQuest.Value == 801) { // skeletons in her closet, finish 'broadening horizons' to unlock the white wolf gate QuestId broadeningHorizons = new QuestId(802); if (IsQuestAccepted(broadeningHorizons)) - return new(broadeningHorizons, QuestManager.GetQuestSequence(broadeningHorizons.Value), true); + return new(broadeningHorizons, QuestManager.GetQuestSequence(broadeningHorizons.Value), questState); } return internalQuest; @@ -115,20 +115,20 @@ internal sealed unsafe class QuestFunctions { var questManager = QuestManager.Instance(); if (questManager == null) - return QuestReference.NoQuest(false); + return QuestReference.NoQuest(MainScenarioQuestState.Unavailable); // always prioritize accepting MSQ quests, to make sure we don't turn in one MSQ quest and then go off to do // side quests until the end of time. - QuestReference msqQuest = GetMainScenarioQuest(); + (QuestReference msqQuest, _) = GetMainScenarioQuest(); if (msqQuest.CurrentQuest != null && !_questRegistry.IsKnownQuest(msqQuest.CurrentQuest)) - msqQuest = QuestReference.NoQuest(msqQuest.MsqInformationAvailable); + msqQuest = QuestReference.NoQuest(msqQuest.State); if (msqQuest.CurrentQuest != null && !IsQuestAccepted(msqQuest.CurrentQuest)) { if (allowNewMsq) return msqQuest; else - msqQuest = QuestReference.NoQuest(msqQuest.MsqInformationAvailable); + msqQuest = QuestReference.NoQuest(msqQuest.State); } // Use the quests in the same order as they're shown in the to-do list, e.g. if the MSQ is the first item, @@ -173,7 +173,7 @@ internal sealed unsafe class QuestFunctions { // only if the other quest isn't ready to be turned in if (sequence != 255) - return new(quest, sequence, msqQuest.MsqInformationAvailable); + return new(quest, sequence, msqQuest.State); } } else if (!IsOnAlliedSocietyMount()) @@ -196,7 +196,7 @@ internal sealed unsafe class QuestFunctions .Cast<(ElementId, byte)?>() .FirstOrDefault(); if (talkToNormalNpcs != null) - return new(talkToNormalNpcs.Value.Item1, talkToNormalNpcs.Value.Item2, msqQuest.MsqInformationAvailable); + return new(talkToNormalNpcs.Value.Item1, talkToNormalNpcs.Value.Item2, msqQuest.State); } /* @@ -220,7 +220,7 @@ internal sealed unsafe class QuestFunctions } } - return new(firstTrackedQuest, firstTrackedSequence, msqQuest.MsqInformationAvailable); + return new(firstTrackedQuest, firstTrackedSequence, msqQuest.State); } ElementId? priorityQuest = GetNextPriorityQuestsThatCanBeAccepted().FirstOrDefault(); @@ -228,7 +228,7 @@ internal sealed unsafe class QuestFunctions { // if we have an accepted msq quest, and know of no quest of those currently in the to-do list... // (1) try and find a priority quest to do - return new(priorityQuest, QuestManager.GetQuestSequence(priorityQuest.Value), msqQuest.MsqInformationAvailable); + return new(priorityQuest, QuestManager.GetQuestSequence(priorityQuest.Value), msqQuest.State); } else if (msqQuest.CurrentQuest != null) { @@ -236,10 +236,10 @@ internal sealed unsafe class QuestFunctions return msqQuest; } - return QuestReference.NoQuest(msqQuest.MsqInformationAvailable); + return QuestReference.NoQuest(msqQuest.State); } - private QuestReference GetMainScenarioQuest() + public (QuestReference, string?) GetMainScenarioQuest() { if (QuestManager.IsQuestComplete(3759)) // Memories Rekindled { @@ -259,23 +259,24 @@ internal sealed unsafe class QuestFunctions // redoHud+44 is chapter // redoHud+46 is quest ushort questId = MemoryHelper.Read((nint)questRedoHud + 46); - return new(new QuestId(questId), QuestManager.GetQuestSequence(questId), true); + return (new(new QuestId(questId), QuestManager.GetQuestSequence(questId), MainScenarioQuestState.Available), "NG+"); } } } var scenarioTree = AgentScenarioTree.Instance(); if (scenarioTree == null) - return QuestReference.NoQuest(false); + return (QuestReference.NoQuest(MainScenarioQuestState.Unavailable), "No Scenario Tree"); if (scenarioTree->Data == null) - return QuestReference.NoQuest(false); + return (QuestReference.NoQuest(MainScenarioQuestState.LoadingScreen), "Scenario Tree Data is null"); QuestId currentQuest = new QuestId(scenarioTree->Data->CurrentScenarioQuest); + string extraData = $"sq: {currentQuest}"; if (currentQuest.Value == 0) { if (IsMainScenarioQuestComplete()) - return QuestReference.NoQuest(); + return (QuestReference.NoQuest(MainScenarioQuestState.Complete), "Main Scenario is complete"); // fallback lookup; find a quest which isn't completed but where all prequisites are met // excluding branching quests @@ -286,7 +287,7 @@ internal sealed unsafe class QuestFunctions .Where(q => IsReadyToAcceptQuest(q.QuestId, true)) .ToList(); if (potentialQuests.Count == 0) - return QuestReference.NoQuest(false); + return (QuestReference.NoQuest(MainScenarioQuestState.Unavailable), "No potential quests found"); else if (potentialQuests.Count > 1) { // for all of these (except the GC quests), questionable normally auto-picks the next quest based on the @@ -306,7 +307,8 @@ internal sealed unsafe class QuestFunctions else if (potentialQuests.Any(x => x.QuestId.Value == 4865)) currentQuest = new QuestId(4865); // DT: To Kozama'uk vs. To Urqopacha if (potentialQuests.Count != 1) - return QuestReference.NoQuest(false); + return (QuestReference.NoQuest(MainScenarioQuestState.Unavailable), "Multiple potential quests found: " + + string.Join(", ", potentialQuests.Select(x => x.QuestId.Value))); } else currentQuest = (QuestId)potentialQuests.Single().QuestId; @@ -315,28 +317,28 @@ internal sealed unsafe class QuestFunctions // if the MSQ is hidden, we generally ignore it QuestManager* questManager = QuestManager.Instance(); if (IsQuestAccepted(currentQuest) && questManager->GetQuestById(currentQuest.Value)->IsHidden) - return QuestReference.NoQuest(); + return (QuestReference.NoQuest(MainScenarioQuestState.Available), "Quest accepted but hidden"); // it can sometimes happen (although this isn't reliably reproducible) that the quest returned here // is one you've just completed. We return 255 as sequence here, since that is the end of said quest; // but this is just really hoping that this breaks nothing. if (IsQuestComplete(currentQuest)) - return new(currentQuest, 255, true); + return (new(currentQuest, 255, MainScenarioQuestState.Available), $"Quest {currentQuest.Value} complete"); else if (!IsReadyToAcceptQuest(currentQuest)) - return QuestReference.NoQuest(); + return (QuestReference.NoQuest(MainScenarioQuestState.Unavailable), $"Not readdy to accept quest {currentQuest.Value}"); var currentLevel = _clientState.LocalPlayer?.Level; // are we in a loading screen? if (currentLevel == null) - return QuestReference.NoQuest(); + return (QuestReference.NoQuest(MainScenarioQuestState.LoadingScreen), "In loading screen"); // if we're not at a high enough level to continue, we also ignore it if (_questRegistry.TryGetQuest(currentQuest, out Quest? quest) && quest.Info.Level > currentLevel) - return QuestReference.NoQuest(); + return (QuestReference.NoQuest(MainScenarioQuestState.Unavailable), "Low level"); - return new(currentQuest, QuestManager.GetQuestSequence(currentQuest.Value), true); + return (new(currentQuest, QuestManager.GetQuestSequence(currentQuest.Value), MainScenarioQuestState.Available), extraData); } private bool IsOnAlliedSocietyMount() @@ -837,7 +839,15 @@ internal sealed unsafe class QuestFunctions } } -public sealed record QuestReference(ElementId? CurrentQuest, byte Sequence, bool MsqInformationAvailable) +public sealed record QuestReference(ElementId? CurrentQuest, byte Sequence, MainScenarioQuestState State) { - public static QuestReference NoQuest(bool msqInformationAvailable = true) => new(null, 0, msqInformationAvailable); + public static QuestReference NoQuest(MainScenarioQuestState state) => new(null, 0, state); +} + +public enum MainScenarioQuestState +{ + Unavailable, + Available, + Complete, + LoadingScreen, } -- 2.20.1