Add config option to switch between VBM/RSR
authorLiza Carvelli <liza@carvel.li>
Sun, 22 Dec 2024 20:26:29 +0000 (21:26 +0100)
committerLiza Carvelli <liza@carvel.li>
Sun, 22 Dec 2024 20:26:29 +0000 (21:26 +0100)
Questionable/Configuration.cs
Questionable/Controller/CombatModules/BossModModule.cs
Questionable/Controller/CombatModules/RotationSolverRebornModule.cs
Questionable/Windows/ConfigWindow.cs
Questionable/Windows/OneTimeSetupWindow.cs
Questionable/Windows/QuestWindow.cs
Questionable/Windows/UiUtils.cs

index 29358b34eaf241c0e12897859aa13257206eff9a..ce4c961659eca8cda2f1e79622e6c415fddde6b0 100644 (file)
@@ -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; } =;
+    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,
+    }
 }
index a26d1c74530922ab4578adce128b7f8cfa30ca2a..f27a6f8104180ecb5f885815b5b22e453cf6ed63 100644 (file)
@@ -12,22 +12,44 @@ using System.Numerics;
 
 namespace Questionable.Controller.CombatModules;
 
-internal sealed class BossModModule(ILogger<BossModModule> logger, MovementController movementController, IClientState clientState, IDalamudPluginInterface pluginInterface) : ICombatModule, IDisposable
+internal sealed class BossModModule : ICombatModule, IDisposable
 {
     private const string Name = "BossMod";
-    private readonly ILogger<BossModModule> _logger = logger;
-    private readonly MovementController _movementController = movementController;
-    private readonly IClientState _clientState = clientState;
-    private readonly ICallGateSubscriber<string, string?> _getPreset = pluginInterface.GetIpcSubscriber<string, string?>($"{Name}.Presets.Get");
-    private readonly ICallGateSubscriber<string, bool, bool> _createPreset = pluginInterface.GetIpcSubscriber<string, bool, bool>($"{Name}.Presets.Create");
-    private readonly ICallGateSubscriber<string, bool> _setPreset = pluginInterface.GetIpcSubscriber<string, bool>($"{Name}.Presets.SetActive");
-    private readonly ICallGateSubscriber<bool> _clearPreset = pluginInterface.GetIpcSubscriber<bool>($"{Name}.Presets.ClearActive");
+    private readonly ILogger<BossModModule> _logger;
+    private readonly MovementController _movementController;
+    private readonly IClientState _clientState;
+    private readonly Configuration _configuration;
+    private readonly ICallGateSubscriber<string, string?> _getPreset;
+    private readonly ICallGateSubscriber<string, bool, bool> _createPreset;
+    private readonly ICallGateSubscriber<string, bool> _setPreset;
+    private readonly ICallGateSubscriber<bool> _clearPreset;
 
     private static Stream Preset => typeof(BossModModule).Assembly.GetManifestResourceStream("Questionable.Controller.CombatModules.BossModPreset")!;
     private DateTime _lastDistanceCheck = DateTime.MinValue;
 
+    public BossModModule(
+        ILogger<BossModModule> logger,
+        MovementController movementController,
+        IClientState clientState,
+        IDalamudPluginInterface pluginInterface,
+        Configuration configuration)
+    {
+        _logger = logger;
+        _movementController = movementController;
+        _clientState = clientState;
+        _configuration = configuration;
+
+        _getPreset = pluginInterface.GetIpcSubscriber<string, string?>($"{Name}.Presets.Get");
+        _createPreset = pluginInterface.GetIpcSubscriber<string, bool, bool>($"{Name}.Presets.Create");
+        _setPreset = pluginInterface.GetIpcSubscriber<string, bool>($"{Name}.Presets.SetActive");
+        _clearPreset = pluginInterface.GetIpcSubscriber<bool>($"{Name}.Presets.ClearActive");
+    }
+
     public bool CanHandleFight(CombatController.CombatData combatData)
     {
+        if (_configuration.General.CombatModule != Configuration.ECombatModule.BossMod)
+            return false;
+
         try
         {
             return _getPreset.HasFunction;
index 581e07a9665231e4d0b2afa606cee09cf1908528..d5f13f4efae75840a2ff24972583695f9cf9f73f 100644 (file)
@@ -16,17 +16,19 @@ internal sealed class RotationSolverRebornModule : ICombatModule, IDisposable
     private readonly ILogger<RotationSolverRebornModule> _logger;
     private readonly MovementController _movementController;
     private readonly IClientState _clientState;
+    private readonly Configuration _configuration;
     private readonly ICallGateSubscriber<string, object> _test;
     private readonly ICallGateSubscriber<StateCommandType, object> _changeOperationMode;
 
     private DateTime _lastDistanceCheck = DateTime.MinValue;
 
     public RotationSolverRebornModule(ILogger<RotationSolverRebornModule> logger, MovementController movementController,
-        IClientState clientState, IDalamudPluginInterface pluginInterface)
+        IClientState clientState, IDalamudPluginInterface pluginInterface, Configuration configuration)
     {
         _logger = logger;
         _movementController = movementController;
         _clientState = clientState;
+        _configuration = configuration;
         _test = pluginInterface.GetIpcSubscriber<string, object>("RotationSolverReborn.Test");
         _changeOperationMode =
             pluginInterface.GetIpcSubscriber<StateCommandType, object>("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");
index 27c968a0f0a5ca0f23d8d4e925a8789beb054e02..7cd19627f5a827a6ee2fd2e889785f5530d479e3 100644 (file)
@@ -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)
         {
index b94b3fc80a047458119c1b4955f22fb40b59699c..3ba35a09619d433691ce7debb9282e978bc1ad3a 100644 (file)
@@ -44,6 +44,27 @@ internal sealed class OneTimeSetupWindow : LWindow
             new Uri("https://github.com/NightmareXIV/MyDalamudPlugins/raw/main/pluginmaster.json")),
     ];
 
+    private static readonly IReadOnlyDictionary<Configuration.ECombatModule, PluginInfo> CombatPlugins = new Dictionary<Configuration.ECombatModule, PluginInfo>
+    {
+        {
+            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<PluginInfo> _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)
index 4c100a1ba829b91b5a9173a93c42c4ed53c0d4ad..2bcdbf9d2befe2bcfd96fcac470877088485a8c3 100644 (file)
@@ -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;
     }
index 9c51f253f358391243b0e84701ebab485f71d449..b03fabfe0784f59c2dd56274304e992ef504162c 100644 (file)
@@ -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());
         }