diff --git a/Questionable/Configuration.cs b/Questionable/Configuration.cs index 29358b34..ce4c9616 100644 --- a/Questionable/Configuration.cs +++ b/Questionable/Configuration.cs @@ -7,9 +7,9 @@ namespace Questionable; internal sealed class Configuration : IPluginConfiguration { - public const int PluginSetupVersion = 2; + public const int PluginSetupVersion = 3; - public int Version { get; set; } =1 ; + public int Version { get; set; } = 1; public int PluginSetupCompleteVersion { get; set; } public GeneralConfiguration General { get; } = new(); public NotificationConfiguration Notifications { get; } = new(); @@ -23,6 +23,7 @@ internal sealed class Configuration : IPluginConfiguration internal sealed class GeneralConfiguration { + public ECombatModule CombatModule { get; set; } = ECombatModule.BossMod; public uint MountId { get; set; } = 71; public GrandCompany GrandCompany { get; set; } = GrandCompany.None; public bool HideInAllInstances { get; set; } = true; @@ -45,4 +46,11 @@ internal sealed class Configuration : IPluginConfiguration public bool NeverFly { get; set; } public bool AdditionalStatusInformation { get; set; } } + + internal enum ECombatModule + { + None, + BossMod, + RotationSolverReborn, + } } diff --git a/Questionable/Controller/CombatModules/BossModModule.cs b/Questionable/Controller/CombatModules/BossModModule.cs index a26d1c74..f27a6f81 100644 --- a/Questionable/Controller/CombatModules/BossModModule.cs +++ b/Questionable/Controller/CombatModules/BossModModule.cs @@ -12,22 +12,44 @@ using System.Numerics; namespace Questionable.Controller.CombatModules; -internal sealed class BossModModule(ILogger logger, MovementController movementController, IClientState clientState, IDalamudPluginInterface pluginInterface) : ICombatModule, IDisposable +internal sealed class BossModModule : ICombatModule, IDisposable { private const string Name = "BossMod"; - private readonly ILogger _logger = logger; - private readonly MovementController _movementController = movementController; - private readonly IClientState _clientState = clientState; - private readonly ICallGateSubscriber _getPreset = pluginInterface.GetIpcSubscriber($"{Name}.Presets.Get"); - private readonly ICallGateSubscriber _createPreset = pluginInterface.GetIpcSubscriber($"{Name}.Presets.Create"); - private readonly ICallGateSubscriber _setPreset = pluginInterface.GetIpcSubscriber($"{Name}.Presets.SetActive"); - private readonly ICallGateSubscriber _clearPreset = pluginInterface.GetIpcSubscriber($"{Name}.Presets.ClearActive"); + private readonly ILogger _logger; + private readonly MovementController _movementController; + private readonly IClientState _clientState; + private readonly Configuration _configuration; + private readonly ICallGateSubscriber _getPreset; + private readonly ICallGateSubscriber _createPreset; + private readonly ICallGateSubscriber _setPreset; + private readonly ICallGateSubscriber _clearPreset; private static Stream Preset => typeof(BossModModule).Assembly.GetManifestResourceStream("Questionable.Controller.CombatModules.BossModPreset")!; private DateTime _lastDistanceCheck = DateTime.MinValue; + public BossModModule( + ILogger logger, + MovementController movementController, + IClientState clientState, + IDalamudPluginInterface pluginInterface, + Configuration configuration) + { + _logger = logger; + _movementController = movementController; + _clientState = clientState; + _configuration = configuration; + + _getPreset = pluginInterface.GetIpcSubscriber($"{Name}.Presets.Get"); + _createPreset = pluginInterface.GetIpcSubscriber($"{Name}.Presets.Create"); + _setPreset = pluginInterface.GetIpcSubscriber($"{Name}.Presets.SetActive"); + _clearPreset = pluginInterface.GetIpcSubscriber($"{Name}.Presets.ClearActive"); + } + public bool CanHandleFight(CombatController.CombatData combatData) { + if (_configuration.General.CombatModule != Configuration.ECombatModule.BossMod) + return false; + try { return _getPreset.HasFunction; diff --git a/Questionable/Controller/CombatModules/RotationSolverRebornModule.cs b/Questionable/Controller/CombatModules/RotationSolverRebornModule.cs index 581e07a9..d5f13f4e 100644 --- a/Questionable/Controller/CombatModules/RotationSolverRebornModule.cs +++ b/Questionable/Controller/CombatModules/RotationSolverRebornModule.cs @@ -16,17 +16,19 @@ internal sealed class RotationSolverRebornModule : ICombatModule, IDisposable private readonly ILogger _logger; private readonly MovementController _movementController; private readonly IClientState _clientState; + private readonly Configuration _configuration; private readonly ICallGateSubscriber _test; private readonly ICallGateSubscriber _changeOperationMode; private DateTime _lastDistanceCheck = DateTime.MinValue; public RotationSolverRebornModule(ILogger logger, MovementController movementController, - IClientState clientState, IDalamudPluginInterface pluginInterface) + IClientState clientState, IDalamudPluginInterface pluginInterface, Configuration configuration) { _logger = logger; _movementController = movementController; _clientState = clientState; + _configuration = configuration; _test = pluginInterface.GetIpcSubscriber("RotationSolverReborn.Test"); _changeOperationMode = pluginInterface.GetIpcSubscriber("RotationSolverReborn.ChangeOperatingMode"); @@ -34,6 +36,9 @@ internal sealed class RotationSolverRebornModule : ICombatModule, IDisposable public bool CanHandleFight(CombatController.CombatData combatData) { + if (_configuration.General.CombatModule != Configuration.ECombatModule.RotationSolverReborn) + return false; + try { _test.InvokeAction("Validate RSR is callable from Questionable"); diff --git a/Questionable/Windows/ConfigWindow.cs b/Questionable/Windows/ConfigWindow.cs index 27c968a0..7cd19627 100644 --- a/Questionable/Windows/ConfigWindow.cs +++ b/Questionable/Windows/ConfigWindow.cs @@ -26,6 +26,7 @@ internal sealed class ConfigWindow : LWindow, IPersistableWindowConfig private readonly uint[] _mountIds; private readonly string[] _mountNames; + private readonly string[] _combatModuleNames = ["None", "Boss Mod (VBM)", "Rotation Solver Reborn"]; private readonly string[] _grandCompanyNames = ["None (manually pick quest)", "Maelstrom", "Twin Adder", "Immortal Flames"]; @@ -65,6 +66,14 @@ internal sealed class ConfigWindow : LWindow, IPersistableWindowConfig using var tab = ImRaii.TabItem("General"); if (!tab) return; + + int selectedCombatModule = (int)_configuration.General.CombatModule; + if (ImGui.Combo("Preferred Combat Module", ref selectedCombatModule, _combatModuleNames, _combatModuleNames.Length)) + { + _configuration.General.CombatModule = (Configuration.ECombatModule)selectedCombatModule; + Save(); + } + int selectedMount = Array.FindIndex(_mountIds, x => x == _configuration.General.MountId); if (selectedMount == -1) { diff --git a/Questionable/Windows/OneTimeSetupWindow.cs b/Questionable/Windows/OneTimeSetupWindow.cs index b94b3fc8..3ba35a09 100644 --- a/Questionable/Windows/OneTimeSetupWindow.cs +++ b/Questionable/Windows/OneTimeSetupWindow.cs @@ -44,6 +44,27 @@ internal sealed class OneTimeSetupWindow : LWindow new Uri("https://github.com/NightmareXIV/MyDalamudPlugins/raw/main/pluginmaster.json")), ]; + private static readonly IReadOnlyDictionary CombatPlugins = new Dictionary + { + { + Configuration.ECombatModule.BossMod, + new("Boss Mod (VBM)", + "BossMod", + string.Empty, + new Uri("https://github.com/awgil/ffxiv_bossmod"), + new Uri("https://puni.sh/api/repository/veyn")) + }, + { + Configuration.ECombatModule.RotationSolverReborn, + new("Rotation Solver Reborn", + "RotationSolver", + string.Empty, + new Uri("https://github.com/FFXIV-CombatReborn/RotationSolverReborn"), + new Uri( + "https://raw.githubusercontent.com/FFXIV-CombatReborn/CombatRebornRepo/main/pluginmaster.json")) + }, + }.AsReadOnly(); + private readonly IReadOnlyList _recommendedPlugins; private readonly Configuration _configuration; @@ -60,18 +81,8 @@ internal sealed class OneTimeSetupWindow : LWindow _pluginInterface = pluginInterface; _uiUtils = uiUtils; _logger = logger; - _recommendedPlugins = [ - new("Rotation Solver Reborn", - "RotationSolver", - """ - Automatically handles most combat interactions you encounter - during quests, including being interrupted by mobs. - """, - new Uri("https://github.com/FFXIV-CombatReborn/RotationSolverReborn"), - new Uri( - "https://raw.githubusercontent.com/FFXIV-CombatReborn/CombatRebornRepo/main/pluginmaster.json")), new PluginInfo("CBT (formerly known as Automaton)", "Automaton", """ @@ -120,6 +131,25 @@ internal sealed class OneTimeSetupWindow : LWindow ImGui.Separator(); ImGui.Spacing(); + ImGui.Text("Questionable supports multiple rotation/combat plugins, please pick the one\nyou want to use:"); + + using (ImRaii.PushIndent()) + { + if (ImGui.RadioButton("No rotation/combat plugin (combat must be done manually)", + _configuration.General.CombatModule == Configuration.ECombatModule.None)) + { + _configuration.General.CombatModule = Configuration.ECombatModule.None; + _pluginInterface.SavePluginConfig(_configuration); + } + + DrawCombatPlugin(Configuration.ECombatModule.BossMod, checklistPadding); + DrawCombatPlugin(Configuration.ECombatModule.RotationSolverReborn, checklistPadding); + } + + ImGui.Spacing(); + ImGui.Separator(); + ImGui.Spacing(); + ImGui.Text("The following plugins are recommended, but not required:"); using (ImRaii.PushIndent()) { @@ -164,39 +194,75 @@ internal sealed class OneTimeSetupWindow : LWindow private bool DrawPlugin(PluginInfo plugin, float checklistPadding) { - bool isInstalled = IsPluginInstalled(plugin); using (ImRaii.PushId("plugin_" + plugin.DisplayName)) { + bool isInstalled = IsPluginInstalled(plugin); _uiUtils.ChecklistItem(plugin.DisplayName, isInstalled); - using (ImRaii.PushIndent(checklistPadding)) + + DrawPluginDetails(plugin, checklistPadding, isInstalled); + return isInstalled; + } + } + + private void DrawCombatPlugin(Configuration.ECombatModule combatModule, float checklistPadding) + { + ImGui.Spacing(); + + PluginInfo plugin = CombatPlugins[combatModule]; + using (ImRaii.PushId("plugin_" + plugin.DisplayName)) + { + bool isInstalled = IsPluginInstalled(plugin); + if (ImGui.RadioButton(plugin.DisplayName, _configuration.General.CombatModule == combatModule)) { + _configuration.General.CombatModule = combatModule; + _pluginInterface.SavePluginConfig(_configuration); + } + + ImGui.SameLine(0); + using (_pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push()) + { + var iconColor = isInstalled ? ImGuiColors.ParsedGreen : ImGuiColors.DalamudRed; + var icon = isInstalled ? FontAwesomeIcon.Check : FontAwesomeIcon.Times; + + ImGui.AlignTextToFramePadding(); + ImGui.TextColored(iconColor, icon.ToIconString()); + } + + + DrawPluginDetails(plugin, checklistPadding, isInstalled); + } + } + + private void DrawPluginDetails(PluginInfo plugin, float checklistPadding, bool isInstalled) + { + using (ImRaii.PushIndent(checklistPadding)) + { + if (!string.IsNullOrEmpty(plugin.Details)) ImGui.TextUnformatted(plugin.Details); - if (plugin.DetailsToCheck != null) - { - foreach (var detail in plugin.DetailsToCheck) - _uiUtils.ChecklistItem(detail.DisplayName, isInstalled && detail.Predicate()); - } - ImGui.Spacing(); + if (plugin.DetailsToCheck != null) + { + foreach (var detail in plugin.DetailsToCheck) + _uiUtils.ChecklistItem(detail.DisplayName, isInstalled && detail.Predicate()); + } - if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Globe, "Open Website")) - Util.OpenLink(plugin.WebsiteUri.ToString()); + ImGui.Spacing(); - ImGui.SameLine(); - if (plugin.DalamudRepositoryUri != null) - { - if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Code, "Open Repository")) - Util.OpenLink(plugin.DalamudRepositoryUri.ToString()); - } - else - { - ImGui.AlignTextToFramePadding(); - ImGuiComponents.HelpMarker("Available on official Dalamud Repository"); - } + if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Globe, "Open Website")) + Util.OpenLink(plugin.WebsiteUri.ToString()); + + ImGui.SameLine(); + if (plugin.DalamudRepositoryUri != null) + { + if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Code, "Open Repository")) + Util.OpenLink(plugin.DalamudRepositoryUri.ToString()); + } + else + { + ImGui.AlignTextToFramePadding(); + ImGuiComponents.HelpMarker("Available on official Dalamud Repository"); } } - - return isInstalled; } private bool IsPluginInstalled(PluginInfo pluginInfo) diff --git a/Questionable/Windows/QuestWindow.cs b/Questionable/Windows/QuestWindow.cs index 4c100a1b..2bcdbf9d 100644 --- a/Questionable/Windows/QuestWindow.cs +++ b/Questionable/Windows/QuestWindow.cs @@ -44,7 +44,8 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig QuickAccessButtonsComponent quickAccessButtonsComponent, RemainingTasksComponent remainingTasksComponent, IFramework framework, - InteractionUiController interactionUiController) + InteractionUiController interactionUiController, + ConfigWindow configWindow) : base($"Questionable v{PluginVersion.ToString(2)}###Questionable", ImGuiWindowFlags.AlwaysAutoResize) { @@ -67,7 +68,7 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig #endif SizeConstraints = new WindowSizeConstraints { - MinimumSize = new Vector2(230, 30), + MinimumSize = new Vector2(240, 30), MaximumSize = default }; RespectCloseHotkey = false; @@ -87,6 +88,20 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig }; TitleBarButtons.Insert(0, _minimizeButton); + TitleBarButtons.Add(new TitleBarButton + { + Icon = FontAwesomeIcon.Cog, + IconOffset = new Vector2(1.5f, 1), + Click = _ => configWindow.IsOpen = true, + Priority = int.MinValue, + ShowTooltip = () => + { + ImGui.BeginTooltip(); + ImGui.Text("Open Configuration"); + ImGui.EndTooltip(); + } + }); + _activeQuestComponent.Reload += OnReload; _quickAccessButtonsComponent.Reload += OnReload; } diff --git a/Questionable/Windows/UiUtils.cs b/Questionable/Windows/UiUtils.cs index 9c51f253..b03fabfe 100644 --- a/Questionable/Windows/UiUtils.cs +++ b/Questionable/Windows/UiUtils.cs @@ -55,8 +55,7 @@ internal sealed class UiUtils public bool ChecklistItem(string text, Vector4 color, FontAwesomeIcon icon, float extraPadding = 0) { - // ReSharper disable once UnusedVariable - using (var font = _pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push()) + using (_pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push()) { ImGui.TextColored(color, icon.ToIconString()); }