Add option to only unlock Dusk Vigil/Shisui/ARR alliance raids instead of opening...
authorLiza Carvelli <liza@carvel.li>
Sat, 26 Jul 2025 14:36:59 +0000 (16:36 +0200)
committerLiza Carvelli <liza@carvel.li>
Sat, 26 Jul 2025 14:36:59 +0000 (16:36 +0200)
14 files changed:
QuestPathGenerator/RoslynElements/DutyOptionsExtensions.cs
QuestPaths/2.x - A Realm Reborn/Alliance Raid Quests/1202_Labyrinth of the Ancients.json
QuestPaths/2.x - A Realm Reborn/Alliance Raid Quests/1474_Syrcus Tower.json
QuestPaths/2.x - A Realm Reborn/Alliance Raid Quests/494_The World of Darkness.json
QuestPaths/3.x - Heavensward/Aether Currents/Coerthas Western Highlands/2111_For All the Nights to Come.json
QuestPaths/4.x - Stormblood/Aether Currents/The Ruby Sea/2632_The Palace of Lost Souls.json
QuestPaths/quest-v1.json
Questionable.Model/Questing/DutyOptions.cs
Questionable/Configuration.cs
Questionable/Controller/QuestRegistry.cs
Questionable/Controller/Steps/Interactions/Duty.cs
Questionable/Functions/QuestFunctions.cs
Questionable/Windows/ConfigComponents/DebugConfigComponent.cs
Questionable/Windows/ConfigComponents/GeneralConfigComponent.cs

index c7092a4..8827669 100644 (file)
@@ -24,6 +24,9 @@ internal static class DutyOptionsExtensions
                             Assignment(nameof(DutyOptions.ContentFinderConditionId),
                                     dutyOptions.ContentFinderConditionId, emptyOptions.ContentFinderConditionId)
                                 .AsSyntaxNodeOrToken(),
+                            Assignment(nameof(DutyOptions.LowPriority),
+                                    dutyOptions.LowPriority, emptyOptions.LowPriority)
+                                .AsSyntaxNodeOrToken(),
                             AssignmentList(nameof(DutyOptions.Notes), dutyOptions.Notes)
                                 .AsSyntaxNodeOrToken()))));
     }
index 2c1c819..97ceb0b 100644 (file)
@@ -56,7 +56,8 @@
           "InteractionType": "Duty",
           "DutyOptions": {
             "ContentFinderConditionId": 92,
-            "Enabled": false
+            "Enabled": false,
+            "LowPriority": true
           }
         }
       ]
index 88c4065..5756d8c 100644 (file)
@@ -99,7 +99,8 @@
           "InteractionType": "Duty",
           "DutyOptions": {
             "ContentFinderConditionId": 102,
-            "Enabled": false
+            "Enabled": false,
+            "LowPriority": true
           }
         }
       ]
index 5186db8..8c4ef01 100644 (file)
@@ -83,7 +83,8 @@
           "InteractionType": "Duty",
           "DutyOptions": {
             "ContentFinderConditionId": 111,
-            "Enabled": false
+            "Enabled": false,
+            "LowPriority": true
           }
         }
       ]
index e869abd..db7c71d 100644 (file)
@@ -40,7 +40,8 @@
           "InteractionType": "Duty",
           "DutyOptions": {
             "ContentFinderConditionId": 36,
-            "Enabled": false
+            "Enabled": false,
+            "LowPriority": true
           }
         }
       ]
index 9910f29..df52cc2 100644 (file)
@@ -84,7 +84,8 @@
           "InteractionType": "Duty",
           "DutyOptions": {
             "ContentFinderConditionId": 235,
-            "Enabled": false
+            "Enabled": false,
+            "LowPriority": true
           }
         }
       ]
index 0290ac6..84d6af8 100644 (file)
                     "type": "string",
                     "pattern": "^0\\.\\d+\\.\\d+\\.\\d+$"
                   },
+                  "LowPriority": {
+                    "type": "boolean",
+                    "description": "Indicates that this dungeon is included in the 'Skip certain optional dungeons and raids' option"
+                  },
                   "$": {
                     "type": "string"
                   }
index 12ea14e..5f23cab 100644 (file)
@@ -6,5 +6,6 @@ public class DutyOptions
 {
     public bool Enabled { get; set; }
     public uint ContentFinderConditionId { get; set; }
+    public bool LowPriority { get; set; }
     public List<string> Notes { get; set; } = [];
 }
index fdfef8b..1aae915 100644 (file)
@@ -35,6 +35,7 @@ internal sealed class Configuration : IPluginConfiguration
         public bool HideInAllInstances { get; set; } = true;
         public bool UseEscToCancelQuesting { get; set; } = true;
         public bool ShowIncompleteSeasonalEvents { get; set; } = true;
+        public bool SkipLowPriorityDuties { get; set; }
         public bool ConfigureTextAdvance { get; set; } = true;
     }
 
index 11fc771..c9425c7 100644 (file)
@@ -33,6 +33,7 @@ internal sealed class QuestRegistry
     private readonly ICallGateProvider<object> _reloadDataIpc;
     private readonly Dictionary<ElementId, Quest> _quests = [];
     private readonly Dictionary<uint, (ElementId QuestId, QuestStep Step)> _contentFinderConditionIds = [];
+    private readonly List<(uint ContentFinderConditionId, ElementId QuestId, int Sequence)> _lowPriorityContentFinderConditionQuests = [];
 
     public QuestRegistry(
         IDalamudPluginInterface pluginInterface,
@@ -58,6 +59,9 @@ internal sealed class QuestRegistry
     public int ValidationIssueCount => _questValidator.IssueCount;
     public int ValidationErrorCount => _questValidator.ErrorCount;
 
+    public IReadOnlyList<(uint ContentFinderConditionId, ElementId QuestId, int Sequence)>
+        LowPriorityContentFinderConditionQuests => _lowPriorityContentFinderConditionQuests;
+
     public event EventHandler? Reloaded;
 
     public void Reload()
@@ -65,6 +69,7 @@ internal sealed class QuestRegistry
         _questValidator.Reset();
         _quests.Clear();
         _contentFinderConditionIds.Clear();
+        _lowPriorityContentFinderConditionQuests.Clear();
 
         LoadQuestsFromAssembly();
         LoadQuestsFromProjectDirectory();
@@ -157,16 +162,25 @@ internal sealed class QuestRegistry
     {
         foreach (var quest in _quests.Values)
         {
-            foreach (var dutyStep in quest.AllSteps().Where(x =>
-                         x.Step.InteractionType is EInteractionType.Duty or EInteractionType.SinglePlayerDuty))
+            foreach (var dutySequence in quest.AllSequences())
             {
-                if (dutyStep.Step is { InteractionType: EInteractionType.Duty, DutyOptions: { } dutyOptions })
-                    _contentFinderConditionIds[dutyOptions.ContentFinderConditionId] =
-                        (quest.Id, dutyStep.Step);
-                else if (dutyStep.Step.InteractionType == EInteractionType.SinglePlayerDuty &&
-                         _territoryData.TryGetContentFinderConditionForSoloInstance(quest.Id,
-                             dutyStep.Step.SinglePlayerDutyIndex, out var cfcData))
-                    _contentFinderConditionIds[cfcData.ContentFinderConditionId] = (quest.Id, dutyStep.Step);
+                foreach (var dutyStep in dutySequence.Steps.Where(x =>
+                             x.InteractionType is EInteractionType.Duty or EInteractionType.SinglePlayerDuty))
+                {
+                    if (dutyStep is { InteractionType: EInteractionType.Duty, DutyOptions: { } dutyOptions })
+                    {
+                        _contentFinderConditionIds[dutyOptions.ContentFinderConditionId] = (quest.Id, dutyStep);
+                        if (dutyOptions.LowPriority)
+                        {
+                            _lowPriorityContentFinderConditionQuests.Add((dutyOptions.ContentFinderConditionId,
+                                quest.Id, dutySequence.Sequence));
+                        }
+                    }
+                    else if (dutyStep.InteractionType == EInteractionType.SinglePlayerDuty &&
+                             _territoryData.TryGetContentFinderConditionForSoloInstance(quest.Id,
+                                 dutyStep.SinglePlayerDutyIndex, out var cfcData))
+                        _contentFinderConditionIds[cfcData.ContentFinderConditionId] = (quest.Id, dutyStep);
+                }
             }
         }
     }
index 250c015..8c4221f 100644 (file)
@@ -33,7 +33,8 @@ internal static class Duty
             }
             else
             {
-                yield return new OpenDutyFinderTask(step.DutyOptions.ContentFinderConditionId);
+                if (!step.DutyOptions.LowPriority)
+                    yield return new OpenDutyFinderTask(step.DutyOptions.ContentFinderConditionId);
             }
         }
     }
index 0bbd041..dbeb55a 100644 (file)
@@ -155,6 +155,12 @@ internal sealed unsafe class QuestFunctions
 
         if (trackedQuests.Count > 0)
         {
+            if (_configuration.General.SkipLowPriorityDuties)
+            {
+                var lowPriorityQuests = _questRegistry.LowPriorityContentFinderConditionQuests;
+                trackedQuests.RemoveAll(x => lowPriorityQuests.Any(y => x.Quest == y.QuestId && x.Sequence == y.Sequence));
+            }
+
             // if we have multiple quests to turn in for an allied society, try and complete all of them
             var (firstTrackedQuest, firstTrackedSequence) = trackedQuests.First();
             EAlliedSociety firstTrackedAlliedSociety =
index 6a6ddf2..e86fbaf 100644 (file)
@@ -60,8 +60,8 @@ internal sealed class DebugConfigComponent : ConfigComponent
 
         ImGui.Separator();
 
-        ImGui.Text("AutoDuty options");
-        using (var _ = ImRaii.PushIndent())
+        ImGui.Text("AutoDuty Settings");
+        using (ImRaii.PushIndent())
         {
             ImGui.AlignTextToFramePadding();
             bool disableAutoDutyBareMode = Configuration.Advanced.DisableAutoDutyBareMode;
@@ -78,7 +78,7 @@ internal sealed class DebugConfigComponent : ConfigComponent
 
         ImGui.Separator();
         ImGui.Text("Quest/Interaction Skips");
-        using (_ = ImRaii.PushIndent())
+        using (ImRaii.PushIndent())
         {
             bool skipAetherCurrents = Configuration.Advanced.SkipAetherCurrents;
             if (ImGui.Checkbox("Don't pick up aether currents/aether current quests", ref skipAetherCurrents))
index c63798e..7686c54 100644 (file)
@@ -1,12 +1,14 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using Dalamud.Interface;
 using Dalamud.Interface.Utility.Raii;
 using Dalamud.Plugin;
 using Dalamud.Plugin.Services;
 using ImGuiNET;
 using LLib.GameData;
 using Lumina.Excel.Sheets;
+using Questionable.Controller;
 using Questionable.Data;
 using GrandCompany = FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany;
 
@@ -17,6 +19,9 @@ internal sealed class GeneralConfigComponent : ConfigComponent
     private static readonly List<(uint Id, string Name)> DefaultMounts = [(0, "Mount Roulette")];
     private static readonly List<(EClassJob ClassJob, string Name)> DefaultClassJobs = [(EClassJob.Adventurer, "Auto (highest level/item level)")];
 
+    private readonly QuestRegistry _questRegistry;
+    private readonly TerritoryData _territoryData;
+
     private readonly uint[] _mountIds;
     private readonly string[] _mountNames;
     private readonly string[] _combatModuleNames = ["None", "Boss Mod (VBM)", "Wrath Combo", "Rotation Solver Reborn"];
@@ -31,9 +36,14 @@ internal sealed class GeneralConfigComponent : ConfigComponent
         IDalamudPluginInterface pluginInterface,
         Configuration configuration,
         IDataManager dataManager,
-        ClassJobUtils classJobUtils)
+        ClassJobUtils classJobUtils,
+        QuestRegistry questRegistry,
+        TerritoryData territoryData)
         : base(pluginInterface, configuration)
     {
+        _questRegistry = questRegistry;
+        _territoryData = territoryData;
+
         var mounts = dataManager.GetExcelSheet<Mount>()
             .Where(x => x is { RowId: > 0, Icon: > 0 })
             .Select(x => (MountId: x.RowId, Name: x.Singular.ToString()))
@@ -108,33 +118,75 @@ internal sealed class GeneralConfigComponent : ConfigComponent
             Save();
         }
 
-        bool hideInAllInstances = Configuration.General.HideInAllInstances;
-        if (ImGui.Checkbox("Hide quest window in all instanced duties", ref hideInAllInstances))
+        ImGui.Separator();
+        ImGui.Text("UI");
+        using (ImRaii.PushIndent())
         {
-            Configuration.General.HideInAllInstances = hideInAllInstances;
-            Save();
-        }
+            bool hideInAllInstances = Configuration.General.HideInAllInstances;
+            if (ImGui.Checkbox("Hide quest window in all instanced duties", ref hideInAllInstances))
+            {
+                Configuration.General.HideInAllInstances = hideInAllInstances;
+                Save();
+            }
 
-        bool useEscToCancelQuesting = Configuration.General.UseEscToCancelQuesting;
-        if (ImGui.Checkbox("Use ESC to cancel questing/movement", ref useEscToCancelQuesting))
-        {
-            Configuration.General.UseEscToCancelQuesting = useEscToCancelQuesting;
-            Save();
-        }
+            bool useEscToCancelQuesting = Configuration.General.UseEscToCancelQuesting;
+            if (ImGui.Checkbox("Use ESC to cancel questing/movement", ref useEscToCancelQuesting))
+            {
+                Configuration.General.UseEscToCancelQuesting = useEscToCancelQuesting;
+                Save();
+            }
 
-        bool showIncompleteSeasonalEvents = Configuration.General.ShowIncompleteSeasonalEvents;
-        if (ImGui.Checkbox("Show details for incomplete seasonal events", ref showIncompleteSeasonalEvents))
-        {
-            Configuration.General.ShowIncompleteSeasonalEvents = showIncompleteSeasonalEvents;
-            Save();
+            bool showIncompleteSeasonalEvents = Configuration.General.ShowIncompleteSeasonalEvents;
+            if (ImGui.Checkbox("Show details for incomplete seasonal events", ref showIncompleteSeasonalEvents))
+            {
+                Configuration.General.ShowIncompleteSeasonalEvents = showIncompleteSeasonalEvents;
+                Save();
+            }
         }
 
-        bool configureTextAdvance = Configuration.General.ConfigureTextAdvance;
-        if (ImGui.Checkbox("Automatically configure TextAdvance with the recommended settings",
-                ref configureTextAdvance))
+        ImGui.Separator();
+        ImGui.Text("Questing");
+        using (ImRaii.PushIndent())
         {
-            Configuration.General.ConfigureTextAdvance = configureTextAdvance;
-            Save();
+            bool configureTextAdvance = Configuration.General.ConfigureTextAdvance;
+            if (ImGui.Checkbox("Automatically configure TextAdvance with the recommended settings",
+                    ref configureTextAdvance))
+            {
+                Configuration.General.ConfigureTextAdvance = configureTextAdvance;
+                Save();
+            }
+
+            bool skipLowPriorityInstances = Configuration.General.SkipLowPriorityDuties;
+            if (ImGui.Checkbox("Unlock certain optional dungeons and raids (instead of waiting for completion)", ref skipLowPriorityInstances))
+            {
+                Configuration.General.SkipLowPriorityDuties = skipLowPriorityInstances;
+                Save();
+            }
+
+            ImGui.SameLine();
+            using (ImRaii.PushFont(UiBuilder.IconFont))
+            {
+                ImGui.TextDisabled(FontAwesomeIcon.InfoCircle.ToIconString());
+            }
+
+            if (ImGui.IsItemHovered())
+            {
+                using (ImRaii.Tooltip())
+                {
+                    ImGui.Text("Questionable automatically picks up some optional quests (e.g. for aether currents, or the ARR alliance raids).");
+                    ImGui.Text("If this setting is enabled, Questionable will continue with other quests, instead of waiting for manual completion of the duty.");
+
+                    ImGui.Separator();
+                    ImGui.Text("This affects the following dungeons and raids:");
+                    foreach (var lowPriorityCfc in _questRegistry.LowPriorityContentFinderConditionQuests)
+                    {
+                        if (_territoryData.TryGetContentFinderCondition(lowPriorityCfc.ContentFinderConditionId, out var cfcData))
+                        {
+                            ImGui.BulletText($"{cfcData.Name}");
+                        }
+                    }
+                }
+            }
         }
     }
 }