Add plugin setup window
authorLiza Carvelli <liza@carvel.li>
Sun, 3 Nov 2024 18:25:33 +0000 (19:25 +0100)
committerLiza Carvelli <liza@carvel.li>
Sun, 3 Nov 2024 18:25:46 +0000 (19:25 +0100)
LLib
Questionable/Configuration.cs
Questionable/Controller/CommandHandler.cs
Questionable/DalamudInitializer.cs
Questionable/QuestionablePlugin.cs
Questionable/Windows/OneTimeSetupWindow.cs [new file with mode: 0644]
Questionable/Windows/QuestWindow.cs

diff --git a/LLib b/LLib
index 6dfc18ee6a187138036ee2d51ba2257741c1e568..912a7b04ce180e337af9beeef2d1393b040c1ba8 160000 (submodule)
--- a/LLib
+++ b/LLib
@@ -1 +1 @@
-Subproject commit 6dfc18ee6a187138036ee2d51ba2257741c1e568
+Subproject commit 912a7b04ce180e337af9beeef2d1393b040c1ba8
index f2a251640dcb63f05213679f5d0a02b763d47cb2..fb96ce3533ad76f1fff6e705acea98a90236e98b 100644 (file)
@@ -6,12 +6,19 @@ namespace Questionable;
 
 internal sealed class Configuration : IPluginConfiguration
 {
+    public const int PluginSetupVersion = 1;
+
     public int Version { get; set; } = 1;
+    public int PluginSetupCompleteVersion { get; set; }
     public GeneralConfiguration General { get; } = new();
     public AdvancedConfiguration Advanced { get; } = new();
     public WindowConfig DebugWindowConfig { get; } = new();
     public WindowConfig ConfigWindowConfig { get; } = new();
 
+    internal bool IsPluginSetupComplete() => PluginSetupCompleteVersion == PluginSetupVersion;
+
+    internal void MarkPluginSetupComplete() => PluginSetupCompleteVersion = PluginSetupVersion;
+
     internal sealed class GeneralConfiguration
     {
         public uint MountId { get; set; } = 71;
index 4f80f53b538df1661c20ac7493ae24db3d56dd5d..0bb87429f3b85e1b358d53c823024a61253faac2 100644 (file)
@@ -24,12 +24,14 @@ internal sealed class CommandHandler : IDisposable
     private readonly QuestRegistry _questRegistry;
     private readonly ConfigWindow _configWindow;
     private readonly DebugOverlay _debugOverlay;
+    private readonly OneTimeSetupWindow _oneTimeSetupWindow;
     private readonly QuestWindow _questWindow;
     private readonly QuestSelectionWindow _questSelectionWindow;
     private readonly ITargetManager _targetManager;
     private readonly QuestFunctions _questFunctions;
     private readonly GameFunctions _gameFunctions;
     private readonly IDataManager _dataManager;
+    private readonly Configuration _configuration;
 
     public CommandHandler(
         ICommandManager commandManager,
@@ -39,12 +41,14 @@ internal sealed class CommandHandler : IDisposable
         QuestRegistry questRegistry,
         ConfigWindow configWindow,
         DebugOverlay debugOverlay,
+        OneTimeSetupWindow oneTimeSetupWindow,
         QuestWindow questWindow,
         QuestSelectionWindow questSelectionWindow,
         ITargetManager targetManager,
         QuestFunctions questFunctions,
         GameFunctions gameFunctions,
-        IDataManager dataManager)
+        IDataManager dataManager,
+        Configuration configuration)
     {
         _commandManager = commandManager;
         _chatGui = chatGui;
@@ -53,12 +57,14 @@ internal sealed class CommandHandler : IDisposable
         _questRegistry = questRegistry;
         _configWindow = configWindow;
         _debugOverlay = debugOverlay;
+        _oneTimeSetupWindow = oneTimeSetupWindow;
         _questWindow = questWindow;
         _questSelectionWindow = questSelectionWindow;
         _targetManager = targetManager;
         _questFunctions = questFunctions;
         _gameFunctions = gameFunctions;
         _dataManager = dataManager;
+        _configuration = configuration;
 
         _commandManager.AddHandler("/qst", new CommandInfo(ProcessCommand)
         {
@@ -75,6 +81,15 @@ internal sealed class CommandHandler : IDisposable
 
     private void ProcessCommand(string command, string arguments)
     {
+        if (!_configuration.IsPluginSetupComplete())
+        {
+            if (string.IsNullOrEmpty(arguments))
+                _oneTimeSetupWindow.IsOpen = true;
+            else
+                _chatGui.PrintError("Please complete the one-time setup first.", MessageTag, TagColor);
+            return;
+        }
+
         string[] parts = arguments.Split(' ');
         switch (parts[0])
         {
index 78ac865ceac77976517256193c9083a4e5c646d7..a4b228d924428d7cf41fbbab31e2d8ad57a7af8b 100644 (file)
@@ -18,9 +18,11 @@ internal sealed class DalamudInitializer : IDisposable
     private readonly QuestController _questController;
     private readonly MovementController _movementController;
     private readonly WindowSystem _windowSystem;
+    private readonly OneTimeSetupWindow _oneTimeSetupWindow;
     private readonly QuestWindow _questWindow;
     private readonly ConfigWindow _configWindow;
     private readonly IToastGui _toastGui;
+    private readonly Configuration _configuration;
     private readonly ILogger<DalamudInitializer> _logger;
 
     public DalamudInitializer(
@@ -30,6 +32,7 @@ internal sealed class DalamudInitializer : IDisposable
         MovementController movementController,
         InteractionUiController interactionUiController,
         WindowSystem windowSystem,
+        OneTimeSetupWindow oneTimeSetupWindow,
         QuestWindow questWindow,
         DebugOverlay debugOverlay,
         ConfigWindow configWindow,
@@ -38,6 +41,7 @@ internal sealed class DalamudInitializer : IDisposable
         JournalProgressWindow journalProgressWindow,
         PriorityWindow priorityWindow,
         IToastGui toastGui,
+        Configuration configuration,
         ILogger<DalamudInitializer> logger)
     {
         _pluginInterface = pluginInterface;
@@ -45,11 +49,14 @@ internal sealed class DalamudInitializer : IDisposable
         _questController = questController;
         _movementController = movementController;
         _windowSystem = windowSystem;
+        _oneTimeSetupWindow = oneTimeSetupWindow;
         _questWindow = questWindow;
         _configWindow = configWindow;
         _toastGui = toastGui;
+        _configuration = configuration;
         _logger = logger;
 
+        _windowSystem.AddWindow(oneTimeSetupWindow);
         _windowSystem.AddWindow(questWindow);
         _windowSystem.AddWindow(configWindow);
         _windowSystem.AddWindow(debugOverlay);
@@ -59,7 +66,7 @@ internal sealed class DalamudInitializer : IDisposable
         _windowSystem.AddWindow(priorityWindow);
 
         _pluginInterface.UiBuilder.Draw += _windowSystem.Draw;
-        _pluginInterface.UiBuilder.OpenMainUi += _questWindow.Toggle;
+        _pluginInterface.UiBuilder.OpenMainUi += ToggleQuestWindow;
         _pluginInterface.UiBuilder.OpenConfigUi += _configWindow.Toggle;
         _framework.Update += FrameworkUpdate;
         _framework.RunOnTick(interactionUiController.HandleCurrentDialogueChoices, TimeSpan.FromMilliseconds(200));
@@ -91,6 +98,14 @@ internal sealed class DalamudInitializer : IDisposable
     private void OnQuestToast(ref SeString message, ref QuestToastOptions options, ref bool isHandled)
         => _logger.LogTrace("Quest Toast: {Message}", message);
 
+    private void ToggleQuestWindow()
+    {
+        if (_configuration.IsPluginSetupComplete())
+            _questWindow.Toggle();
+        else
+            _oneTimeSetupWindow.IsOpen = true;
+    }
+
     public void Dispose()
     {
         _toastGui.QuestToast -= OnQuestToast;
@@ -98,7 +113,7 @@ internal sealed class DalamudInitializer : IDisposable
         _toastGui.Toast -= OnToast;
         _framework.Update -= FrameworkUpdate;
         _pluginInterface.UiBuilder.OpenConfigUi -= _configWindow.Toggle;
-        _pluginInterface.UiBuilder.OpenMainUi -= _questWindow.Toggle;
+        _pluginInterface.UiBuilder.OpenMainUi -= ToggleQuestWindow;
         _pluginInterface.UiBuilder.Draw -= _windowSystem.Draw;
 
         _windowSystem.RemoveAllWindows();
index bac542419057aadbc3866e93b09b769a44aa51f3..5e8dc11d8b5d75c24b41fa32327d055f8f04383c 100644 (file)
@@ -6,6 +6,7 @@ using Dalamud.Game.ClientState.Objects;
 using Dalamud.Interface.Windowing;
 using Dalamud.Plugin;
 using Dalamud.Plugin.Services;
+using LLib;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Logging;
 using Questionable.Controller;
@@ -110,6 +111,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
         serviceCollection.AddSingleton<GameFunctions>();
         serviceCollection.AddSingleton<ChatFunctions>();
         serviceCollection.AddSingleton<QuestFunctions>();
+        serviceCollection.AddSingleton<DalamudReflector>();
         serviceCollection.AddSingleton<AutoSnipeHandler>();
 
         serviceCollection.AddSingleton<AetherCurrentData>();
@@ -255,6 +257,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
         serviceCollection.AddSingleton<QuestJournalComponent>();
         serviceCollection.AddSingleton<GatheringJournalComponent>();
 
+        serviceCollection.AddSingleton<OneTimeSetupWindow>();
         serviceCollection.AddSingleton<QuestWindow>();
         serviceCollection.AddSingleton<ConfigWindow>();
         serviceCollection.AddSingleton<DebugOverlay>();
diff --git a/Questionable/Windows/OneTimeSetupWindow.cs b/Questionable/Windows/OneTimeSetupWindow.cs
new file mode 100644 (file)
index 0000000..2e8ac1f
--- /dev/null
@@ -0,0 +1,164 @@
+using System;
+using System.Collections.Generic;
+using Dalamud.Interface;
+using Dalamud.Interface.Colors;
+using Dalamud.Interface.Components;
+using Dalamud.Interface.Utility.Raii;
+using Dalamud.Plugin;
+using Dalamud.Utility;
+using ImGuiNET;
+using LLib;
+using LLib.ImGui;
+using Microsoft.Extensions.Logging;
+
+namespace Questionable.Windows;
+
+internal sealed class OneTimeSetupWindow : LWindow, IDisposable
+{
+    private static readonly IReadOnlyList<PluginInfo> RequiredPlugins =
+    [
+        new("vnavmesh",
+            """
+            vnavmesh handles the navigation within a zone, moving
+            your character to the next quest-related objective.
+            """,
+            new Uri("https://github.com/awgil/ffxiv_navmesh/")),
+        new("Lifestream",
+            """
+            Used to travel to aethernet shards in cities.
+            """,
+            new Uri("https://github.com/NightmareXIV/Lifestream")),
+        new("TextAdvance",
+            """
+            Automatically accepts and turns in quests, skips cutscenes
+            and dialogue.
+            """,
+            new Uri("https://github.com/NightmareXIV/TextAdvance")),
+    ];
+
+    private static readonly IReadOnlyList<PluginInfo> RecommendedPlugins =
+    [
+        new("Rotation Solver Reborn",
+            """
+            Automatically handles most combat interactions you encounter
+            during quests, including being interrupted by mobs.
+            """,
+            new Uri("https://github.com/FFXIV-CombatReborn/RotationSolverReborn")),
+    ];
+
+    private readonly Configuration _configuration;
+    private readonly IDalamudPluginInterface _pluginInterface;
+    private readonly UiUtils _uiUtils;
+    private readonly DalamudReflector _dalamudReflector;
+    private readonly ILogger<OneTimeSetupWindow> _logger;
+
+    public OneTimeSetupWindow(Configuration configuration, IDalamudPluginInterface pluginInterface, UiUtils uiUtils,
+        DalamudReflector dalamudReflector, ILogger<OneTimeSetupWindow> logger)
+        : base("Questionable Setup###QuestionableOneTimeSetup",
+            ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoSavedSettings, true)
+    {
+        _configuration = configuration;
+        _pluginInterface = pluginInterface;
+        _uiUtils = uiUtils;
+        _dalamudReflector = dalamudReflector;
+        _logger = logger;
+
+        RespectCloseHotkey = false;
+        ShowCloseButton = false;
+        AllowPinning = false;
+        AllowClickthrough = false;
+        IsOpen = !_configuration.IsPluginSetupComplete();
+        _logger.LogInformation("One-time setup needed: {IsOpen}", IsOpen);
+    }
+
+    public override void Draw()
+    {
+        float checklistPadding;
+        using (_pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push())
+        {
+            checklistPadding = ImGui.CalcTextSize(FontAwesomeIcon.Check.ToIconString()).X +
+                               ImGui.GetStyle().ItemSpacing.X;
+        }
+
+        ImGui.Text("Questionable requires the following plugins to work:");
+        bool allRequiredInstalled = true;
+        using (ImRaii.PushIndent())
+        {
+            foreach (var plugin in RequiredPlugins)
+                allRequiredInstalled &= DrawPlugin(plugin, checklistPadding);
+        }
+
+        ImGui.Spacing();
+        ImGui.Separator();
+        ImGui.Spacing();
+
+        ImGui.Text("The following plugins are recommended, but not required:");
+        using (ImRaii.PushIndent())
+        {
+            foreach (var plugin in RecommendedPlugins)
+                DrawPlugin(plugin, checklistPadding);
+        }
+
+        ImGui.Spacing();
+        ImGui.Separator();
+        ImGui.Spacing();
+
+        if (allRequiredInstalled)
+        {
+            using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.ParsedGreen))
+            {
+                if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Check, "Finish Setup"))
+                {
+                    _configuration.MarkPluginSetupComplete();
+                    _pluginInterface.SavePluginConfig(_configuration);
+                }
+            }
+        }
+        else
+        {
+            using (ImRaii.Disabled())
+            {
+                using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed))
+                    ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Check, "Missing required plugins");
+            }
+        }
+
+        ImGui.SameLine();
+
+        if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Times, "Close window & don't enable Questionable"))
+        {
+            IsOpen = false;
+        }
+    }
+
+    private bool DrawPlugin(PluginInfo plugin, float checklistPadding)
+    {
+        bool isInstalled = IsPluginInstalled(plugin.DisplayName);
+        using (ImRaii.PushId("plugin_" + plugin.DisplayName))
+        {
+            _uiUtils.ChecklistItem(plugin.DisplayName, isInstalled);
+            using (ImRaii.PushIndent(checklistPadding))
+            {
+                ImGui.TextUnformatted(plugin.Details);
+                if (!isInstalled && ImGui.Button("Open Repository"))
+                    Util.OpenLink(plugin.Uri.ToString());
+            }
+        }
+
+        return isInstalled;
+    }
+
+    private bool IsPluginInstalled(string internalName)
+    {
+        return _dalamudReflector.TryGetDalamudPlugin(internalName, out _, suppressErrors: true, ignoreCache: true);
+    }
+
+    public void Dispose()
+    {
+    }
+
+    private sealed record PluginInfo(
+        string DisplayName,
+        string Details,
+        Uri Uri);
+}
index 37724c8cee0d1dd4e781037dbb0267498ef93f79..1deaf934ca84dc8ce4729995231d08589ab5904a 100644 (file)
@@ -113,6 +113,9 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
 
     public override bool DrawConditions()
     {
+        if (!_configuration.IsPluginSetupComplete())
+            return false;
+
         if (!_clientState.IsLoggedIn || _clientState.LocalPlayer == null || _clientState.IsPvPExcludingDen)
             return false;