Add minimal debug overlay
authorLiza Carvelli <liza@carvel.li>
Tue, 18 Jun 2024 15:51:23 +0000 (17:51 +0200)
committerLiza Carvelli <liza@carvel.li>
Tue, 18 Jun 2024 15:51:23 +0000 (17:51 +0200)
QuestPaths/Endwalker/MSQ/B-Garlemald/4385_How the Mighty Are Fallen.json
Questionable/Configuration.cs
Questionable/DalamudInitializer.cs
Questionable/QuestionablePlugin.cs
Questionable/Windows/ConfigWindow.cs
Questionable/Windows/DebugOverlay.cs [new file with mode: 0644]
Questionable/Windows/DebugWindow.cs [deleted file]
Questionable/Windows/QuestWindow.cs [new file with mode: 0644]

index 94d98301a0cc2923dd1c2bf01fb214db334c116f..aa95cdb21aeb6756f941051ff3b772317f9db44a 100644 (file)
           },
           "TerritoryId": 958,
           "InteractionType": "Interact",
-          "Mount": false,
-          "Comment": "TODO Should cancel Navmesh on fade out"
+          "Mount": false
         }
       ]
     },
index 7dc415b916e14091d56188df72a49c43bc3c3576..eaef49365cfb3f4796326d054ed15c4c7b0265b5 100644 (file)
@@ -21,6 +21,7 @@ internal sealed class Configuration : IPluginConfiguration
 
     internal sealed class AdvancedConfiguration
     {
+        public bool DebugOverlay { get; set; }
         public bool NeverFly { get; set; }
     }
 }
index 192a9ba925293774619d732097c3e0c92e590caa..e137096426bcdfa3a8b053caaa2b0685d8730941 100644 (file)
@@ -17,13 +17,13 @@ internal sealed class DalamudInitializer : IDisposable
     private readonly MovementController _movementController;
     private readonly NavigationShortcutController _navigationShortcutController;
     private readonly WindowSystem _windowSystem;
-    private readonly DebugWindow _debugWindow;
+    private readonly QuestWindow _questWindow;
     private readonly ConfigWindow _configWindow;
 
     public DalamudInitializer(DalamudPluginInterface pluginInterface, IFramework framework,
         ICommandManager commandManager, QuestController questController, MovementController movementController,
         GameUiController gameUiController, NavigationShortcutController navigationShortcutController,
-        WindowSystem windowSystem, DebugWindow debugWindow, ConfigWindow configWindow)
+        WindowSystem windowSystem, QuestWindow questWindow, DebugOverlay debugOverlay, ConfigWindow configWindow)
     {
         _pluginInterface = pluginInterface;
         _framework = framework;
@@ -32,14 +32,15 @@ internal sealed class DalamudInitializer : IDisposable
         _movementController = movementController;
         _navigationShortcutController = navigationShortcutController;
         _windowSystem = windowSystem;
-        _debugWindow = debugWindow;
+        _questWindow = questWindow;
         _configWindow = configWindow;
 
-        _windowSystem.AddWindow(debugWindow);
+        _windowSystem.AddWindow(questWindow);
         _windowSystem.AddWindow(configWindow);
+        _windowSystem.AddWindow(debugOverlay);
 
         _pluginInterface.UiBuilder.Draw += _windowSystem.Draw;
-        _pluginInterface.UiBuilder.OpenMainUi += _debugWindow.Toggle;
+        _pluginInterface.UiBuilder.OpenMainUi += _questWindow.Toggle;
         _pluginInterface.UiBuilder.OpenConfigUi += _configWindow.Toggle;
         _framework.Update += FrameworkUpdate;
         _commandManager.AddHandler("/qst", new CommandInfo(ProcessCommand)
@@ -70,14 +71,14 @@ internal sealed class DalamudInitializer : IDisposable
         if (arguments is "c" or "config")
             _configWindow.Toggle();
         else
-            _debugWindow.Toggle();
+            _questWindow.Toggle();
     }
 
     public void Dispose()
     {
         _commandManager.RemoveHandler("/qst");
         _framework.Update -= FrameworkUpdate;
-        _pluginInterface.UiBuilder.OpenMainUi -= _debugWindow.Toggle;
+        _pluginInterface.UiBuilder.OpenMainUi -= _questWindow.Toggle;
         _pluginInterface.UiBuilder.Draw -= _windowSystem.Draw;
 
         _windowSystem.RemoveAllWindows();
index 4046b3bbf408eccee4d790accad445212b9de748..a4f2f9b22e9caa36698a606eb651fca0a33e934e 100644 (file)
@@ -80,6 +80,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
         serviceCollection.AddTaskWithFactory<AetheryteShortcut.Factory, AetheryteShortcut.UseAetheryteShortcut>();
         serviceCollection.AddTaskWithFactory<SkipCondition.Factory, SkipCondition.CheckTask>();
         serviceCollection.AddTaskWithFactory<AethernetShortcut.Factory, AethernetShortcut.UseAethernetShortcut>();
+        serviceCollection.AddTaskWithFactory<WaitAtStart.Factory, WaitAtStart.WaitDelay>();
         serviceCollection.AddTaskWithFactory<Move.Factory, Move.MoveInternal, Move.ExpectToBeNearDataId>();
         serviceCollection.AddTransient<Move.MoveBuilder>();
 
@@ -109,13 +110,14 @@ public sealed class QuestionablePlugin : IDalamudPlugin
         serviceCollection.AddSingleton<GameUiController>();
         serviceCollection.AddSingleton<NavigationShortcutController>();
 
-        serviceCollection.AddSingleton<DebugWindow>();
+        serviceCollection.AddSingleton<QuestWindow>();
         serviceCollection.AddSingleton<ConfigWindow>();
+        serviceCollection.AddSingleton<DebugOverlay>();
         serviceCollection.AddSingleton<DalamudInitializer>();
 
         _serviceProvider = serviceCollection.BuildServiceProvider();
         _serviceProvider.GetRequiredService<QuestRegistry>().Reload();
-        _serviceProvider.GetRequiredService<DebugWindow>();
+        _serviceProvider.GetRequiredService<QuestWindow>();
         _serviceProvider.GetRequiredService<DalamudInitializer>();
     }
 
index 3a3ce0833159b0a423872f082e1cce3ed84f70eb..b820e7e5921cb575a59380f30d9e25b51b78e48c 100644 (file)
@@ -79,6 +79,13 @@ internal sealed class ConfigWindow : LWindow, IPersistableWindowConfig
 
                 ImGui.Separator();
 
+                bool debugOverlay = _configuration.Advanced.DebugOverlay;
+                if (ImGui.Checkbox("Enable debug overlay", ref debugOverlay))
+                {
+                    _configuration.Advanced.DebugOverlay = debugOverlay;
+                    Save();
+                }
+
                 bool neverFly = _configuration.Advanced.NeverFly;
                 if (ImGui.Checkbox("Disable flying (even if unlocked for the zone)", ref neverFly))
                 {
diff --git a/Questionable/Windows/DebugOverlay.cs b/Questionable/Windows/DebugOverlay.cs
new file mode 100644 (file)
index 0000000..191a7d5
--- /dev/null
@@ -0,0 +1,68 @@
+using System;
+using System.Globalization;
+using System.Linq;
+using System.Numerics;
+using Dalamud.Interface.Utility;
+using Dalamud.Interface.Windowing;
+using Dalamud.Plugin.Services;
+using ImGuiNET;
+using Questionable.Controller;
+using Questionable.Model.V1;
+
+namespace Questionable.Windows;
+
+internal sealed class DebugOverlay : Window
+{
+    private readonly QuestController _questController;
+    private readonly IGameGui _gameGui;
+    private readonly Configuration _configuration;
+
+    public DebugOverlay(QuestController questController, IGameGui gameGui, Configuration configuration)
+        : base("Questionable Debug Overlay###QuestionableDebugOverlay",
+            ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoBackground |
+            ImGuiWindowFlags.NoInputs | ImGuiWindowFlags.NoSavedSettings, true)
+    {
+        _questController = questController;
+        _gameGui = gameGui;
+        _configuration = configuration;
+
+        Position = Vector2.Zero;
+        PositionCondition = ImGuiCond.Always;
+        Size = ImGui.GetIO().DisplaySize;
+        SizeCondition = ImGuiCond.Always;
+        IsOpen = true;
+    }
+
+    public override bool DrawConditions() => _configuration.Advanced.DebugOverlay;
+
+    public override void PreDraw()
+    {
+        Size = ImGui.GetIO().DisplaySize;
+    }
+
+    public override void Draw()
+    {
+        var currentQuest = _questController.CurrentQuest;
+        if (currentQuest == null)
+            return;
+
+        var sequence = currentQuest.Quest.FindSequence(currentQuest.Sequence);
+        if (sequence == null)
+            return;
+
+        for (int i = currentQuest.Step; i <= sequence.Steps.Count; ++i)
+        {
+            QuestStep? step = sequence.FindStep(i);
+            if (step == null || step.Position == null)
+                continue;
+
+            bool visible = _gameGui.WorldToScreen(step.Position.Value, out Vector2 screenPos);
+            if (!visible)
+                continue;
+
+            ImGui.GetWindowDrawList().AddCircleFilled(screenPos, 3f, 0xFF0000FF);
+            ImGui.GetWindowDrawList().AddText(screenPos + new Vector2(10, -8), 0xFF0000FF,
+                $"{i}: {step.InteractionType}\n{step.Position.Value.ToString("G", CultureInfo.InvariantCulture)}\n{step.Comment}");
+        }
+    }
+}
diff --git a/Questionable/Windows/DebugWindow.cs b/Questionable/Windows/DebugWindow.cs
deleted file mode 100644 (file)
index 7cc3f97..0000000
+++ /dev/null
@@ -1,360 +0,0 @@
-using System;
-using System.Diagnostics;
-using System.Globalization;
-using System.Linq;
-using System.Numerics;
-using Dalamud.Game.ClientState.Objects;
-using Dalamud.Interface;
-using Dalamud.Interface.Colors;
-using Dalamud.Interface.Components;
-using Dalamud.Plugin;
-using Dalamud.Plugin.Services;
-using FFXIVClientStructs.FFXIV.Client.Game;
-using FFXIVClientStructs.FFXIV.Client.Game.Control;
-using FFXIVClientStructs.FFXIV.Client.Game.Object;
-using FFXIVClientStructs.FFXIV.Client.UI.Agent;
-using ImGuiNET;
-using LLib.ImGui;
-using Microsoft.Extensions.Logging;
-using Questionable.Controller;
-using Questionable.Controller.Steps.BaseFactory;
-using Questionable.External;
-using Questionable.Model;
-using Questionable.Model.V1;
-
-namespace Questionable.Windows;
-
-internal sealed class DebugWindow : LWindow, IPersistableWindowConfig
-{
-    private readonly DalamudPluginInterface _pluginInterface;
-    private readonly MovementController _movementController;
-    private readonly QuestController _questController;
-    private readonly GameFunctions _gameFunctions;
-    private readonly IClientState _clientState;
-    private readonly IFramework _framework;
-    private readonly ITargetManager _targetManager;
-    private readonly GameUiController _gameUiController;
-    private readonly Configuration _configuration;
-    private readonly NavmeshIpc _navmeshIpc;
-    private readonly ILogger<DebugWindow> _logger;
-
-    public DebugWindow(DalamudPluginInterface pluginInterface,
-        MovementController movementController,
-        QuestController questController,
-        GameFunctions gameFunctions,
-        IClientState clientState,
-        IFramework framework,
-        ITargetManager targetManager,
-        GameUiController gameUiController,
-        Configuration configuration,
-        NavmeshIpc navmeshIpc,
-        ILogger<DebugWindow> logger)
-        : base("Questionable", ImGuiWindowFlags.AlwaysAutoResize)
-    {
-        _pluginInterface = pluginInterface;
-        _movementController = movementController;
-        _questController = questController;
-        _gameFunctions = gameFunctions;
-        _clientState = clientState;
-        _framework = framework;
-        _targetManager = targetManager;
-        _gameUiController = gameUiController;
-        _configuration = configuration;
-        _navmeshIpc = navmeshIpc;
-        _logger = logger;
-
-#if DEBUG
-        IsOpen = true;
-#endif
-        SizeConstraints = new WindowSizeConstraints
-        {
-            MinimumSize = new Vector2(200, 30),
-            MaximumSize = default
-        };
-        RespectCloseHotkey = false;
-    }
-
-    public WindowConfig WindowConfig => _configuration.DebugWindowConfig;
-
-    public void SaveWindowConfig() => _pluginInterface.SavePluginConfig(_configuration);
-
-    public override bool DrawConditions()
-    {
-        if (!_clientState.IsLoggedIn || _clientState.LocalPlayer == null)
-            return false;
-
-        var currentQuest = _questController.CurrentQuest;
-        return currentQuest == null || !currentQuest.Quest.Data.TerritoryBlacklist.Contains(_clientState.TerritoryType);
-    }
-
-    public override void Draw()
-    {
-        DrawQuest();
-        ImGui.Separator();
-
-        DrawCreationUtils();
-        ImGui.Separator();
-
-        DrawQuickAccessButtons();
-        DrawRemainingTasks();
-    }
-
-    private unsafe void DrawQuest()
-    {
-        var currentQuest = _questController.CurrentQuest;
-        if (currentQuest != null)
-        {
-            ImGui.TextUnformatted($"Quest: {currentQuest.Quest.Name} / {currentQuest.Sequence} / {currentQuest.Step}");
-
-            ImGui.BeginDisabled();
-            var questWork = _gameFunctions.GetQuestEx(currentQuest.Quest.QuestId);
-            if (questWork != null)
-            {
-                var qw = questWork.Value;
-                string vars = "";
-                for (int i = 0; i < 6; ++i)
-                {
-                    vars += qw.Variables[i] + " ";
-                    if (i % 2 == 1)
-                        vars += "   ";
-                }
-
-                // For combat quests, a sequence to kill 3 enemies works a bit like this:
-                // Trigger enemies → 0
-                // Kill first enemy → 1
-                // Kill second enemy → 2
-                // Last enemy → increase sequence, reset variable to 0
-                // The order in which enemies are killed doesn't seem to matter.
-                // If multiple waves spawn, this continues to count up (e.g. 1 enemy from wave 1, 2 enemies from wave 2, 1 from wave 3) would count to 3 then 0
-                ImGui.Text($"QW: {vars.Trim()}");
-            }
-            else
-                ImGui.TextUnformatted("(Not accepted)");
-
-            ImGui.TextUnformatted(_questController.DebugState ?? "--");
-            ImGui.EndDisabled();
-            ImGui.TextUnformatted(_questController.Comment ?? "--");
-
-            //var nextStep = _questController.GetNextStep();
-            //ImGui.BeginDisabled(nextStep.Step == null);
-            ImGui.Text(_questController.ToStatString());
-            //ImGui.EndDisabled();
-
-            ImGui.BeginDisabled(_questController.IsRunning);
-            if (ImGuiComponents.IconButton(FontAwesomeIcon.Play))
-            {
-                _questController.ExecuteNextStep(true);
-            }
-
-            ImGui.SameLine();
-
-            if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.StepForward, "Step"))
-            {
-                _questController.ExecuteNextStep(false);
-            }
-
-            ImGui.EndDisabled();
-            ImGui.SameLine();
-
-            if (ImGuiComponents.IconButton(FontAwesomeIcon.Stop))
-            {
-                _movementController.Stop();
-                _questController.Stop("Manual");
-            }
-
-            QuestStep? currentStep = currentQuest.Quest
-                .FindSequence(currentQuest.Sequence)
-                ?.FindStep(currentQuest.Step);
-            bool lastStep = currentStep ==
-                            currentQuest.Quest.FindSequence(currentQuest.Sequence)?.Steps.LastOrDefault();
-            bool colored = currentStep != null
-                           && !lastStep
-                           && currentStep.InteractionType == EInteractionType.Instruction
-                           && _questController.HasCurrentTaskMatching<WaitAtEnd.WaitNextStepOrSequence>();
-
-            ImGui.BeginDisabled(lastStep);
-            if (colored)
-                ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.HealerGreen);
-            if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.ArrowCircleRight, "Skip"))
-            {
-                _movementController.Stop();
-                _questController.Skip(currentQuest.Quest.QuestId, currentQuest.Sequence);
-            }
-
-            if (colored)
-                ImGui.PopStyleColor();
-            ImGui.EndDisabled();
-
-            bool autoAcceptNextQuest = _configuration.General.AutoAcceptNextQuest;
-            if (ImGui.Checkbox("Automatically accept next quest", ref autoAcceptNextQuest))
-            {
-                _configuration.General.AutoAcceptNextQuest = autoAcceptNextQuest;
-                _pluginInterface.SavePluginConfig(_configuration);
-            }
-        }
-        else
-            ImGui.Text("No active quest");
-    }
-
-    private unsafe void DrawCreationUtils()
-    {
-        Debug.Assert(_clientState.LocalPlayer != null, "_clientState.LocalPlayer != null");
-
-        ImGui.Text(
-            $"Current TerritoryId: {_clientState.TerritoryType}, Flying: {(_gameFunctions.IsFlyingUnlockedInCurrentZone() ? "Yes" : "No")}");
-
-        var q = _gameFunctions.GetCurrentQuest();
-        ImGui.Text($"Current Quest: {q.CurrentQuest} → {q.Sequence}");
-
-        var questManager = QuestManager.Instance();
-        if (questManager != null)
-        {
-            // unsure how these are sorted
-            for (int i = 0; i < 1 /*questManager->TrackedQuestsSpan.Length*/; ++i)
-            {
-                var trackedQuest = questManager->TrackedQuestsSpan[i];
-                switch (trackedQuest.QuestType)
-                {
-                    default:
-                        ImGui.Text($"Tracked quest {i}: {trackedQuest.QuestType}, {trackedQuest.Index}");
-                        break;
-
-                    case 1:
-                        ImGui.Text(
-                            $"Tracked quest: {questManager->NormalQuestsSpan[trackedQuest.Index].QuestId}, {trackedQuest.Index}");
-                        break;
-                }
-            }
-        }
-
-
-        if (_targetManager.Target != null)
-        {
-            ImGui.Separator();
-            ImGui.Text(string.Create(CultureInfo.InvariantCulture,
-                $"Target: {_targetManager.Target.Name}  ({_targetManager.Target.ObjectKind}; {_targetManager.Target.DataId})"));
-
-            GameObject* gameObject = (GameObject*)_targetManager.Target.Address;
-            ImGui.Text(string.Create(CultureInfo.InvariantCulture,
-                $"Distance: {(_targetManager.Target.Position - _clientState.LocalPlayer.Position).Length():F2}, Y: {_targetManager.Target.Position.Y - _clientState.LocalPlayer.Position.Y:F2} | QM: {gameObject->NamePlateIconId}"));
-
-            ImGui.BeginDisabled(!_movementController.IsNavmeshReady);
-            if (!_movementController.IsPathfinding)
-            {
-                if (ImGui.Button("Move to Target"))
-                {
-                    _movementController.NavigateTo(EMovementType.DebugWindow, _targetManager.Target.DataId,
-                        _targetManager.Target.Position, _gameFunctions.IsFlyingUnlockedInCurrentZone(),
-                        true);
-                }
-            }
-            else
-            {
-                if (ImGui.Button("Cancel pathfinding"))
-                    _movementController.ResetPathfinding();
-            }
-
-            ImGui.EndDisabled();
-
-            ImGui.SameLine();
-            if (ImGui.Button("Interact"))
-            {
-                ulong result = TargetSystem.Instance()->InteractWithObject(
-                    (GameObject*)_targetManager.Target.Address, false);
-                _logger.LogInformation("XXXXX Interaction Result: {Result}", result);
-            }
-
-            ImGui.SameLine();
-
-            ImGui.Button("Copy");
-            if (ImGui.IsItemClicked(ImGuiMouseButton.Left))
-            {
-                ImGui.SetClipboardText($$"""
-                                         "DataId": {{_targetManager.Target.DataId}},
-                                         "Position": {
-                                             "X": {{_targetManager.Target.Position.X.ToString(CultureInfo.InvariantCulture)}},
-                                             "Y": {{_targetManager.Target.Position.Y.ToString(CultureInfo.InvariantCulture)}},
-                                             "Z": {{_targetManager.Target.Position.Z.ToString(CultureInfo.InvariantCulture)}}
-                                         },
-                                         "TerritoryId": {{_clientState.TerritoryType}},
-                                         "InteractionType": "Interact"
-                                         """);
-            }
-            else if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
-            {
-                EAetheryteLocation location = (EAetheryteLocation)_targetManager.Target.DataId;
-                ImGui.SetClipboardText(string.Create(CultureInfo.InvariantCulture,
-                    $"{{EAetheryteLocation.{location}, new({_targetManager.Target.Position.X}f, {_targetManager.Target.Position.Y}f, {_targetManager.Target.Position.Z}f)}},"));
-            }
-        }
-        else
-        {
-            ImGui.Button($"Copy");
-            if (ImGui.IsItemClicked(ImGuiMouseButton.Left))
-            {
-                ImGui.SetClipboardText($$"""
-                                         "Position": {
-                                             "X": {{_clientState.LocalPlayer.Position.X.ToString(CultureInfo.InvariantCulture)}},
-                                             "Y": {{_clientState.LocalPlayer.Position.Y.ToString(CultureInfo.InvariantCulture)}},
-                                             "Z": {{_clientState.LocalPlayer.Position.Z.ToString(CultureInfo.InvariantCulture)}}
-                                         },
-                                         "TerritoryId": {{_clientState.TerritoryType}},
-                                         "InteractionType": ""
-                                         """);
-            }
-            else if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
-            {
-                Vector3 position = _clientState.LocalPlayer!.Position;
-                ImGui.SetClipboardText(string.Create(CultureInfo.InvariantCulture,
-                    $"new({position.X}f, {position.Y}f, {position.Z}f)"));
-            }
-        }
-    }
-
-    private unsafe void DrawQuickAccessButtons()
-    {
-        var map = AgentMap.Instance();
-        ImGui.BeginDisabled(map == null || map->IsFlagMarkerSet == 0 ||
-                            map->FlagMapMarker.TerritoryId != _clientState.TerritoryType ||
-                            !_navmeshIpc.IsReady);
-        if (ImGui.Button("Move to Flag"))
-        {
-            _movementController.Destination = null;
-            _gameFunctions.ExecuteCommand(
-                $"/vnav {(_gameFunctions.IsFlyingUnlockedInCurrentZone() ? "flyflag" : "moveflag")}");
-        }
-
-        ImGui.EndDisabled();
-
-        ImGui.SameLine();
-
-        ImGui.BeginDisabled(!_movementController.IsPathRunning);
-        if (ImGui.Button("Stop Nav"))
-        {
-            _movementController.Stop();
-            _questController.Stop("Manual");
-        }
-
-        ImGui.EndDisabled();
-
-        if (ImGui.Button("Reload Data"))
-        {
-            _questController.Reload();
-            _framework.RunOnTick(() => _gameUiController.HandleCurrentDialogueChoices(),
-                TimeSpan.FromMilliseconds(200));
-        }
-    }
-
-    private void DrawRemainingTasks()
-    {
-        var remainingTasks = _questController.GetRemainingTaskNames();
-        if (remainingTasks.Count > 0)
-        {
-            ImGui.Separator();
-            ImGui.BeginDisabled();
-            foreach (var task in remainingTasks)
-                ImGui.TextUnformatted(task);
-            ImGui.EndDisabled();
-        }
-    }
-}
diff --git a/Questionable/Windows/QuestWindow.cs b/Questionable/Windows/QuestWindow.cs
new file mode 100644 (file)
index 0000000..1167264
--- /dev/null
@@ -0,0 +1,360 @@
+using System;
+using System.Diagnostics;
+using System.Globalization;
+using System.Linq;
+using System.Numerics;
+using Dalamud.Game.ClientState.Objects;
+using Dalamud.Interface;
+using Dalamud.Interface.Colors;
+using Dalamud.Interface.Components;
+using Dalamud.Plugin;
+using Dalamud.Plugin.Services;
+using FFXIVClientStructs.FFXIV.Client.Game;
+using FFXIVClientStructs.FFXIV.Client.Game.Control;
+using FFXIVClientStructs.FFXIV.Client.Game.Object;
+using FFXIVClientStructs.FFXIV.Client.UI.Agent;
+using ImGuiNET;
+using LLib.ImGui;
+using Microsoft.Extensions.Logging;
+using Questionable.Controller;
+using Questionable.Controller.Steps.BaseFactory;
+using Questionable.External;
+using Questionable.Model;
+using Questionable.Model.V1;
+
+namespace Questionable.Windows;
+
+internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
+{
+    private readonly DalamudPluginInterface _pluginInterface;
+    private readonly MovementController _movementController;
+    private readonly QuestController _questController;
+    private readonly GameFunctions _gameFunctions;
+    private readonly IClientState _clientState;
+    private readonly IFramework _framework;
+    private readonly ITargetManager _targetManager;
+    private readonly GameUiController _gameUiController;
+    private readonly Configuration _configuration;
+    private readonly NavmeshIpc _navmeshIpc;
+    private readonly ILogger<QuestWindow> _logger;
+
+    public QuestWindow(DalamudPluginInterface pluginInterface,
+        MovementController movementController,
+        QuestController questController,
+        GameFunctions gameFunctions,
+        IClientState clientState,
+        IFramework framework,
+        ITargetManager targetManager,
+        GameUiController gameUiController,
+        Configuration configuration,
+        NavmeshIpc navmeshIpc,
+        ILogger<QuestWindow> logger)
+        : base("Questionable###Questionable", ImGuiWindowFlags.AlwaysAutoResize)
+    {
+        _pluginInterface = pluginInterface;
+        _movementController = movementController;
+        _questController = questController;
+        _gameFunctions = gameFunctions;
+        _clientState = clientState;
+        _framework = framework;
+        _targetManager = targetManager;
+        _gameUiController = gameUiController;
+        _configuration = configuration;
+        _navmeshIpc = navmeshIpc;
+        _logger = logger;
+
+#if DEBUG
+        IsOpen = true;
+#endif
+        SizeConstraints = new WindowSizeConstraints
+        {
+            MinimumSize = new Vector2(200, 30),
+            MaximumSize = default
+        };
+        RespectCloseHotkey = false;
+    }
+
+    public WindowConfig WindowConfig => _configuration.DebugWindowConfig;
+
+    public void SaveWindowConfig() => _pluginInterface.SavePluginConfig(_configuration);
+
+    public override bool DrawConditions()
+    {
+        if (!_clientState.IsLoggedIn || _clientState.LocalPlayer == null)
+            return false;
+
+        var currentQuest = _questController.CurrentQuest;
+        return currentQuest == null || !currentQuest.Quest.Data.TerritoryBlacklist.Contains(_clientState.TerritoryType);
+    }
+
+    public override void Draw()
+    {
+        DrawQuest();
+        ImGui.Separator();
+
+        DrawCreationUtils();
+        ImGui.Separator();
+
+        DrawQuickAccessButtons();
+        DrawRemainingTasks();
+    }
+
+    private unsafe void DrawQuest()
+    {
+        var currentQuest = _questController.CurrentQuest;
+        if (currentQuest != null)
+        {
+            ImGui.TextUnformatted($"Quest: {currentQuest.Quest.Name} / {currentQuest.Sequence} / {currentQuest.Step}");
+
+            ImGui.BeginDisabled();
+            var questWork = _gameFunctions.GetQuestEx(currentQuest.Quest.QuestId);
+            if (questWork != null)
+            {
+                var qw = questWork.Value;
+                string vars = "";
+                for (int i = 0; i < 6; ++i)
+                {
+                    vars += qw.Variables[i] + " ";
+                    if (i % 2 == 1)
+                        vars += "   ";
+                }
+
+                // For combat quests, a sequence to kill 3 enemies works a bit like this:
+                // Trigger enemies → 0
+                // Kill first enemy → 1
+                // Kill second enemy → 2
+                // Last enemy → increase sequence, reset variable to 0
+                // The order in which enemies are killed doesn't seem to matter.
+                // If multiple waves spawn, this continues to count up (e.g. 1 enemy from wave 1, 2 enemies from wave 2, 1 from wave 3) would count to 3 then 0
+                ImGui.Text($"QW: {vars.Trim()}");
+            }
+            else
+                ImGui.TextUnformatted("(Not accepted)");
+
+            ImGui.TextUnformatted(_questController.DebugState ?? "--");
+            ImGui.EndDisabled();
+            ImGui.TextUnformatted(_questController.Comment ?? "--");
+
+            //var nextStep = _questController.GetNextStep();
+            //ImGui.BeginDisabled(nextStep.Step == null);
+            ImGui.Text(_questController.ToStatString());
+            //ImGui.EndDisabled();
+
+            ImGui.BeginDisabled(_questController.IsRunning);
+            if (ImGuiComponents.IconButton(FontAwesomeIcon.Play))
+            {
+                _questController.ExecuteNextStep(true);
+            }
+
+            ImGui.SameLine();
+
+            if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.StepForward, "Step"))
+            {
+                _questController.ExecuteNextStep(false);
+            }
+
+            ImGui.EndDisabled();
+            ImGui.SameLine();
+
+            if (ImGuiComponents.IconButton(FontAwesomeIcon.Stop))
+            {
+                _movementController.Stop();
+                _questController.Stop("Manual");
+            }
+
+            QuestStep? currentStep = currentQuest.Quest
+                .FindSequence(currentQuest.Sequence)
+                ?.FindStep(currentQuest.Step);
+            bool lastStep = currentStep ==
+                            currentQuest.Quest.FindSequence(currentQuest.Sequence)?.Steps.LastOrDefault();
+            bool colored = currentStep != null
+                           && !lastStep
+                           && currentStep.InteractionType == EInteractionType.Instruction
+                           && _questController.HasCurrentTaskMatching<WaitAtEnd.WaitNextStepOrSequence>();
+
+            ImGui.BeginDisabled(lastStep);
+            if (colored)
+                ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.HealerGreen);
+            if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.ArrowCircleRight, "Skip"))
+            {
+                _movementController.Stop();
+                _questController.Skip(currentQuest.Quest.QuestId, currentQuest.Sequence);
+            }
+
+            if (colored)
+                ImGui.PopStyleColor();
+            ImGui.EndDisabled();
+
+            bool autoAcceptNextQuest = _configuration.General.AutoAcceptNextQuest;
+            if (ImGui.Checkbox("Automatically accept next quest", ref autoAcceptNextQuest))
+            {
+                _configuration.General.AutoAcceptNextQuest = autoAcceptNextQuest;
+                _pluginInterface.SavePluginConfig(_configuration);
+            }
+        }
+        else
+            ImGui.Text("No active quest");
+    }
+
+    private unsafe void DrawCreationUtils()
+    {
+        Debug.Assert(_clientState.LocalPlayer != null, "_clientState.LocalPlayer != null");
+
+        ImGui.Text(
+            $"Current TerritoryId: {_clientState.TerritoryType}, Flying: {(_gameFunctions.IsFlyingUnlockedInCurrentZone() ? "Yes" : "No")}");
+
+        var q = _gameFunctions.GetCurrentQuest();
+        ImGui.Text($"Current Quest: {q.CurrentQuest} → {q.Sequence}");
+
+        var questManager = QuestManager.Instance();
+        if (questManager != null)
+        {
+            // unsure how these are sorted
+            for (int i = 0; i < 1 /*questManager->TrackedQuestsSpan.Length*/; ++i)
+            {
+                var trackedQuest = questManager->TrackedQuestsSpan[i];
+                switch (trackedQuest.QuestType)
+                {
+                    default:
+                        ImGui.Text($"Tracked quest {i}: {trackedQuest.QuestType}, {trackedQuest.Index}");
+                        break;
+
+                    case 1:
+                        ImGui.Text(
+                            $"Tracked quest: {questManager->NormalQuestsSpan[trackedQuest.Index].QuestId}, {trackedQuest.Index}");
+                        break;
+                }
+            }
+        }
+
+
+        if (_targetManager.Target != null)
+        {
+            ImGui.Separator();
+            ImGui.Text(string.Create(CultureInfo.InvariantCulture,
+                $"Target: {_targetManager.Target.Name}  ({_targetManager.Target.ObjectKind}; {_targetManager.Target.DataId})"));
+
+            GameObject* gameObject = (GameObject*)_targetManager.Target.Address;
+            ImGui.Text(string.Create(CultureInfo.InvariantCulture,
+                $"Distance: {(_targetManager.Target.Position - _clientState.LocalPlayer.Position).Length():F2}, Y: {_targetManager.Target.Position.Y - _clientState.LocalPlayer.Position.Y:F2} | QM: {gameObject->NamePlateIconId}"));
+
+            ImGui.BeginDisabled(!_movementController.IsNavmeshReady);
+            if (!_movementController.IsPathfinding)
+            {
+                if (ImGui.Button("Move to Target"))
+                {
+                    _movementController.NavigateTo(EMovementType.DebugWindow, _targetManager.Target.DataId,
+                        _targetManager.Target.Position, _gameFunctions.IsFlyingUnlockedInCurrentZone(),
+                        true);
+                }
+            }
+            else
+            {
+                if (ImGui.Button("Cancel pathfinding"))
+                    _movementController.ResetPathfinding();
+            }
+
+            ImGui.EndDisabled();
+
+            ImGui.SameLine();
+            if (ImGui.Button("Interact"))
+            {
+                ulong result = TargetSystem.Instance()->InteractWithObject(
+                    (GameObject*)_targetManager.Target.Address, false);
+                _logger.LogInformation("XXXXX Interaction Result: {Result}", result);
+            }
+
+            ImGui.SameLine();
+
+            ImGui.Button("Copy");
+            if (ImGui.IsItemClicked(ImGuiMouseButton.Left))
+            {
+                ImGui.SetClipboardText($$"""
+                                         "DataId": {{_targetManager.Target.DataId}},
+                                         "Position": {
+                                             "X": {{_targetManager.Target.Position.X.ToString(CultureInfo.InvariantCulture)}},
+                                             "Y": {{_targetManager.Target.Position.Y.ToString(CultureInfo.InvariantCulture)}},
+                                             "Z": {{_targetManager.Target.Position.Z.ToString(CultureInfo.InvariantCulture)}}
+                                         },
+                                         "TerritoryId": {{_clientState.TerritoryType}},
+                                         "InteractionType": "Interact"
+                                         """);
+            }
+            else if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
+            {
+                EAetheryteLocation location = (EAetheryteLocation)_targetManager.Target.DataId;
+                ImGui.SetClipboardText(string.Create(CultureInfo.InvariantCulture,
+                    $"{{EAetheryteLocation.{location}, new({_targetManager.Target.Position.X}f, {_targetManager.Target.Position.Y}f, {_targetManager.Target.Position.Z}f)}},"));
+            }
+        }
+        else
+        {
+            ImGui.Button($"Copy");
+            if (ImGui.IsItemClicked(ImGuiMouseButton.Left))
+            {
+                ImGui.SetClipboardText($$"""
+                                         "Position": {
+                                             "X": {{_clientState.LocalPlayer.Position.X.ToString(CultureInfo.InvariantCulture)}},
+                                             "Y": {{_clientState.LocalPlayer.Position.Y.ToString(CultureInfo.InvariantCulture)}},
+                                             "Z": {{_clientState.LocalPlayer.Position.Z.ToString(CultureInfo.InvariantCulture)}}
+                                         },
+                                         "TerritoryId": {{_clientState.TerritoryType}},
+                                         "InteractionType": ""
+                                         """);
+            }
+            else if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
+            {
+                Vector3 position = _clientState.LocalPlayer!.Position;
+                ImGui.SetClipboardText(string.Create(CultureInfo.InvariantCulture,
+                    $"new({position.X}f, {position.Y}f, {position.Z}f)"));
+            }
+        }
+    }
+
+    private unsafe void DrawQuickAccessButtons()
+    {
+        var map = AgentMap.Instance();
+        ImGui.BeginDisabled(map == null || map->IsFlagMarkerSet == 0 ||
+                            map->FlagMapMarker.TerritoryId != _clientState.TerritoryType ||
+                            !_navmeshIpc.IsReady);
+        if (ImGui.Button("Move to Flag"))
+        {
+            _movementController.Destination = null;
+            _gameFunctions.ExecuteCommand(
+                $"/vnav {(_gameFunctions.IsFlyingUnlockedInCurrentZone() ? "flyflag" : "moveflag")}");
+        }
+
+        ImGui.EndDisabled();
+
+        ImGui.SameLine();
+
+        ImGui.BeginDisabled(!_movementController.IsPathRunning);
+        if (ImGui.Button("Stop Nav"))
+        {
+            _movementController.Stop();
+            _questController.Stop("Manual");
+        }
+
+        ImGui.EndDisabled();
+
+        if (ImGui.Button("Reload Data"))
+        {
+            _questController.Reload();
+            _framework.RunOnTick(() => _gameUiController.HandleCurrentDialogueChoices(),
+                TimeSpan.FromMilliseconds(200));
+        }
+    }
+
+    private void DrawRemainingTasks()
+    {
+        var remainingTasks = _questController.GetRemainingTaskNames();
+        if (remainingTasks.Count > 0)
+        {
+            ImGui.Separator();
+            ImGui.BeginDisabled();
+            foreach (var task in remainingTasks)
+                ImGui.TextUnformatted(task);
+            ImGui.EndDisabled();
+        }
+    }
+}