From 348460a7c61325f18de6129938c585574c67255e Mon Sep 17 00:00:00 2001
From: Liza Carvelli <liza@carvel.li>
Date: Sat, 10 Aug 2024 18:35:54 +0200
Subject: [PATCH] Include expansionversion and NG+ chapter in QuestInfo

---
 GatheringPathRenderer/EditorCommands.cs      |  2 +-
 Questionable.Model/EExpansionVersion.cs      | 27 ++++++
 Questionable.Model/ExpansionVersion.cs       | 16 ----
 Questionable/Controller/QuestRegistry.cs     |  8 ++
 Questionable/Data/QuestData.cs               | 96 +++++++++++++++++++-
 Questionable/Model/IQuestInfo.cs             |  1 +
 Questionable/Model/LeveInfo.cs               |  2 +
 Questionable/Model/QuestInfo.cs              |  6 +-
 Questionable/Model/SatisfactionSupplyInfo.cs |  2 +
 9 files changed, 141 insertions(+), 19 deletions(-)
 create mode 100644 Questionable.Model/EExpansionVersion.cs
 delete mode 100644 Questionable.Model/ExpansionVersion.cs

diff --git a/GatheringPathRenderer/EditorCommands.cs b/GatheringPathRenderer/EditorCommands.cs
index 792a3ab37..75c50db60 100644
--- a/GatheringPathRenderer/EditorCommands.cs
+++ b/GatheringPathRenderer/EditorCommands.cs
@@ -175,7 +175,7 @@ internal sealed class EditorCommands : IDisposable
         {
             var territoryInfo = _dataManager.GetExcelSheet<TerritoryType>()!.GetRow(_clientState.TerritoryType)!;
             targetFolder = _plugin.PathsDirectory
-                .CreateSubdirectory(ExpansionData.ExpansionFolders[(byte)territoryInfo.ExVersion.Row])
+                .CreateSubdirectory(ExpansionData.ExpansionFolders[(EExpansionVersion)territoryInfo.ExVersion.Row])
                 .CreateSubdirectory(territoryInfo.PlaceName.Value!.Name.ToString());
         }
 
diff --git a/Questionable.Model/EExpansionVersion.cs b/Questionable.Model/EExpansionVersion.cs
new file mode 100644
index 000000000..038d43ec4
--- /dev/null
+++ b/Questionable.Model/EExpansionVersion.cs
@@ -0,0 +1,27 @@
+using System.Collections.Generic;
+
+namespace Questionable.Model;
+
+public enum EExpansionVersion : byte
+{
+    ARealmReborn = 0,
+    Heavensward = 1,
+    Stormblood = 2,
+    Shadowbringers = 3,
+    Endwalker = 4,
+    Dawntrail = 5
+}
+
+public static class ExpansionData
+{
+    public static IReadOnlyDictionary<EExpansionVersion, string> ExpansionFolders =
+        new Dictionary<EExpansionVersion, string>
+        {
+            { EExpansionVersion.ARealmReborn, "2.x - A Realm Reborn" },
+            { EExpansionVersion.Heavensward, "3.x - Heavensward" },
+            { EExpansionVersion.Stormblood, "4.x - Stormblood" },
+            { EExpansionVersion.Shadowbringers, "5.x - Shadowbringers" },
+            { EExpansionVersion.Endwalker, "6.x - Endwalker" },
+            { EExpansionVersion.Dawntrail, "7.x - Dawntrail" }
+        };
+}
diff --git a/Questionable.Model/ExpansionVersion.cs b/Questionable.Model/ExpansionVersion.cs
deleted file mode 100644
index fe92f1604..000000000
--- a/Questionable.Model/ExpansionVersion.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using System.Collections.Generic;
-
-namespace Questionable.Model;
-
-public static class ExpansionData
-{
-    public static IReadOnlyDictionary<byte, string> ExpansionFolders = new Dictionary<byte, string>()
-    {
-        { 0, "2.x - A Realm Reborn" },
-        { 1, "3.x - Heavensward" },
-        { 2, "4.x - Stormblood" },
-        { 3, "5.x - Shadowbringers" },
-        { 4, "6.x - Endwalker" },
-        { 5, "7.x - Dawntrail" }
-    };
-}
diff --git a/Questionable/Controller/QuestRegistry.cs b/Questionable/Controller/QuestRegistry.cs
index 713f5d7bc..3ccd3ee37 100644
--- a/Questionable/Controller/QuestRegistry.cs
+++ b/Questionable/Controller/QuestRegistry.cs
@@ -8,6 +8,7 @@ using System.Text.Json;
 using System.Text.Json.Nodes;
 using Dalamud.Plugin;
 using Dalamud.Plugin.Ipc;
+using LLib.GameData;
 using Microsoft.Extensions.Logging;
 using Questionable.Data;
 using Questionable.Model;
@@ -203,4 +204,11 @@ internal sealed class QuestRegistry
 
     public bool TryGetQuest(ElementId questId, [NotNullWhen(true)] out Quest? quest)
         => _quests.TryGetValue(questId, out quest);
+
+    public List<QuestInfo> GetKnownClassJobQuests(EClassJob classJob)
+    {
+        return _questData.GetClassJobQuests(classJob)
+            .Where(x => IsKnownQuest(x.QuestId))
+            .ToList();
+    }
 }
diff --git a/Questionable/Data/QuestData.cs b/Questionable/Data/QuestData.cs
index 8f42db9aa..10224d1ac 100644
--- a/Questionable/Data/QuestData.cs
+++ b/Questionable/Data/QuestData.cs
@@ -15,12 +15,17 @@ internal sealed class QuestData
 
     public QuestData(IDataManager dataManager)
     {
+        Dictionary<uint, ushort> questChapters =
+            dataManager.GetExcelSheet<QuestChapter>()!
+                .Where(x => x.RowId > 0 && x.Quest.Row > 0)
+                .ToDictionary(x => x.Quest.Row, x => x.Redo);
+
         List<IQuestInfo> quests =
         [
             ..dataManager.GetExcelSheet<Quest>()!
                 .Where(x => x.RowId > 0)
                 .Where(x => x.IssuerLocation.Row > 0)
-                .Select(x => new QuestInfo(x)),
+                .Select(x => new QuestInfo(x, questChapters.GetValueOrDefault(x.RowId))),
             ..dataManager.GetExcelSheet<SatisfactionNpc>()!
                 .Where(x => x.RowId > 0)
                 .Select(x => new SatisfactionSupplyInfo(x)),
@@ -56,4 +61,93 @@ internal sealed class QuestData
             .ThenBy(x => x.QuestId)
             .ToList();
     }
+
+    public List<QuestInfo> GetClassJobQuests(EClassJob classJob)
+    {
+        List<ushort> chapterIds = classJob switch
+        {
+            EClassJob.Adventurer => throw new ArgumentOutOfRangeException(nameof(classJob)),
+
+            // ARR
+            EClassJob.Gladiator => [63],
+            EClassJob.Paladin => [72, 73, 74, 75],
+            EClassJob.Marauder => [64],
+            EClassJob.Warrior => [76, 77, 78, 79],
+            EClassJob.Conjurer => [65],
+            EClassJob.WhiteMage => [86, 87, 88, 89],
+            EClassJob.Arcanist => [66],
+            EClassJob.Summoner => [127, 128, 129, 130],
+            EClassJob.Scholar => [90, 91, 92, 93],
+            EClassJob.Pugilist => [67],
+            EClassJob.Monk => [98, 99, 100, 101],
+            EClassJob.Lancer => [68],
+            EClassJob.Dragoon => [102, 103, 104, 105],
+            EClassJob.Rogue => [69],
+            EClassJob.Ninja => [106, 107, 108, 109],
+            EClassJob.Archer => [70],
+            EClassJob.Bard => [113, 114, 115, 116],
+            EClassJob.Thaumaturge => [71],
+            EClassJob.BlackMage => [123, 124, 125, 126],
+
+            // HW
+            EClassJob.DarkKnight => [80, 81, 82, 83],
+            EClassJob.Astrologian => [94, 95, 96, 97],
+            EClassJob.Machinist => [117, 118, 119, 120],
+
+            // SB
+            EClassJob.Samurai => [110, 111, 112],
+            EClassJob.RedMage => [131, 132, 133],
+            EClassJob.BlueMage => [134, 135, 146, 170],
+
+            // ShB
+            EClassJob.Gunbreaker => [84, 85],
+            EClassJob.Dancer => [121, 122],
+
+            // EW
+            EClassJob.Sage => [152],
+            EClassJob.Reaper => [153],
+
+            // DT
+            EClassJob.Viper => [176],
+            EClassJob.Pictomancer => [177],
+
+            // Crafter
+            EClassJob.Alchemist => [48, 49, 50],
+            EClassJob.Armorer => [36, 37, 38],
+            EClassJob.Blacksmith => [33, 34, 35],
+            EClassJob.Carpenter => [30, 31, 32],
+            EClassJob.Culinarian => [51, 52, 53],
+            EClassJob.Goldsmith => [39, 40, 41],
+            EClassJob.Leatherworker => [42, 43, 44],
+            EClassJob.Weaver => [45, 46, 47],
+
+            // Gatherer
+            EClassJob.Miner => [54, 55, 56],
+            EClassJob.Botanist => [57, 58, 59],
+            EClassJob.Fisher => [60, 61, 62],
+
+            _ => throw new ArgumentOutOfRangeException(nameof(classJob)),
+        };
+
+        chapterIds.AddRange(classJob switch
+        {
+            _ when classJob.IsTank() => [136, 154, 178],
+            _ when classJob.IsHealer() => [137, 155, 179],
+            _ when classJob.IsMelee() => [138, 156, 180],
+            _ when classJob.IsPhysicalRanged() => [138, 157, 181],
+            _ when classJob.IsCaster() && classJob != EClassJob.BlueMage => [139, 158, 182],
+            _ => []
+        });
+
+        return GetQuestsInNewGamePlusChapters(chapterIds);
+    }
+
+    private List<QuestInfo> GetQuestsInNewGamePlusChapters(List<ushort> chapterIds)
+    {
+        return _quests.Values
+            .Where(x => x is QuestInfo)
+            .Cast<QuestInfo>()
+            .Where(x => chapterIds.Contains(x.NewGamePlusChapter))
+            .ToList();
+    }
 }
diff --git a/Questionable/Model/IQuestInfo.cs b/Questionable/Model/IQuestInfo.cs
index 1410de30f..b4b362e8e 100644
--- a/Questionable/Model/IQuestInfo.cs
+++ b/Questionable/Model/IQuestInfo.cs
@@ -16,6 +16,7 @@ public interface IQuestInfo
     public EBeastTribe BeastTribe { get; }
     public bool IsMainScenarioQuest { get; }
     public IReadOnlyList<EClassJob> ClassJobs { get; }
+    public EExpansionVersion Expansion { get; }
 
     public string SimplifiedName => Name
         .Replace(".", "", StringComparison.Ordinal)
diff --git a/Questionable/Model/LeveInfo.cs b/Questionable/Model/LeveInfo.cs
index 2be46ecbc..499402168 100644
--- a/Questionable/Model/LeveInfo.cs
+++ b/Questionable/Model/LeveInfo.cs
@@ -14,6 +14,7 @@ internal sealed class LeveInfo : IQuestInfo
         Level = leve.ClassJobLevel;
         IssuerDataId = leve.LevelLevemete.Value!.Object;
         ClassJobs = QuestInfoUtils.AsList(leve.ClassJobCategory.Value!);
+        Expansion = (EExpansionVersion)leve.LevelLevemete.Value.Territory.Value!.ExVersion.Row;
     }
 
     public ElementId QuestId { get; }
@@ -24,4 +25,5 @@ internal sealed class LeveInfo : IQuestInfo
     public EBeastTribe BeastTribe => EBeastTribe.None;
     public bool IsMainScenarioQuest => false;
     public IReadOnlyList<EClassJob> ClassJobs { get; }
+    public EExpansionVersion Expansion { get; }
 }
diff --git a/Questionable/Model/QuestInfo.cs b/Questionable/Model/QuestInfo.cs
index 769ad7cbc..504c0e570 100644
--- a/Questionable/Model/QuestInfo.cs
+++ b/Questionable/Model/QuestInfo.cs
@@ -11,7 +11,7 @@ namespace Questionable.Model;
 
 internal sealed class QuestInfo : IQuestInfo
 {
-    public QuestInfo(ExcelQuest quest)
+    public QuestInfo(ExcelQuest quest, ushort newGamePlusChapter)
     {
         QuestId = new QuestId((ushort)(quest.RowId & 0xFFFF));
 
@@ -54,6 +54,8 @@ internal sealed class QuestInfo : IQuestInfo
         BeastTribe = (EBeastTribe)quest.BeastTribe.Row;
         ClassJobs = QuestInfoUtils.AsList(quest.ClassJobCategory0.Value!);
         IsSeasonalEvent = quest.Festival.Row != 0;
+        NewGamePlusChapter = newGamePlusChapter;
+        Expansion = (EExpansionVersion)quest.Expansion.Row;
     }
 
 
@@ -76,6 +78,8 @@ internal sealed class QuestInfo : IQuestInfo
     public EBeastTribe BeastTribe { get; }
     public IReadOnlyList<EClassJob> ClassJobs { get; }
     public bool IsSeasonalEvent { get; }
+    public ushort NewGamePlusChapter { get; }
+    public EExpansionVersion Expansion { get; }
 
     [UsedImplicitly(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.Members)]
     public enum QuestJoin : byte
diff --git a/Questionable/Model/SatisfactionSupplyInfo.cs b/Questionable/Model/SatisfactionSupplyInfo.cs
index 810437c63..b46061ad0 100644
--- a/Questionable/Model/SatisfactionSupplyInfo.cs
+++ b/Questionable/Model/SatisfactionSupplyInfo.cs
@@ -13,6 +13,7 @@ internal sealed class SatisfactionSupplyInfo : IQuestInfo
         Name = npc.Npc.Value!.Singular;
         IssuerDataId = npc.Npc.Row;
         Level = npc.LevelUnlock;
+        Expansion = (EExpansionVersion)npc.QuestRequired.Value!.Expansion.Row;
     }
 
     public ElementId QuestId { get; }
@@ -22,6 +23,7 @@ internal sealed class SatisfactionSupplyInfo : IQuestInfo
     public ushort Level { get; }
     public EBeastTribe BeastTribe => EBeastTribe.None;
     public bool IsMainScenarioQuest => false;
+    public EExpansionVersion Expansion { get; }
 
     /// <summary>
     /// We don't have collectables implemented for any other class.