From bc0993198aa5a78f3e35deb8ac4c9d4588500f73 Mon Sep 17 00:00:00 2001 From: Liza Carvelli Date: Sat, 26 Jul 2025 16:36:59 +0200 Subject: [PATCH] Add option to only unlock Dusk Vigil/Shisui/ARR alliance raids instead of opening DF/waiting for completion --- .../RoslynElements/DutyOptionsExtensions.cs | 3 + .../1202_Labyrinth of the Ancients.json | 3 +- .../1474_Syrcus Tower.json | 3 +- .../494_The World of Darkness.json | 3 +- .../2111_For All the Nights to Come.json | 3 +- .../2632_The Palace of Lost Souls.json | 3 +- QuestPaths/quest-v1.json | 4 + Questionable.Model/Questing/DutyOptions.cs | 1 + Questionable/Configuration.cs | 1 + Questionable/Controller/QuestRegistry.cs | 32 +++++-- .../Controller/Steps/Interactions/Duty.cs | 3 +- Questionable/Functions/QuestFunctions.cs | 6 ++ .../ConfigComponents/DebugConfigComponent.cs | 6 +- .../GeneralConfigComponent.cs | 96 ++++++++++++++----- 14 files changed, 127 insertions(+), 40 deletions(-) diff --git a/QuestPathGenerator/RoslynElements/DutyOptionsExtensions.cs b/QuestPathGenerator/RoslynElements/DutyOptionsExtensions.cs index c7092a49..8827669e 100644 --- a/QuestPathGenerator/RoslynElements/DutyOptionsExtensions.cs +++ b/QuestPathGenerator/RoslynElements/DutyOptionsExtensions.cs @@ -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())))); } diff --git a/QuestPaths/2.x - A Realm Reborn/Alliance Raid Quests/1202_Labyrinth of the Ancients.json b/QuestPaths/2.x - A Realm Reborn/Alliance Raid Quests/1202_Labyrinth of the Ancients.json index 2c1c8191..97ceb0b1 100644 --- a/QuestPaths/2.x - A Realm Reborn/Alliance Raid Quests/1202_Labyrinth of the Ancients.json +++ b/QuestPaths/2.x - A Realm Reborn/Alliance Raid Quests/1202_Labyrinth of the Ancients.json @@ -56,7 +56,8 @@ "InteractionType": "Duty", "DutyOptions": { "ContentFinderConditionId": 92, - "Enabled": false + "Enabled": false, + "LowPriority": true } } ] diff --git a/QuestPaths/2.x - A Realm Reborn/Alliance Raid Quests/1474_Syrcus Tower.json b/QuestPaths/2.x - A Realm Reborn/Alliance Raid Quests/1474_Syrcus Tower.json index 88c4065b..5756d8ca 100644 --- a/QuestPaths/2.x - A Realm Reborn/Alliance Raid Quests/1474_Syrcus Tower.json +++ b/QuestPaths/2.x - A Realm Reborn/Alliance Raid Quests/1474_Syrcus Tower.json @@ -99,7 +99,8 @@ "InteractionType": "Duty", "DutyOptions": { "ContentFinderConditionId": 102, - "Enabled": false + "Enabled": false, + "LowPriority": true } } ] diff --git a/QuestPaths/2.x - A Realm Reborn/Alliance Raid Quests/494_The World of Darkness.json b/QuestPaths/2.x - A Realm Reborn/Alliance Raid Quests/494_The World of Darkness.json index 5186db8d..8c4ef01a 100644 --- a/QuestPaths/2.x - A Realm Reborn/Alliance Raid Quests/494_The World of Darkness.json +++ b/QuestPaths/2.x - A Realm Reborn/Alliance Raid Quests/494_The World of Darkness.json @@ -83,7 +83,8 @@ "InteractionType": "Duty", "DutyOptions": { "ContentFinderConditionId": 111, - "Enabled": false + "Enabled": false, + "LowPriority": true } } ] diff --git a/QuestPaths/3.x - Heavensward/Aether Currents/Coerthas Western Highlands/2111_For All the Nights to Come.json b/QuestPaths/3.x - Heavensward/Aether Currents/Coerthas Western Highlands/2111_For All the Nights to Come.json index e869abdd..db7c71dc 100644 --- a/QuestPaths/3.x - Heavensward/Aether Currents/Coerthas Western Highlands/2111_For All the Nights to Come.json +++ b/QuestPaths/3.x - Heavensward/Aether Currents/Coerthas Western Highlands/2111_For All the Nights to Come.json @@ -40,7 +40,8 @@ "InteractionType": "Duty", "DutyOptions": { "ContentFinderConditionId": 36, - "Enabled": false + "Enabled": false, + "LowPriority": true } } ] diff --git a/QuestPaths/4.x - Stormblood/Aether Currents/The Ruby Sea/2632_The Palace of Lost Souls.json b/QuestPaths/4.x - Stormblood/Aether Currents/The Ruby Sea/2632_The Palace of Lost Souls.json index 9910f297..df52cc24 100644 --- a/QuestPaths/4.x - Stormblood/Aether Currents/The Ruby Sea/2632_The Palace of Lost Souls.json +++ b/QuestPaths/4.x - Stormblood/Aether Currents/The Ruby Sea/2632_The Palace of Lost Souls.json @@ -84,7 +84,8 @@ "InteractionType": "Duty", "DutyOptions": { "ContentFinderConditionId": 235, - "Enabled": false + "Enabled": false, + "LowPriority": true } } ] diff --git a/QuestPaths/quest-v1.json b/QuestPaths/quest-v1.json index 0290ac62..84d6af8f 100644 --- a/QuestPaths/quest-v1.json +++ b/QuestPaths/quest-v1.json @@ -1324,6 +1324,10 @@ "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" } diff --git a/Questionable.Model/Questing/DutyOptions.cs b/Questionable.Model/Questing/DutyOptions.cs index 12ea14e6..5f23cabc 100644 --- a/Questionable.Model/Questing/DutyOptions.cs +++ b/Questionable.Model/Questing/DutyOptions.cs @@ -6,5 +6,6 @@ public class DutyOptions { public bool Enabled { get; set; } public uint ContentFinderConditionId { get; set; } + public bool LowPriority { get; set; } public List Notes { get; set; } = []; } diff --git a/Questionable/Configuration.cs b/Questionable/Configuration.cs index fdfef8b5..1aae915c 100644 --- a/Questionable/Configuration.cs +++ b/Questionable/Configuration.cs @@ -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; } diff --git a/Questionable/Controller/QuestRegistry.cs b/Questionable/Controller/QuestRegistry.cs index 11fc771d..c9425c7c 100644 --- a/Questionable/Controller/QuestRegistry.cs +++ b/Questionable/Controller/QuestRegistry.cs @@ -33,6 +33,7 @@ internal sealed class QuestRegistry private readonly ICallGateProvider _reloadDataIpc; private readonly Dictionary _quests = []; private readonly Dictionary _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); + } } } } diff --git a/Questionable/Controller/Steps/Interactions/Duty.cs b/Questionable/Controller/Steps/Interactions/Duty.cs index 250c015c..8c4221fa 100644 --- a/Questionable/Controller/Steps/Interactions/Duty.cs +++ b/Questionable/Controller/Steps/Interactions/Duty.cs @@ -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); } } } diff --git a/Questionable/Functions/QuestFunctions.cs b/Questionable/Functions/QuestFunctions.cs index 0bbd041b..dbeb55a5 100644 --- a/Questionable/Functions/QuestFunctions.cs +++ b/Questionable/Functions/QuestFunctions.cs @@ -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 = diff --git a/Questionable/Windows/ConfigComponents/DebugConfigComponent.cs b/Questionable/Windows/ConfigComponents/DebugConfigComponent.cs index 6a6ddf2e..e86fbafd 100644 --- a/Questionable/Windows/ConfigComponents/DebugConfigComponent.cs +++ b/Questionable/Windows/ConfigComponents/DebugConfigComponent.cs @@ -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)) diff --git a/Questionable/Windows/ConfigComponents/GeneralConfigComponent.cs b/Questionable/Windows/ConfigComponents/GeneralConfigComponent.cs index c63798ee..7686c54c 100644 --- a/Questionable/Windows/ConfigComponents/GeneralConfigComponent.cs +++ b/Questionable/Windows/ConfigComponents/GeneralConfigComponent.cs @@ -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() .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}"); + } + } + } + } } } } -- 2.20.1