using Lumina.Excel.GeneratedSheets; using System.Collections.Generic; using System.Linq; using FFXIVClientStructs.FFXIV.Client.Game; namespace QuestMap { internal class Filters { private Plugin Plugin; private List FilterConditions { get; } = new List { // starting city questlines new OneQuestOf(65_575, 65_643, 66_130), // Coming to [starting city] new OneQuestOf(65_621, 65_644, 65_645, 65_659, 65_660, 66_104, 66_105, 66_106), // Close to Home (one per starting class) #region Gridania new ConditionalQuests( 65_575, // Coming to Gridania // MSQ 65_564, // To the Bannock 65_737, // Passing Muster 65_981, // Chasing Shadows 65_664, // Eggs over Queasy (old) 69_390, // Eggs over Queasy 65_711, // Surveying the Damage 65_734, // Butcher of Greentear (removed) 65_661, // A Soldier's Breakfast (old) 69_391, // A Soldier's Breakfast 65_665, // Spirithold Broken 65_712, // On the Bentbranch 65_912, // You Shall Not Trespass 65_913, // Don't Look Down 65_915, // In the Grim Darkness of the Forest 65_916, // Threat Level Elevated 65_917, // Migrant Marauders 65_920, // A Hearer Is Often Late 65_923, // Salvaging the Scene 65_697, // Leia's Legacy 65_982, // Dread is in the Air 65_983, // To Guard a Guardian 65_984, // Festive Endeavors 65_985, // Renewing the Covenant 66_043, // The Gridanian Envoy 66_210, // Call of the Sea, used in both Gridania & Limsa // side quests 65_756, // Leves of Bentbranch 65_596, // Simply the Hest 65_537, // A Good Adventurer is Hard to Find 65_573, // Population Control 65_568, // A Hard Nut to Crack 65_570, // Derision of Labor 65_732, // An Eft for Effort 65_708, // More than a Flesh Wound 65_663, // Parasite Cleave 65_666, // Soil Despoilers 65_914 // Look, but Won't Touch // This may not be a complete list, my main char is from Gridania so this doesn't matter much to me ), new ReplacedQuest(65_664, 69_390), // Eggs over Queasy new ReplacedQuest(65_661, 69_391), // A Soldier's Breakfast new UnobtainableQuest(66_964), // He's Got a Ticket to Ride new UnobtainableQuest(65_734), // Butcher of Greentear new UnobtainableQuest(65_910), // Feeding Time new UnobtainableQuest(65_692), // Ruffled Feathers new UnobtainableQuest(65_918), // Skeletons in My Deepcroft new UnobtainableQuest(65_695), // Lights Out #endregion Gridania #region Limsa new ConditionalQuests( 65_643, // Coming to Limsa Lominsa // MSQ 65_998, // On to Summerford 65_999, // Dressed to Call 66_079, // Lurkers in the Grotto 66_001, // Washed Up 66_002, // Double Dealing 66_003, // Loam Maintenance 66_004, // Plowshares to Swords 66_005, // Just Deserts 65_933, // Sky-high 65_938, // Thanks a Million 65_939, // Relighting the Torch 65_942, // On the Drydocks 65_948, // Without a Doubt 65_951, // Righting the Shipwright 65_949, // Do Angry Pirates Dream 65_950, // Victory in Peril 66_225, // Men of the Blue Tattoos 66_080, // Feint and Strike 66_226, // High Society 66_081, // A Mizzenmast Repast 66_082, // The Lominsan Envoy 66_210, // Call of the Sea, used in both Gridania & Limsa // side quests 66_229, // Leves of Swiftperch 65_595, // Simply the Hest 65_647, // Making a Name 65_648, // Suspiciously Sober 65_658, // Glory Days 66_199, // Fool Me Once 66_008, // The Time Is Ripe 66_009, // Orange Crush 66_000, // Further Afield 66_006, // Yarzon, Yarzoff 66_001, // Washed Up, 66_002, // Double Dealing 66_010, // A Pound of Cure 66_023, // Meet, Greet, and Deceit 65_936, // Keeping it Fresh 65_937, // Baiting Buzzards 65_940, // Farmer of Fortune 65_941, // Need to Breed 66_011, // Cry Me a Liver 66_012, // Wool's Errand 66_013, // Flour Power 66_014, // Kobolds and Ingredients 66_015, // Bait and Swat 66_022 // Kobolds Ahoy ), new UnobtainableQuest(65_934), // Courier for a Day #endregion Limsa #region Ul'dah new ConditionalQuests( 66_130, // Coming to Ul'dah // MSQ 66_131, // We Must Rebuild 66_207, // Nothing to See Here 66_086, // Underneath the Sultantree 65_839, // Step Nine 65_842, // Prudence at this Junction (old) 69_388, // Prudence at this Junction 65_843, // Out of House and Home 65_856, // Way Down in the Hole 66_159, // Takin' What They're Givin' 65_864, // Supply & Demand 66_039, // Give It to Me Raw 65_865, // The Perfect Swarm 65_866, // The Letter to Lost Hope 65_867, // Heir Today, Gone Tomorrow (old) 69_389, // Heir Today, Gone Tomorrow 65_868, // Passing the Blade 65_869, // Following Footfalls 65_870, // Storms on the Horizon 65_872, // Oh Captain, My Captain 66_164, // Secrets and Lies 66_087, // Duty, Honor, Country 66_177, // A Matter of Tradition 66_088, // A Royal Reception 66_064, // The Ul'dahn Envoy 66_209, // Call of the Sea, used only in Ul'dah // side quests 66_223, // Leves of Horizon 65_594, // Simply the Hest 65_857, // The Slaying of the Shrews 65_858, // Listen to Your Fish 65_840, // The Tasting of the Shrews 65_862, // Sometimes the Old Ways Are Best 65_844, // In the Court of the Rat King 65_877, // The Scent of a Mongrel 65_878, // Lominsan Rubbish 65_924, // Bloodied and Bowed 65_925, // Fantastic Voyage 65_926, // Catch Your Breath 66_040, // No Tool Gets Left Behind 66_041, // Angling Ankle-Biters 66_042, // This Fishwife's Life 66_066, // Alternative Medicine 66_067, // Need Not Apply in Person 66_109 // Fringe Benefits ), new UnobtainableQuest(65_860), // Disorderly Conduct new UnobtainableQuest(65_841), // Until a Quieter Time new UnobtainableQuest(65_863), // Spriggan Cleaning new UnobtainableQuest(65_871), // Compulsory Catering new UnobtainableQuest(66_965), // Lend Me Your Ears Already new ReplacedQuest(65_842, 69_388), // Prudence at this Junction new ReplacedQuest(65_867, 69_389), // Heir Today, Gone Tomorrow #endregion Ul'dah // Grand Company specific quests - can only complete them for one GC, even if you switch later // Shadows Uncast & Gilding the Bilbious can be done once per GC, so they're not exclusive new OneQuestOf(66_216, 66_217, 66_218), // The Company You Keep new OneQuestOf(66_219, 66_220, 66_221), // The Company You Keep follow-up quest new OneQuestOf(66_236, 66_237, 66_238), // My Little Chocobo new OneQuestOf(66_640, 66_641, 66_642), // A Pup No Longer new OneQuestOf(67_001, 67_002, 67_003), // Call of the Wild new OneQuestOf(67_063, 67_064, 67_065), // Like Civilized Men and Women new OneQuestOf(67_099, 67_100, 67_101), // Let the Hunt Begin new OneQuestOf(67_925, 67_926, 67_927), // Squadron and Commander // class quests: depending on whether or not it's your starting class, you get a different quest // Format: ([quest id if this was your starting class], [so you want to be a lancer], [way of the lancer, if picked up later], [your first spear]) new StartingJobQuest(65_557, 65_717, 65_667, 65_755), // ARC new StartingJobQuest(65_558, 65_718, 65_669, 65_747), // CNJ // seems odd that the replaced if for CNJ & LNC aren't the same order as the original ids new StartingJobQuest(65_559, 65_716, 65_668, 65_754), // LNC new StartingJobQuest(65_789, 65_713, 65_821, 65_822), // GLA new StartingJobQuest(65_847, 65_715, 65_846, 65_848), // MRD new StartingJobQuest(65_881, 65_719, 65_880, 65_882), // THM new StartingJobQuest(65_989, 65_987, 65_988, 65_990), // ACN new StartingJobQuest(66_069, 65_714, 66_068, 66_089), // PGL // duplicate quests - unsure what happened to those, as the quest ids are very close; might be dependent on your starting job/city but isn't a thing for all classes new OneQuestOf(65_571, 65_679), // LNC: Courage of Stone new OneQuestOf(65_850, 65_851), // MRD: Wake of Destruction new OneQuestOf(65_884, 65_885), // THM: The Threat of Paucity new OneQuestOf(65_992, 65_993), // ACN: Tactical Planning new OneQuestOf(65_797, 65_824), // GLA: Ul'dah's Most Wanted, large id difference new OneQuestOf(66_091, 66_234), // PGL: Burning Up the Quarter Malm // quest choices new OneQuestOf(66_968, 66_969, 66_970), // An Ill-conceived Venture new OneQuestOf(69_256, 69_257), // NieR: Tails, You Lose -- Heads, I Win new OneQuestOf(69_336, 69_337), // Qitari: First Stela new OneQuestOf(69_338, 69_339), // Qitari: Second Stela new OneQuestOf(69_340, 69_341), // Qitari: Third Stela new IslandSanctuaryQuest(), // Ishgard Restoration, I think this was something to do with interacting with the board when the relvant stage was finished // These aren't in the journal, and they don't have any wiki pages either. As of 6.28, none of them seems obtainable. new UnobtainableQuest(69_296), // The Mendicant's Court new UnobtainableQuest(69_377), // The New Nest new UnobtainableQuest(69_508), // Featherfall new UnobtainableQuest(69_578), // The Risensong Quarter // updated quests new ReplacedQuest(65_603, 65_670), // BRD: School of Hard Nooks -> Training with Leih new ReplacedQuest(65_627, 65_683), // WHM: Trial by Wind new ReplacedQuest(66_957, 68_553), // Glamour unlock new ReplacedQuest(66_958, 68_554), // Glamour prism crafting new ReplacedQuest(66_097, 67_823), // ARR Relics: The Vital Title new UnobtainableQuest(67_819), // A Seat at the Feast new UnobtainableQuest(66_033), // But I Hardly Noah new UnobtainableQuest(66_034), // The Gift of the Archmagus }; public Filters(Plugin plugin) { Plugin = plugin; } public bool IsObtainable(Quest quest) { if (QuestManager.IsQuestComplete(quest.RowId)) return true; if (FilterConditions.Any(f => f.IsUnobtainable(Plugin, quest.RowId))) return false; return true; } internal interface IFiltered { bool IsUnobtainable(Plugin plugin, uint quest); } internal class OneQuestOf : IFiltered { public List QuestIds { get; } public OneQuestOf(params uint[] questIds) { QuestIds = questIds.ToList(); } public bool IsUnobtainable(Plugin plugin, uint quest) { return QuestIds.Contains(quest) && QuestIds.Any(QuestManager.IsQuestComplete); } } internal class ReplacedQuest : IFiltered { public uint OriginalQuestId { get; } public uint NewQuestId { get; } public ReplacedQuest(uint originalQuestId, uint newQuestId) { OriginalQuestId = originalQuestId; NewQuestId = newQuestId; } public bool IsUnobtainable(Plugin plugin, uint quest) { return quest == OriginalQuestId || (quest == NewQuestId && QuestManager.IsQuestComplete(OriginalQuestId)); } } internal class ConditionalQuests : IFiltered { public uint RequiredQuest { get; } public List FollowUpQuests { get; } public ConditionalQuests(uint requiredQuest, params uint[] followUpQuestIds) { RequiredQuest = requiredQuest; FollowUpQuests = followUpQuestIds.ToList(); } public bool IsUnobtainable(Plugin plugin, uint quest) { if (QuestManager.IsQuestComplete(RequiredQuest)) return false; return FollowUpQuests.Contains(quest); } } internal class StartingJobQuest : IFiltered { public uint StartingClassQuestId; public List LaterClassQuestIds; public StartingJobQuest(uint wayOfTheMsqQuestId, uint soYouWantToBeAQuestId, uint wayOfTheSideQuestId, uint myFirstWeaponQuestId) { StartingClassQuestId = wayOfTheMsqQuestId; LaterClassQuestIds = new List { soYouWantToBeAQuestId, wayOfTheSideQuestId, myFirstWeaponQuestId }; } public bool IsUnobtainable(Plugin plugin, uint quest) { if (QuestManager.IsQuestComplete(StartingClassQuestId)) return LaterClassQuestIds.Contains(quest); else return quest == StartingClassQuestId; } } internal class UnobtainableQuest : IFiltered { public uint QuestId { get; } public UnobtainableQuest(uint questId) { this.QuestId = questId; } public bool IsUnobtainable(Plugin plugin, uint quest) { return quest == QuestId; } } internal class IslandSanctuaryQuest : IFiltered { // Seeing the Cieldalaes doesn't seem to be available after unlocking the Island Sanctuary // - Seeking the Cieldalaes is unlockable after the Praetorium // - Seeking Sanctuary is unlockable after 6.0 public bool IsUnobtainable(Plugin plugin, uint quest) { return quest == 70_180 && QuestManager.IsQuestComplete(70_179); } } } }