Add config option to switch between VBM/RSR

This commit is contained in:
Liza 2024-12-22 21:26:29 +01:00
parent 471cc563f4
commit 118a5948a5
Signed by: liza
GPG Key ID: 2C41B84815CF6445
7 changed files with 172 additions and 48 deletions

View File

@ -7,9 +7,9 @@ namespace Questionable;
internal sealed class Configuration : IPluginConfiguration 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 int PluginSetupCompleteVersion { get; set; }
public GeneralConfiguration General { get; } = new(); public GeneralConfiguration General { get; } = new();
public NotificationConfiguration Notifications { get; } = new(); public NotificationConfiguration Notifications { get; } = new();
@ -23,6 +23,7 @@ internal sealed class Configuration : IPluginConfiguration
internal sealed class GeneralConfiguration internal sealed class GeneralConfiguration
{ {
public ECombatModule CombatModule { get; set; } = ECombatModule.BossMod;
public uint MountId { get; set; } = 71; public uint MountId { get; set; } = 71;
public GrandCompany GrandCompany { get; set; } = GrandCompany.None; public GrandCompany GrandCompany { get; set; } = GrandCompany.None;
public bool HideInAllInstances { get; set; } = true; public bool HideInAllInstances { get; set; } = true;
@ -45,4 +46,11 @@ internal sealed class Configuration : IPluginConfiguration
public bool NeverFly { get; set; } public bool NeverFly { get; set; }
public bool AdditionalStatusInformation { get; set; } public bool AdditionalStatusInformation { get; set; }
} }
internal enum ECombatModule
{
None,
BossMod,
RotationSolverReborn,
}
} }

View File

@ -12,22 +12,44 @@ using System.Numerics;
namespace Questionable.Controller.CombatModules; 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 const string Name = "BossMod";
private readonly ILogger<BossModModule> _logger = logger; private readonly ILogger<BossModModule> _logger;
private readonly MovementController _movementController = movementController; private readonly MovementController _movementController;
private readonly IClientState _clientState = clientState; private readonly IClientState _clientState;
private readonly ICallGateSubscriber<string, string?> _getPreset = pluginInterface.GetIpcSubscriber<string, string?>($"{Name}.Presets.Get"); private readonly Configuration _configuration;
private readonly ICallGateSubscriber<string, bool, bool> _createPreset = pluginInterface.GetIpcSubscriber<string, bool, bool>($"{Name}.Presets.Create"); private readonly ICallGateSubscriber<string, string?> _getPreset;
private readonly ICallGateSubscriber<string, bool> _setPreset = pluginInterface.GetIpcSubscriber<string, bool>($"{Name}.Presets.SetActive"); private readonly ICallGateSubscriber<string, bool, bool> _createPreset;
private readonly ICallGateSubscriber<bool> _clearPreset = pluginInterface.GetIpcSubscriber<bool>($"{Name}.Presets.ClearActive"); private readonly ICallGateSubscriber<string, bool> _setPreset;
private readonly ICallGateSubscriber<bool> _clearPreset;
private static Stream Preset => typeof(BossModModule).Assembly.GetManifestResourceStream("Questionable.Controller.CombatModules.BossModPreset")!; private static Stream Preset => typeof(BossModModule).Assembly.GetManifestResourceStream("Questionable.Controller.CombatModules.BossModPreset")!;
private DateTime _lastDistanceCheck = DateTime.MinValue; 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) public bool CanHandleFight(CombatController.CombatData combatData)
{ {
if (_configuration.General.CombatModule != Configuration.ECombatModule.BossMod)
return false;
try try
{ {
return _getPreset.HasFunction; return _getPreset.HasFunction;

View File

@ -16,17 +16,19 @@ internal sealed class RotationSolverRebornModule : ICombatModule, IDisposable
private readonly ILogger<RotationSolverRebornModule> _logger; private readonly ILogger<RotationSolverRebornModule> _logger;
private readonly MovementController _movementController; private readonly MovementController _movementController;
private readonly IClientState _clientState; private readonly IClientState _clientState;
private readonly Configuration _configuration;
private readonly ICallGateSubscriber<string, object> _test; private readonly ICallGateSubscriber<string, object> _test;
private readonly ICallGateSubscriber<StateCommandType, object> _changeOperationMode; private readonly ICallGateSubscriber<StateCommandType, object> _changeOperationMode;
private DateTime _lastDistanceCheck = DateTime.MinValue; private DateTime _lastDistanceCheck = DateTime.MinValue;
public RotationSolverRebornModule(ILogger<RotationSolverRebornModule> logger, MovementController movementController, public RotationSolverRebornModule(ILogger<RotationSolverRebornModule> logger, MovementController movementController,
IClientState clientState, IDalamudPluginInterface pluginInterface) IClientState clientState, IDalamudPluginInterface pluginInterface, Configuration configuration)
{ {
_logger = logger; _logger = logger;
_movementController = movementController; _movementController = movementController;
_clientState = clientState; _clientState = clientState;
_configuration = configuration;
_test = pluginInterface.GetIpcSubscriber<string, object>("RotationSolverReborn.Test"); _test = pluginInterface.GetIpcSubscriber<string, object>("RotationSolverReborn.Test");
_changeOperationMode = _changeOperationMode =
pluginInterface.GetIpcSubscriber<StateCommandType, object>("RotationSolverReborn.ChangeOperatingMode"); pluginInterface.GetIpcSubscriber<StateCommandType, object>("RotationSolverReborn.ChangeOperatingMode");
@ -34,6 +36,9 @@ internal sealed class RotationSolverRebornModule : ICombatModule, IDisposable
public bool CanHandleFight(CombatController.CombatData combatData) public bool CanHandleFight(CombatController.CombatData combatData)
{ {
if (_configuration.General.CombatModule != Configuration.ECombatModule.RotationSolverReborn)
return false;
try try
{ {
_test.InvokeAction("Validate RSR is callable from Questionable"); _test.InvokeAction("Validate RSR is callable from Questionable");

View File

@ -26,6 +26,7 @@ internal sealed class ConfigWindow : LWindow, IPersistableWindowConfig
private readonly uint[] _mountIds; private readonly uint[] _mountIds;
private readonly string[] _mountNames; private readonly string[] _mountNames;
private readonly string[] _combatModuleNames = ["None", "Boss Mod (VBM)", "Rotation Solver Reborn"];
private readonly string[] _grandCompanyNames = private readonly string[] _grandCompanyNames =
["None (manually pick quest)", "Maelstrom", "Twin Adder", "Immortal Flames"]; ["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"); using var tab = ImRaii.TabItem("General");
if (!tab) if (!tab)
return; 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); int selectedMount = Array.FindIndex(_mountIds, x => x == _configuration.General.MountId);
if (selectedMount == -1) if (selectedMount == -1)
{ {

View File

@ -44,6 +44,27 @@ internal sealed class OneTimeSetupWindow : LWindow
new Uri("https://github.com/NightmareXIV/MyDalamudPlugins/raw/main/pluginmaster.json")), 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 IReadOnlyList<PluginInfo> _recommendedPlugins;
private readonly Configuration _configuration; private readonly Configuration _configuration;
@ -60,18 +81,8 @@ internal sealed class OneTimeSetupWindow : LWindow
_pluginInterface = pluginInterface; _pluginInterface = pluginInterface;
_uiUtils = uiUtils; _uiUtils = uiUtils;
_logger = logger; _logger = logger;
_recommendedPlugins = _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)", new PluginInfo("CBT (formerly known as Automaton)",
"Automaton", "Automaton",
""" """
@ -120,6 +131,25 @@ internal sealed class OneTimeSetupWindow : LWindow
ImGui.Separator(); ImGui.Separator();
ImGui.Spacing(); 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:"); ImGui.Text("The following plugins are recommended, but not required:");
using (ImRaii.PushIndent()) using (ImRaii.PushIndent())
{ {
@ -164,39 +194,75 @@ internal sealed class OneTimeSetupWindow : LWindow
private bool DrawPlugin(PluginInfo plugin, float checklistPadding) private bool DrawPlugin(PluginInfo plugin, float checklistPadding)
{ {
bool isInstalled = IsPluginInstalled(plugin);
using (ImRaii.PushId("plugin_" + plugin.DisplayName)) using (ImRaii.PushId("plugin_" + plugin.DisplayName))
{ {
bool isInstalled = IsPluginInstalled(plugin);
_uiUtils.ChecklistItem(plugin.DisplayName, isInstalled); _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); 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")) ImGui.Spacing();
Util.OpenLink(plugin.WebsiteUri.ToString());
ImGui.SameLine(); if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Globe, "Open Website"))
if (plugin.DalamudRepositoryUri != null) Util.OpenLink(plugin.WebsiteUri.ToString());
{
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Code, "Open Repository")) ImGui.SameLine();
Util.OpenLink(plugin.DalamudRepositoryUri.ToString()); if (plugin.DalamudRepositoryUri != null)
} {
else if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Code, "Open Repository"))
{ Util.OpenLink(plugin.DalamudRepositoryUri.ToString());
ImGui.AlignTextToFramePadding(); }
ImGuiComponents.HelpMarker("Available on official Dalamud Repository"); else
} {
ImGui.AlignTextToFramePadding();
ImGuiComponents.HelpMarker("Available on official Dalamud Repository");
} }
} }
return isInstalled;
} }
private bool IsPluginInstalled(PluginInfo pluginInfo) private bool IsPluginInstalled(PluginInfo pluginInfo)

View File

@ -44,7 +44,8 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
QuickAccessButtonsComponent quickAccessButtonsComponent, QuickAccessButtonsComponent quickAccessButtonsComponent,
RemainingTasksComponent remainingTasksComponent, RemainingTasksComponent remainingTasksComponent,
IFramework framework, IFramework framework,
InteractionUiController interactionUiController) InteractionUiController interactionUiController,
ConfigWindow configWindow)
: base($"Questionable v{PluginVersion.ToString(2)}###Questionable", : base($"Questionable v{PluginVersion.ToString(2)}###Questionable",
ImGuiWindowFlags.AlwaysAutoResize) ImGuiWindowFlags.AlwaysAutoResize)
{ {
@ -67,7 +68,7 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
#endif #endif
SizeConstraints = new WindowSizeConstraints SizeConstraints = new WindowSizeConstraints
{ {
MinimumSize = new Vector2(230, 30), MinimumSize = new Vector2(240, 30),
MaximumSize = default MaximumSize = default
}; };
RespectCloseHotkey = false; RespectCloseHotkey = false;
@ -87,6 +88,20 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
}; };
TitleBarButtons.Insert(0, _minimizeButton); 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; _activeQuestComponent.Reload += OnReload;
_quickAccessButtonsComponent.Reload += OnReload; _quickAccessButtonsComponent.Reload += OnReload;
} }

View File

@ -55,8 +55,7 @@ internal sealed class UiUtils
public bool ChecklistItem(string text, Vector4 color, FontAwesomeIcon icon, float extraPadding = 0) public bool ChecklistItem(string text, Vector4 color, FontAwesomeIcon icon, float extraPadding = 0)
{ {
// ReSharper disable once UnusedVariable using (_pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push())
using (var font = _pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push())
{ {
ImGui.TextColored(color, icon.ToIconString()); ImGui.TextColored(color, icon.ToIconString());
} }