diff --git a/LLib b/LLib index 6dfc18ee..912a7b04 160000 --- a/LLib +++ b/LLib @@ -1 +1 @@ -Subproject commit 6dfc18ee6a187138036ee2d51ba2257741c1e568 +Subproject commit 912a7b04ce180e337af9beeef2d1393b040c1ba8 diff --git a/Questionable/Configuration.cs b/Questionable/Configuration.cs index f2a25164..fb96ce35 100644 --- a/Questionable/Configuration.cs +++ b/Questionable/Configuration.cs @@ -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; diff --git a/Questionable/Controller/CommandHandler.cs b/Questionable/Controller/CommandHandler.cs index 4f80f53b..0bb87429 100644 --- a/Questionable/Controller/CommandHandler.cs +++ b/Questionable/Controller/CommandHandler.cs @@ -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]) { diff --git a/Questionable/DalamudInitializer.cs b/Questionable/DalamudInitializer.cs index 78ac865c..a4b228d9 100644 --- a/Questionable/DalamudInitializer.cs +++ b/Questionable/DalamudInitializer.cs @@ -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 _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 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(); diff --git a/Questionable/QuestionablePlugin.cs b/Questionable/QuestionablePlugin.cs index bac54241..5e8dc11d 100644 --- a/Questionable/QuestionablePlugin.cs +++ b/Questionable/QuestionablePlugin.cs @@ -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(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); @@ -255,6 +257,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); diff --git a/Questionable/Windows/OneTimeSetupWindow.cs b/Questionable/Windows/OneTimeSetupWindow.cs new file mode 100644 index 00000000..2e8ac1ff --- /dev/null +++ b/Questionable/Windows/OneTimeSetupWindow.cs @@ -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 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 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 _logger; + + public OneTimeSetupWindow(Configuration configuration, IDalamudPluginInterface pluginInterface, UiUtils uiUtils, + DalamudReflector dalamudReflector, ILogger 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); +} diff --git a/Questionable/Windows/QuestWindow.cs b/Questionable/Windows/QuestWindow.cs index 37724c8c..1deaf934 100644 --- a/Questionable/Windows/QuestWindow.cs +++ b/Questionable/Windows/QuestWindow.cs @@ -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;