Avoid disabling qst in a 'quest complete' loading screen if SE servers are slow ...
authorLiza Carvelli <liza@carvel.li>
Sat, 26 Jul 2025 12:52:08 +0000 (14:52 +0200)
committerLiza Carvelli <liza@carvel.li>
Sat, 26 Jul 2025 12:52:08 +0000 (14:52 +0200)
Questionable/Controller/QuestController.cs
Questionable/Functions/QuestFunctions.cs

index f8815df..255aff2 100644 (file)
@@ -316,24 +316,34 @@ internal sealed class QuestController : MiniTaskController<QuestController>
             }
             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");
                     }
index 0fb0bfa..0bbd041 100644 (file)
@@ -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<ushort>((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,
 }