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,7 +7,7 @@ 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 PluginSetupCompleteVersion { get; set; }
@ -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,
}
}

View 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;

View 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");

View 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)
{

View 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,13 +194,52 @@ 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);
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)
@ -196,9 +265,6 @@ internal sealed class OneTimeSetupWindow : LWindow
}
}
return isInstalled;
}
private bool IsPluginInstalled(PluginInfo pluginInfo)
{
return _pluginInterface.InstalledPlugins.Any(x => x.InternalName == pluginInfo.InternalName && x.IsLoaded);

View 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;
}

View 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());
}