Don't count unobtainable quests towards completeable quests in journal
authorLiza Carvelli <liza@carvel.li>
Fri, 6 Sep 2024 19:17:11 +0000 (21:17 +0200)
committerLiza Carvelli <liza@carvel.li>
Fri, 6 Sep 2024 19:24:38 +0000 (21:24 +0200)
Questionable/Controller/Steps/Interactions/UseItem.cs
Questionable/Data/QuestData.cs
Questionable/Functions/QuestFunctions.cs
Questionable/Model/QuestInfo.cs
Questionable/Windows/JournalComponents/QuestJournalComponent.cs
Questionable/Windows/UiUtils.cs

index 1891e5b1bd491c913ef6bb5ba6809a163237fdce..358c30d9306c5670161028cb98e05886f3fccfe6 100644 (file)
@@ -91,7 +91,7 @@ internal static class UseItem
                         step.CompletionQuestVariablesFlags);
                 }
 
-                return [unmount, task];
+                return [unmount, new WaitAtEnd.WaitDelay(TimeSpan.FromSeconds(0.5)), task];
             }
             else if (step.DataId != null)
             {
index 7ba7f3cc89c22267967ea87ff00bba4a5993afe0..2be98c4dbdccf625500fdc57205af074690a1ac3 100644 (file)
@@ -45,12 +45,21 @@ internal sealed class QuestData
                 .Where(x => x.RowId > 0 && x.Quest.Row > 0)
                 .ToDictionary(x => x.Quest.Row, x => x.Redo);
 
+        Dictionary<uint, byte> startingCities = new();
+        for (byte redoChapter = 1; redoChapter <= 3; ++redoChapter)
+        {
+            var questRedo = dataManager.GetExcelSheet<QuestRedo>()!.GetRow(redoChapter)!;
+            foreach (var quest in questRedo.Quest.Where(x => x.Row > 0))
+                startingCities[quest.Row] = redoChapter;
+        }
+
         List<IQuestInfo> quests =
         [
             ..dataManager.GetExcelSheet<Quest>()!
                 .Where(x => x.RowId > 0)
                 .Where(x => x.IssuerLocation.Row > 0)
-                .Select(x => new QuestInfo(x, questChapters.GetValueOrDefault(x.RowId))),
+                .Select(x => new QuestInfo(x, questChapters.GetValueOrDefault(x.RowId),
+                    startingCities.GetValueOrDefault(x.RowId))),
             ..dataManager.GetExcelSheet<SatisfactionNpc>()!
                 .Where(x => x.RowId > 0)
                 .Select(x => new SatisfactionSupplyInfo(x)),
@@ -150,6 +159,14 @@ internal sealed class QuestData
         AddPreviousQuest(new QuestId(3821), new QuestId(spearfishing));
         AddPreviousQuest(new QuestId(3833), new QuestId(spearfishing));
         */
+
+        // initial city quests are side quests
+        ((QuestInfo)_quests[new QuestId(107)]).StartingCity = 1;
+        ((QuestInfo)_quests[new QuestId(39)]).StartingCity = 2;
+        ((QuestInfo)_quests[new QuestId(594)]).StartingCity = 3;
+
+        // follow-up quests to picking a GC
+        AddGcFollowUpQuests();
     }
 
     private void AddPreviousQuest(QuestId questToUpdate, QuestId requiredQuestId)
@@ -158,6 +175,16 @@ internal sealed class QuestData
         quest.AddPreviousQuest(new QuestInfo.PreviousQuestInfo(requiredQuestId));
     }
 
+    private void AddGcFollowUpQuests()
+    {
+        QuestId[] questIds = [new(683), new(684), new(685)];
+        foreach (QuestId questId in questIds)
+        {
+            QuestInfo quest = (QuestInfo)_quests[questId];
+            quest.AddQuestLocks(QuestInfo.QuestJoin.AtLeastOne, questIds.Where(x => x != questId).ToArray());
+        }
+    }
+
     public IQuestInfo GetQuestInfo(ElementId elementId)
     {
         return _quests[elementId] ?? throw new ArgumentOutOfRangeException(nameof(elementId));
index 3f48db397dca3a6b03477136f6b1e8f549f5009e..091150a86b5437d5750c6e8708ad169f9bc6c6c1 100644 (file)
@@ -453,6 +453,14 @@ internal sealed unsafe class QuestFunctions
         return !HasCompletedPreviousQuests(questInfo, extraCompletedQuest) || !HasCompletedPreviousInstances(questInfo);
     }
 
+    public bool IsQuestUnobtainable(ElementId elementId, ElementId? extraCompletedQuest = null)
+    {
+        if (elementId is QuestId questId)
+            return IsQuestUnobtainable(questId, extraCompletedQuest);
+        else
+            return false;
+    }
+
     public bool IsQuestUnobtainable(QuestId questId, ElementId? extraCompletedQuest = null)
     {
         var questInfo = (QuestInfo)_questData.GetQuestInfo(questId);
@@ -468,6 +476,36 @@ internal sealed unsafe class QuestFunctions
         if (_questData.GetLockedClassQuests().Contains(questId))
             return true;
 
+        unsafe
+        {
+            var startingCity = PlayerState.Instance()->StartTown;
+            if (questInfo.StartingCity > 0 && questInfo.StartingCity != startingCity)
+                return true;
+
+            if (questId.Value == 674 && startingCity == 3)
+                return true;
+            if (questId.Value == 673 && startingCity != 3)
+                return true;
+
+            Dictionary<ushort, EClassJob> closeToHomeQuests = new()
+            {
+                { 108, EClassJob.Marauder },
+                { 109, EClassJob.Arcanist },
+                { 85, EClassJob.Lancer },
+                { 123, EClassJob.Archer },
+                { 124, EClassJob.Conjurer },
+                { 568, EClassJob.Gladiator },
+                { 569, EClassJob.Pugilist },
+                { 570, EClassJob.Thaumaturge }
+            };
+            if (closeToHomeQuests.TryGetValue(questId.Value, out EClassJob neededStartingClass))
+            {
+                EClassJob actualStartingClass = (EClassJob)PlayerState.Instance()->FirstClass;
+                if (actualStartingClass != neededStartingClass)
+                    return true;
+            }
+        }
+
         return false;
     }
 
index 9b1214b985f182bfcf31a66bd08ab0fffd2ff243..aeda70c3397defdf56a45e73b20bd1c4bc8aa4be 100644 (file)
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
 using System.Collections.Immutable;
 using System.Linq;
 using FFXIVClientStructs.FFXIV.Client.UI.Agent;
@@ -11,7 +12,7 @@ namespace Questionable.Model;
 
 internal sealed class QuestInfo : IQuestInfo
 {
-    public QuestInfo(ExcelQuest quest, ushort newGamePlusChapter)
+    public QuestInfo(ExcelQuest quest, ushort newGamePlusChapter, byte startingCity)
     {
         QuestId = new QuestId((ushort)(quest.RowId & 0xFFFF));
 
@@ -60,6 +61,7 @@ internal sealed class QuestInfo : IQuestInfo
         ClassJobs = QuestInfoUtils.AsList(quest.ClassJobCategory0.Value!);
         IsSeasonalEvent = quest.Festival.Row != 0;
         NewGamePlusChapter = newGamePlusChapter;
+        StartingCity = startingCity;
         Expansion = (EExpansionVersion)quest.Expansion.Row;
     }
 
@@ -69,10 +71,10 @@ internal sealed class QuestInfo : IQuestInfo
     public ushort Level { get; }
     public uint IssuerDataId { get; }
     public bool IsRepeatable { get; }
-    public ImmutableList<PreviousQuestInfo> PreviousQuests { get; set; }
+    public ImmutableList<PreviousQuestInfo> PreviousQuests { get; private set; }
     public QuestJoin PreviousQuestJoin { get; }
-    public ImmutableList<QuestId> QuestLocks { get; }
-    public QuestJoin QuestLockJoin { get; }
+    public ImmutableList<QuestId> QuestLocks { get; private set; }
+    public QuestJoin QuestLockJoin { get; private set; }
     public List<ushort> PreviousInstanceContent { get; }
     public QuestJoin PreviousInstanceContentJoin { get; }
     public uint? JournalGenre { get; }
@@ -84,6 +86,7 @@ internal sealed class QuestInfo : IQuestInfo
     public IReadOnlyList<EClassJob> ClassJobs { get; }
     public bool IsSeasonalEvent { get; }
     public ushort NewGamePlusChapter { get; }
+    public byte StartingCity { get; set; }
     public EExpansionVersion Expansion { get; }
 
     [UsedImplicitly(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.Members)]
@@ -99,5 +102,14 @@ internal sealed class QuestInfo : IQuestInfo
         PreviousQuests = [..PreviousQuests, questId];
     }
 
+    public void AddQuestLocks(QuestJoin questJoin, params QuestId[] questId)
+    {
+        if (QuestLocks.Count > 0 && QuestLockJoin != questJoin)
+            throw new InvalidOperationException();
+
+        QuestLockJoin = questJoin;
+        QuestLocks = [..QuestLocks, ..questId];
+    }
+
     public sealed record PreviousQuestInfo(QuestId QuestId, byte Sequence = 0);
 }
index 9aa04a7e669c73277f92f9c4b61d5395c16070fb..715c045fc9ceb52c72cb4743da2d8ee142ad9e07 100644 (file)
@@ -19,9 +19,13 @@ namespace Questionable.Windows.JournalComponents;
 
 internal sealed class QuestJournalComponent
 {
-    private readonly Dictionary<JournalData.Genre, (int Available, int Completed)> _genreCounts = new();
-    private readonly Dictionary<JournalData.Category, (int Available, int Completed)> _categoryCounts = new();
-    private readonly Dictionary<JournalData.Section, (int Available, int Completed)> _sectionCounts = new();
+    private readonly Dictionary<JournalData.Genre, (int Available, int Obtainable, int Completed)> _genreCounts = [];
+
+    private readonly Dictionary<JournalData.Category, (int Available, int Obtainable, int Completed)> _categoryCounts =
+        [];
+
+    private readonly Dictionary<JournalData.Section, (int Available, int Obtainable, int Completed)> _sectionCounts =
+        [];
 
     private readonly JournalData _journalData;
     private readonly QuestRegistry _questRegistry;
@@ -95,7 +99,7 @@ internal sealed class QuestJournalComponent
         if (filter.Section.QuestCount == 0)
             return;
 
-        (int supported, int completed) = _sectionCounts.GetValueOrDefault(filter.Section);
+        (int available, int obtainable, int completed) = _sectionCounts.GetValueOrDefault(filter.Section);
 
         ImGui.TableNextRow();
         ImGui.TableNextColumn();
@@ -103,9 +107,9 @@ internal sealed class QuestJournalComponent
         bool open = ImGui.TreeNodeEx(filter.Section.Name, ImGuiTreeNodeFlags.SpanFullWidth);
 
         ImGui.TableNextColumn();
-        DrawCount(supported, filter.Section.QuestCount);
+        DrawCount(available, filter.Section.QuestCount);
         ImGui.TableNextColumn();
-        DrawCount(completed, filter.Section.QuestCount);
+        DrawCount(completed, obtainable);
 
         if (open)
         {
@@ -121,7 +125,7 @@ internal sealed class QuestJournalComponent
         if (filter.Category.QuestCount == 0)
             return;
 
-        (int supported, int completed) = _categoryCounts.GetValueOrDefault(filter.Category);
+        (int available, int obtainable, int completed) = _categoryCounts.GetValueOrDefault(filter.Category);
 
         ImGui.TableNextRow();
         ImGui.TableNextColumn();
@@ -129,9 +133,9 @@ internal sealed class QuestJournalComponent
         bool open = ImGui.TreeNodeEx(filter.Category.Name, ImGuiTreeNodeFlags.SpanFullWidth);
 
         ImGui.TableNextColumn();
-        DrawCount(supported, filter.Category.QuestCount);
+        DrawCount(available, filter.Category.QuestCount);
         ImGui.TableNextColumn();
-        DrawCount(completed, filter.Category.QuestCount);
+        DrawCount(completed, obtainable);
 
         if (open)
         {
@@ -147,7 +151,7 @@ internal sealed class QuestJournalComponent
         if (filter.Genre.QuestCount == 0)
             return;
 
-        (int supported, int completed) = _genreCounts.GetValueOrDefault(filter.Genre);
+        (int supported, int obtainable, int completed) = _genreCounts.GetValueOrDefault(filter.Genre);
 
         ImGui.TableNextRow();
         ImGui.TableNextColumn();
@@ -157,7 +161,7 @@ internal sealed class QuestJournalComponent
         ImGui.TableNextColumn();
         DrawCount(supported, filter.Genre.QuestCount);
         ImGui.TableNextColumn();
-        DrawCount(completed, filter.Genre.QuestCount);
+        DrawCount(completed, obtainable);
 
         if (open)
         {
@@ -192,7 +196,8 @@ internal sealed class QuestJournalComponent
             bool openInQuestMap = _commandManager.Commands.TryGetValue("/questinfo", out var commandInfo);
             if (ImGui.MenuItem("View in Quest Map", questInfo.QuestId is QuestId && openInQuestMap))
             {
-                _commandManager.DispatchCommand("/questinfo", questInfo.QuestId.ToString() ?? string.Empty, commandInfo!);
+                _commandManager.DispatchCommand("/questinfo", questInfo.QuestId.ToString() ?? string.Empty,
+                    commandInfo!);
             }
 
             ImGui.EndPopup();
@@ -317,8 +322,9 @@ internal sealed class QuestJournalComponent
         {
             int available = genre.Quests.Count(x =>
                 _questRegistry.TryGetQuest(x.QuestId, out var quest) && !quest.Root.Disabled);
+            int obtainable = genre.Quests.Count(x => !_questFunctions.IsQuestUnobtainable(x.QuestId));
             int completed = genre.Quests.Count(x => _questFunctions.IsQuestComplete(x.QuestId));
-            _genreCounts[genre] = (available, completed);
+            _genreCounts[genre] = (available, obtainable, completed);
         }
 
         foreach (var category in _journalData.Categories)
@@ -328,8 +334,9 @@ internal sealed class QuestJournalComponent
                 .Select(x => x.Value)
                 .ToList();
             int available = counts.Sum(x => x.Available);
+            int obtainable = counts.Sum(x => x.Obtainable);
             int completed = counts.Sum(x => x.Completed);
-            _categoryCounts[category] = (available, completed);
+            _categoryCounts[category] = (available, obtainable, completed);
         }
 
         foreach (var section in _journalData.Sections)
@@ -339,21 +346,22 @@ internal sealed class QuestJournalComponent
                 .Select(x => x.Value)
                 .ToList();
             int available = counts.Sum(x => x.Available);
+            int obtainable = counts.Sum(x => x.Obtainable);
             int completed = counts.Sum(x => x.Completed);
-            _sectionCounts[section] = (available, completed);
+            _sectionCounts[section] = (available, obtainable, completed);
         }
     }
 
     internal void ClearCounts()
     {
         foreach (var genreCount in _genreCounts.ToList())
-            _genreCounts[genreCount.Key] = (genreCount.Value.Available, 0);
+            _genreCounts[genreCount.Key] = (genreCount.Value.Available, genreCount.Value.Available, 0);
 
         foreach (var categoryCount in _categoryCounts.ToList())
-            _categoryCounts[categoryCount.Key] = (categoryCount.Value.Available, 0);
+            _categoryCounts[categoryCount.Key] = (categoryCount.Value.Available, categoryCount.Value.Available, 0);
 
         foreach (var sectionCount in _sectionCounts.ToList())
-            _sectionCounts[sectionCount.Key] = (sectionCount.Value.Available, 0);
+            _sectionCounts[sectionCount.Key] = (sectionCount.Value.Available, sectionCount.Value.Available, 0);
     }
 
     private sealed record FilteredSection(JournalData.Section Section, List<FilteredCategory> Categories);
index e0155816c17f4c91620519533bcc77b05876f846..270a76d90f728b19f3c28116186640df5ebc8e03 100644 (file)
@@ -26,6 +26,8 @@ internal sealed class UiUtils
             return (ImGuiColors.DalamudYellow, FontAwesomeIcon.PersonWalkingArrowRight, "Active");
         else if (_questFunctions.IsQuestAcceptedOrComplete(elementId))
             return (ImGuiColors.ParsedGreen, FontAwesomeIcon.Check, "Complete");
+        else if (_questFunctions.IsQuestUnobtainable(elementId))
+            return (ImGuiColors.DalamudGrey, FontAwesomeIcon.Minus, "Unobtainable");
         else if (_questFunctions.IsQuestLocked(elementId))
             return (ImGuiColors.DalamudRed, FontAwesomeIcon.Times, "Locked");
         else