diff --git a/GatheringPathRenderer/EditorCommands.cs b/GatheringPathRenderer/EditorCommands.cs index 792a3ab3..75c50db6 100644 --- a/GatheringPathRenderer/EditorCommands.cs +++ b/GatheringPathRenderer/EditorCommands.cs @@ -175,7 +175,7 @@ internal sealed class EditorCommands : IDisposable { var territoryInfo = _dataManager.GetExcelSheet()!.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 00000000..038d43ec --- /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 ExpansionFolders = + new Dictionary + { + { 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 fe92f160..00000000 --- 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 ExpansionFolders = new Dictionary() - { - { 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 713f5d7b..3ccd3ee3 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 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 8f42db9a..10224d1a 100644 --- a/Questionable/Data/QuestData.cs +++ b/Questionable/Data/QuestData.cs @@ -15,12 +15,17 @@ internal sealed class QuestData public QuestData(IDataManager dataManager) { + Dictionary questChapters = + dataManager.GetExcelSheet()! + .Where(x => x.RowId > 0 && x.Quest.Row > 0) + .ToDictionary(x => x.Quest.Row, x => x.Redo); + List quests = [ ..dataManager.GetExcelSheet()! .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()! .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 GetClassJobQuests(EClassJob classJob) + { + List 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 GetQuestsInNewGamePlusChapters(List chapterIds) + { + return _quests.Values + .Where(x => x is QuestInfo) + .Cast() + .Where(x => chapterIds.Contains(x.NewGamePlusChapter)) + .ToList(); + } } diff --git a/Questionable/Model/IQuestInfo.cs b/Questionable/Model/IQuestInfo.cs index 1410de30..b4b362e8 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 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 2be46ecb..49940216 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 ClassJobs { get; } + public EExpansionVersion Expansion { get; } } diff --git a/Questionable/Model/QuestInfo.cs b/Questionable/Model/QuestInfo.cs index 769ad7cb..504c0e57 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 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 810437c6..b46061ad 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; } /// /// We don't have collectables implemented for any other class.