Add quest priority window
authorLiza Carvelli <liza@carvel.li>
Sat, 24 Aug 2024 23:30:42 +0000 (01:30 +0200)
committerLiza Carvelli <liza@carvel.li>
Sat, 24 Aug 2024 23:30:42 +0000 (01:30 +0200)
Questionable/Controller/QuestController.cs
Questionable/DalamudInitializer.cs
Questionable/Functions/QuestFunctions.cs
Questionable/QuestionablePlugin.cs
Questionable/Windows/PriorityWindow.cs [new file with mode: 0644]
Questionable/Windows/QuestComponents/ActiveQuestComponent.cs

index d932b471807a81185867238db4204b64d03f272e..529b4603ada578d8eece0ac942779d178d2feb1e 100644 (file)
@@ -138,6 +138,8 @@ internal sealed class QuestController : MiniTaskController<QuestController>, IDi
     /// </summary>
     public QuestProgress? PendingQuest => _pendingQuest;
 
+    public List<Quest> ManualPriorityQuests { get; } = [];
+
     public string? DebugState { get; private set; }
 
     public void Reload()
@@ -291,7 +293,13 @@ internal sealed class QuestController : MiniTaskController<QuestController>, IDi
             }
             else
             {
-                (ElementId? currentQuestId, currentSequence) = _questFunctions.GetCurrentQuest();
+                (ElementId? currentQuestId, currentSequence) =
+                    ManualPriorityQuests
+                        .Where(x => _questFunctions.IsReadyToAcceptQuest(x.Id) || _questFunctions.IsQuestAccepted(x.Id))
+                        .Select(x =>
+                            ((ElementId?, byte)?)(x.Id, _questFunctions.GetQuestProgressInfo(x.Id)?.Sequence ?? 0))
+                        .FirstOrDefault() ??
+                    _questFunctions.GetCurrentQuest();
                 if (currentQuestId == null || currentQuestId.Value == 0)
                 {
                     if (_startedQuest != null)
index f743ac39fc3f45aadcb9067451f964fdfeccca9d..78ac865ceac77976517256193c9083a4e5c646d7 100644 (file)
@@ -36,6 +36,7 @@ internal sealed class DalamudInitializer : IDisposable
         QuestSelectionWindow questSelectionWindow,
         QuestValidationWindow questValidationWindow,
         JournalProgressWindow journalProgressWindow,
+        PriorityWindow priorityWindow,
         IToastGui toastGui,
         ILogger<DalamudInitializer> logger)
     {
@@ -55,6 +56,7 @@ internal sealed class DalamudInitializer : IDisposable
         _windowSystem.AddWindow(questSelectionWindow);
         _windowSystem.AddWindow(questValidationWindow);
         _windowSystem.AddWindow(journalProgressWindow);
+        _windowSystem.AddWindow(priorityWindow);
 
         _pluginInterface.UiBuilder.Draw += _windowSystem.Draw;
         _pluginInterface.UiBuilder.OpenMainUi += _questWindow.Toggle;
index 6fb34dda47ca905b0edf103b6d36428b9051cb8d..7e360cd69760a0cac88d82b8a319d01730829f64 100644 (file)
@@ -449,6 +449,18 @@ internal sealed unsafe class QuestFunctions
     }
 
     public bool IsQuestLocked(QuestId questId, ElementId? extraCompletedQuest = null)
+    {
+        if (IsQuestUnobtainable(questId, extraCompletedQuest))
+            return true;
+
+        var questInfo = (QuestInfo)_questData.GetQuestInfo(questId);
+        if (questInfo.GrandCompany != GrandCompany.None && questInfo.GrandCompany != GetGrandCompany())
+            return true;
+
+        return !HasCompletedPreviousQuests(questInfo, extraCompletedQuest) || !HasCompletedPreviousInstances(questInfo);
+    }
+
+    public bool IsQuestUnobtainable(QuestId questId, ElementId? extraCompletedQuest = null)
     {
         var questInfo = (QuestInfo)_questData.GetQuestInfo(questId);
         if (questInfo.QuestLocks.Count > 0)
@@ -460,13 +472,10 @@ internal sealed unsafe class QuestFunctions
                 return true;
         }
 
-        if (questInfo.GrandCompany != GrandCompany.None && questInfo.GrandCompany != GetGrandCompany())
-            return true;
-
         if (_questData.GetLockedClassQuests().Contains(questId))
             return true;
 
-        return !HasCompletedPreviousQuests(questInfo, extraCompletedQuest) || !HasCompletedPreviousInstances(questInfo);
+        return false;
     }
 
     public bool IsQuestLocked(LeveId leveId)
index db3510cf5dde6631bddea4b22b00cc0c1b494f5d..207cf2b11aae76f5d4f2685a58ceaf05b6409659 100644 (file)
@@ -208,6 +208,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
         serviceCollection.AddSingleton<QuestSelectionWindow>();
         serviceCollection.AddSingleton<QuestValidationWindow>();
         serviceCollection.AddSingleton<JournalProgressWindow>();
+        serviceCollection.AddSingleton<PriorityWindow>();
     }
 
     private static void AddQuestValidators(ServiceCollection serviceCollection)
diff --git a/Questionable/Windows/PriorityWindow.cs b/Questionable/Windows/PriorityWindow.cs
new file mode 100644 (file)
index 0000000..7c76aca
--- /dev/null
@@ -0,0 +1,223 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+using Dalamud.Interface;
+using Dalamud.Interface.Colors;
+using Dalamud.Interface.Components;
+using Dalamud.Plugin;
+using ImGuiNET;
+using LLib.ImGui;
+using Questionable.Controller;
+using Questionable.Functions;
+using Questionable.Model;
+using Questionable.Model.Questing;
+using Questionable.Windows.QuestComponents;
+
+namespace Questionable.Windows;
+
+internal sealed class PriorityWindow : LWindow
+{
+    private readonly QuestController _questController;
+    private readonly QuestRegistry _questRegistry;
+    private readonly QuestFunctions _questFunctions;
+    private readonly QuestTooltipComponent _questTooltipComponent;
+    private readonly UiUtils _uiUtils;
+    private readonly IDalamudPluginInterface _pluginInterface;
+
+    private string _searchString = string.Empty;
+    private ElementId? _draggedItem;
+
+    public PriorityWindow(QuestController questController, QuestRegistry questRegistry, QuestFunctions questFunctions,
+        QuestTooltipComponent questTooltipComponent, UiUtils uiUtils, IDalamudPluginInterface pluginInterface)
+        : base("Quest Priority###QuestionableQuestPriority")
+    {
+        _questController = questController;
+        _questRegistry = questRegistry;
+        _questFunctions = questFunctions;
+        _questTooltipComponent = questTooltipComponent;
+        _uiUtils = uiUtils;
+        _pluginInterface = pluginInterface;
+
+        Size = new Vector2(400, 400);
+        SizeCondition = ImGuiCond.Once;
+        SizeConstraints = new WindowSizeConstraints
+        {
+            MinimumSize = new Vector2(400, 400),
+            MaximumSize = new Vector2(400, 999)
+        };
+    }
+
+    public override void Draw()
+    {
+        ImGui.Text("Quests to do first:");
+        DrawQuestFilter();
+        DrawQuestList();
+        ImGui.Spacing();
+
+        ImGui.Separator();
+        ImGui.Spacing();
+        ImGui.TextWrapped(
+            "If you have an active MSQ quest, Questionable will generally try to do:");
+        ImGui.BulletText("'Priority' quests: class quests, ARR primals, ARR raids");
+        ImGui.BulletText(
+            "Supported quests in your 'To-Do list'\n(quests from your Journal that are always on-screen)");
+        ImGui.BulletText("MSQ quest (if available, unless it is marked as 'ignored'\nin your Journal)");
+        ImGui.TextWrapped(
+            "If you don't have any active MSQ quest, it will always try to pick up the next quest in the MSQ first.");
+    }
+
+    private void DrawQuestFilter()
+    {
+        ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
+        if (ImGui.BeginCombo($"##QuestSelection", "Add Quest...", ImGuiComboFlags.HeightLarge))
+        {
+            ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
+            bool addFirst = ImGui.InputTextWithHint("", "Filter...", ref _searchString, 256,
+                ImGuiInputTextFlags.AutoSelectAll | ImGuiInputTextFlags.EnterReturnsTrue);
+
+            IEnumerable<Quest> foundQuests;
+            if (!string.IsNullOrEmpty(_searchString))
+            {
+                foundQuests = _questRegistry.AllQuests
+                    .Where(x => x.Info.Name.Contains(_searchString, StringComparison.CurrentCultureIgnoreCase))
+                    .Where(x => x.Id is not QuestId questId || !_questFunctions.IsQuestUnobtainable(questId));
+            }
+            else
+            {
+                foundQuests = _questRegistry.AllQuests.Where(x => _questFunctions.IsQuestAccepted(x.Id));
+            }
+
+            foreach (var quest in foundQuests)
+            {
+                if (quest.Info.IsMainScenarioQuest || _questController.ManualPriorityQuests.Contains(quest))
+                    continue;
+
+                bool addThis = ImGui.Selectable(quest.Info.Name);
+                if (addThis || addFirst)
+                {
+                    _questController.ManualPriorityQuests.Add(quest);
+
+                    if (addFirst)
+                    {
+                        ImGui.CloseCurrentPopup();
+                        addFirst = false;
+                    }
+                }
+            }
+
+            ImGui.EndCombo();
+        }
+
+        ImGui.Spacing();
+    }
+
+    private void DrawQuestList()
+    {
+        List<Quest> priorityQuests = _questController.ManualPriorityQuests;
+        Quest? itemToRemove = null;
+        Quest? itemToAdd = null;
+        int indexToAdd = 0;
+
+        float width = ImGui.GetContentRegionAvail().X;
+        List<(Vector2 TopLeft, Vector2 BottomRight)> itemPositions = [];
+
+        for (int i = 0; i < priorityQuests.Count; ++i)
+        {
+            Vector2 topLeft = ImGui.GetCursorScreenPos() +
+                              new Vector2(0, -ImGui.GetStyle().ItemSpacing.Y / 2);
+            var quest = priorityQuests[i];
+            ImGui.PushID($"Quest{quest.Id}");
+
+            var style = _uiUtils.GetQuestStyle(quest.Id);
+            bool hovered;
+            using (var _ = _pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push())
+            {
+                ImGui.AlignTextToFramePadding();
+                ImGui.TextColored(style.Color, style.Icon.ToIconString());
+                hovered = ImGui.IsItemHovered();
+            }
+
+            ImGui.SameLine();
+            ImGui.AlignTextToFramePadding();
+            ImGui.Text(quest.Info.Name);
+            hovered |= ImGui.IsItemHovered();
+
+            if (hovered)
+                _questTooltipComponent.Draw(quest.Info);
+
+            if (priorityQuests.Count > 1)
+            {
+                ImGui.PushFont(UiBuilder.IconFont);
+                ImGui.SameLine(ImGui.GetContentRegionAvail().X +
+                               ImGui.GetStyle().WindowPadding.X -
+                               ImGui.CalcTextSize(FontAwesomeIcon.ArrowsUpDown.ToIconString()).X -
+                               ImGui.CalcTextSize(FontAwesomeIcon.Times.ToIconString()).X -
+                               ImGui.GetStyle().FramePadding.X * 4 -
+                               ImGui.GetStyle().ItemSpacing.X);
+                ImGui.PopFont();
+
+                if (_draggedItem == quest.Id)
+                {
+                    ImGuiComponents.IconButton("##Move", FontAwesomeIcon.ArrowsUpDown,
+                        ImGui.ColorConvertU32ToFloat4(ImGui.GetColorU32(ImGuiCol.ButtonActive)));
+                }
+                else
+                    ImGuiComponents.IconButton("##Move", FontAwesomeIcon.ArrowsUpDown);
+
+                if (_draggedItem == null && ImGui.IsItemActive() && ImGui.IsMouseDragging(ImGuiMouseButton.Left))
+                    _draggedItem = quest.Id;
+
+                ImGui.SameLine();
+            }
+            else
+            {
+                ImGui.PushFont(UiBuilder.IconFont);
+                ImGui.SameLine(ImGui.GetContentRegionAvail().X +
+                               ImGui.GetStyle().WindowPadding.X -
+                               ImGui.CalcTextSize(FontAwesomeIcon.Times.ToIconString()).X -
+                               ImGui.GetStyle().FramePadding.X * 2);
+                ImGui.PopFont();
+            }
+
+            if (ImGuiComponents.IconButton($"##Remove{i}", FontAwesomeIcon.Times))
+                itemToRemove = quest;
+
+            ImGui.PopID();
+
+            Vector2 bottomRight = new Vector2(topLeft.X + width,
+                ImGui.GetCursorScreenPos().Y - ImGui.GetStyle().ItemSpacing.Y + 2);
+            itemPositions.Add((topLeft, bottomRight));
+        }
+
+        if (!ImGui.IsMouseDragging(ImGuiMouseButton.Left))
+            _draggedItem = null;
+        else if (_draggedItem != null)
+        {
+            var draggedItem = priorityQuests.Single(x => x.Id == _draggedItem);
+            int oldIndex = priorityQuests.IndexOf(draggedItem);
+
+            var (topLeft, bottomRight) = itemPositions[oldIndex];
+            ImGui.GetWindowDrawList().AddRect(topLeft, bottomRight, ImGui.GetColorU32(ImGuiColors.DalamudGrey), 3f,
+                ImDrawFlags.RoundCornersAll);
+
+            int newIndex = itemPositions.FindIndex(x => ImGui.IsMouseHoveringRect(x.TopLeft, x.BottomRight, true));
+            if (newIndex >= 0 && oldIndex != newIndex)
+            {
+                itemToAdd = priorityQuests.Single(x => x.Id == _draggedItem);
+                indexToAdd = newIndex;
+            }
+        }
+
+        if (itemToRemove != null)
+        {
+            priorityQuests.Remove(itemToRemove);
+        }
+
+        if (itemToAdd != null)
+        {
+            priorityQuests.Remove(itemToAdd);
+            priorityQuests.Insert(indexToAdd, itemToAdd);
+        }
+    }
+}
index 1234b1f1353ec7aab457b9236b6534b7ddc69d02..6d4a4384bb7279b1c869acbac40f5d98535c3a7a 100644 (file)
@@ -31,6 +31,7 @@ internal sealed partial class ActiveQuestComponent
     private readonly ICommandManager _commandManager;
     private readonly Configuration _configuration;
     private readonly QuestRegistry _questRegistry;
+    private readonly PriorityWindow _priorityWindow;
     private readonly IChatGui _chatGui;
 
     public ActiveQuestComponent(
@@ -42,6 +43,7 @@ internal sealed partial class ActiveQuestComponent
         ICommandManager commandManager,
         Configuration configuration,
         QuestRegistry questRegistry,
+        PriorityWindow priorityWindow,
         IChatGui chatGui)
     {
         _questController = questController;
@@ -52,6 +54,7 @@ internal sealed partial class ActiveQuestComponent
         _commandManager = commandManager;
         _configuration = configuration;
         _questRegistry = questRegistry;
+        _priorityWindow = priorityWindow;
         _chatGui = chatGui;
     }
 
@@ -111,6 +114,10 @@ internal sealed partial class ActiveQuestComponent
                 _questController.Stop("Manual (no active quest)");
                 _gatheringController.Stop("Manual (no active quest)");
             }
+
+            ImGui.SameLine();
+            if (ImGuiComponents.IconButton(FontAwesomeIcon.SortAmountDown))
+                _priorityWindow.Toggle();
         }
     }
 
@@ -293,6 +300,10 @@ internal sealed partial class ActiveQuestComponent
                 ImGui.PopStyleColor();
             ImGui.EndDisabled();
 
+            ImGui.SameLine();
+            if (ImGuiComponents.IconButton(FontAwesomeIcon.SortAmountDown))
+                _priorityWindow.Toggle();
+
             if (_commandManager.Commands.TryGetValue("/questinfo", out var commandInfo))
             {
                 ImGui.SameLine();