From e779275362cbc24225511c5de67ad3cdcb11f604 Mon Sep 17 00:00:00 2001 From: Ethan Henderson Date: Mon, 19 Jan 2026 23:15:31 -0700 Subject: [PATCH 1/5] feat: Add WrathCombo.API --- Questionable/Questionable.csproj | 1 + Questionable/QuestionablePlugin.cs | 3 +++ Questionable/packages.lock.json | 8 +++++++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Questionable/Questionable.csproj b/Questionable/Questionable.csproj index 210a0cc2e..516f49330 100644 --- a/Questionable/Questionable.csproj +++ b/Questionable/Questionable.csproj @@ -14,6 +14,7 @@ + diff --git a/Questionable/QuestionablePlugin.cs b/Questionable/QuestionablePlugin.cs index fcf2ab7dc..a4ae46446 100644 --- a/Questionable/QuestionablePlugin.cs +++ b/Questionable/QuestionablePlugin.cs @@ -31,6 +31,8 @@ using Questionable.Windows.JournalComponents; using Questionable.Windows.QuestComponents; using Questionable.Windows.Utils; +using WrathCombo.API; +using WrathError = WrathCombo.API.WrathIPCWrapper.ErrorType; using Action = Questionable.Controller.Steps.Interactions.Action; namespace Questionable; @@ -62,6 +64,7 @@ public QuestionablePlugin(IDalamudPluginInterface pluginInterface, ArgumentNullException.ThrowIfNull(pluginInterface); ArgumentNullException.ThrowIfNull(chatGui); ECommonsMain.Init(pluginInterface, this, Module.DalamudReflector); + WrathIPCWrapper.Init(pluginInterface, WrathError.IPCNotReady | WrathError.Unexpected); try { diff --git a/Questionable/packages.lock.json b/Questionable/packages.lock.json index 192b52fb2..777f27dc2 100644 --- a/Questionable/packages.lock.json +++ b/Questionable/packages.lock.json @@ -59,6 +59,12 @@ "resolved": "9.0.3", "contentHash": "r2JRkLjsYrq5Dpo7+y3Wa73OfirZPdVhxiTJWwZ+oJM7FOAe0LkM3GlH+pgkNRdd1G1kwUbmRCdmh4uoaWwu1g==" }, + "WrathCombo.API": { + "type": "Direct", + "requested": "[0.5.2, )", + "resolved": "0.5.2", + "contentHash": "O8nlZah+j8v0IGPkZbpdzP/YDG3tjySby+Lo7HvDQPa9G5Vtu/qkonSCdHTqYwTDXxnH3ZseVJbDr8ZAOsA4zw==" + }, "Humanizer.Core": { "type": "Transitive", "resolved": "2.14.1", @@ -125,7 +131,7 @@ "type": "Project", "dependencies": { "DalamudPackager": "[13.1.0, )", - "ECommons": "[3.1.0.3, )", + "ECommons": "[3.1.0.16, )", "JetBrains.Annotations": "[2024.3.0, )" } }, From e3a7697221f5f75e135fd5d7df88632a8552cc88 Mon Sep 17 00:00:00 2001 From: Ethan Henderson Date: Mon, 19 Jan 2026 23:16:05 -0700 Subject: [PATCH 2/5] feat(Wrath): Switch to `WrathCombo.API`, configure Wrath more fully --- .../CombatModules/WrathComboModule.cs | 271 ++++++++++-------- 1 file changed, 159 insertions(+), 112 deletions(-) diff --git a/Questionable/Controller/CombatModules/WrathComboModule.cs b/Questionable/Controller/CombatModules/WrathComboModule.cs index 81a10aa88..121fa845c 100644 --- a/Questionable/Controller/CombatModules/WrathComboModule.cs +++ b/Questionable/Controller/CombatModules/WrathComboModule.cs @@ -1,12 +1,15 @@ using System; -using System.Diagnostics.CodeAnalysis; +using System.Data; +using System.Linq; using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Plugin; using Dalamud.Plugin.Ipc; -using Dalamud.Plugin.Ipc.Exceptions; -using JetBrains.Annotations; using Microsoft.Extensions.Logging; using Questionable.Controller.Steps; +using WrathCombo.API; +using WrathCombo.API.Enum; +using WrathCombo.API.Extension; +using WrathError = WrathCombo.API.Error; namespace Questionable.Controller.CombatModules; @@ -16,12 +19,6 @@ internal sealed class WrathComboModule : ICombatModule, IDisposable private readonly ILogger _logger; private readonly Configuration _configuration; - private readonly ICallGateSubscriber _test; - private readonly ICallGateSubscriber _registerForLeaseWithCallback; - private readonly ICallGateSubscriber _releaseControl; - private readonly ICallGateSubscriber _setAutoRotationState; - private readonly ICallGateSubscriber _setAutoRotationConfigState; - private readonly ICallGateSubscriber _setCurrentJobAutoRotationReady; private readonly ICallGateProvider _callback; private Guid? _lease; @@ -31,14 +28,6 @@ public WrathComboModule(ILogger logger, Configuration configur { _logger = logger; _configuration = configuration; - _test = pluginInterface.GetIpcSubscriber("WrathCombo.Test"); - _registerForLeaseWithCallback = - pluginInterface.GetIpcSubscriber("WrathCombo.RegisterForLeaseWithCallback"); - _releaseControl = pluginInterface.GetIpcSubscriber("WrathCombo.ReleaseControl"); - _setAutoRotationState = pluginInterface.GetIpcSubscriber("WrathCombo.SetAutoRotationState"); - _setAutoRotationConfigState = pluginInterface.GetIpcSubscriber("WrathCombo.SetAutoRotationConfigState"); - _setCurrentJobAutoRotationReady = - pluginInterface.GetIpcSubscriber("WrathCombo.SetCurrentJobAutoRotationReady"); _callback = pluginInterface.GetIpcProvider($"{CallbackPrefix}.WrathComboCallback"); _callback.RegisterAction(Callback); @@ -51,61 +40,145 @@ public bool CanHandleFight(CombatController.CombatData combatData) try { - _test.InvokeAction(); + WrathIPCWrapper.Test(); + if (!WrathIPCWrapper.IPCReady()) + throw new EvaluateException("WrathCombo IPC not ready"); return true; } - catch (IpcError) + catch (WrathError.Exception e) when (e is WrathError.APIBehindException or + WrathError.UninitializedException) { - return false; + _logger.LogWarning(e, "Problem with WrathCombo.API usage. " + + "Please report to Questionable or Wrath team."); + } + catch (EvaluateException e) + { + _logger.LogWarning(e, "Problem with WrathCombo usage. " + + "Please report to Wrath team."); } + catch (Exception) + { + // Ignore + } + + return false; } public bool Start(CombatController.CombatData combatData) { try { - _lease = _registerForLeaseWithCallback.InvokeFunc("Questionable", "Questionable", CallbackPrefix); - if (_lease != null) + _lease = WrathIPCWrapper.RegisterForLeaseWithCallback( + "Questionable", + "Questionable", + CallbackPrefix); + + if (!_lease.HasValue) { - _logger.LogDebug("Wrath combo lease: {Lease}", _lease.Value); - - ESetResult autoRotationSet = _setAutoRotationState.InvokeFunc(_lease.Value, true); - if (!autoRotationSet.IsSuccess()) - { - _logger.LogError("Unable to set autorotation state"); - Stop(); - return false; - } - - ESetResult currentJobSetForAutoRotation = _setCurrentJobAutoRotationReady.InvokeFunc(_lease.Value); - if (!currentJobSetForAutoRotation.IsSuccess()) - { - _logger.LogError("Unable to set current job for autorotation"); - Stop(); - return false; - } - - ESetResult healerRotationModeSet = _setAutoRotationConfigState.InvokeFunc(_lease.Value, - AutoRotationConfigOption.HealerRotationMode, HealerRotationMode.Lowest_Current); - if (!healerRotationModeSet.IsSuccess()) - { - _logger.LogError("Unable to configure healing priority for autorotation: {Result}", - healerRotationModeSet); - } - - return true; + _logger.LogError("Problem with WrathCombo leasing. " + + "Please report to Questionable or Wrath team."); + return false; } - else + + SetResult autoRotationSet = WrathIPCWrapper + .SetAutoRotationState(_lease.Value); + if (!autoRotationSet.IsSuccess()) { - _logger.LogError("Wrath combo did not return a lease"); + _logger.LogError("Unable to set Wrath's Auto Rotation state"); + Stop(); return false; } + + SetResult currentJobSetForAutoRotation = WrathIPCWrapper + .SetCurrentJobAutoRotationReady(_lease.Value); + if (!currentJobSetForAutoRotation.IsSuccess()) + { + _logger.LogError("Unable to set Wrath to be Auto Rotation-ready"); + Stop(); + return false; + } + + // Make Wrath Work + SetResult targetingMode = WrathIPCWrapper + .SetAutoRotationConfigState(_lease.Value, + AutoRotationConfigOption.DPSRotationMode, + DPSRotationMode.Manual); + SetResult healerRotationMode = WrathIPCWrapper + .SetAutoRotationConfigState(_lease.Value, + AutoRotationConfigOption.HealerRotationMode, + HealerRotationMode.Lowest_Current); + SetResult healerMagicTargeting = WrathIPCWrapper + .SetAutoRotationConfigState(_lease.Value, + AutoRotationConfigOption.HealerAlwaysHardTarget, + false); + SetResult combatOnly = WrathIPCWrapper + .SetAutoRotationConfigState(_lease.Value, + AutoRotationConfigOption.InCombatOnly,false); + + // Make Wrath Work well + SetResult includeNPCs = WrathIPCWrapper + .SetAutoRotationConfigState(_lease.Value, + AutoRotationConfigOption.IncludeNPCs, + true); + SetResult targetCombatOnly = WrathIPCWrapper + .SetAutoRotationConfigState(_lease.Value, + AutoRotationConfigOption.OnlyAttackInCombat, + false); + SetResult cleanse = WrathIPCWrapper + .SetAutoRotationConfigState(_lease.Value, + AutoRotationConfigOption.AutoCleanse, + true); + + // Nice-to-haves + SetResult rez = WrathIPCWrapper + .SetAutoRotationConfigState(_lease.Value, + AutoRotationConfigOption.AutoRez, + true); + SetResult rezAsDPS = WrathIPCWrapper + .SetAutoRotationConfigState(_lease.Value, + AutoRotationConfigOption.AutoRezDPSJobs, + true); + SetResult kardia = WrathIPCWrapper + .SetAutoRotationConfigState(_lease.Value, + AutoRotationConfigOption.ManageKardia, + true); + SetResult aoeTargetThreshold = WrathIPCWrapper + .SetAutoRotationConfigState(_lease.Value, + AutoRotationConfigOption.DPSAoETargets, + 2); + + if (!WrathResultExtensions.AllSuccessful(out string failed, + ("HealerRotationMode", healerRotationMode), + ("DPSRotationMode", targetingMode), + ("InCombatOnly", combatOnly), + ("IncludeNPCs", includeNPCs), + ("OnlyAttackInCombat", targetCombatOnly), + ("AutoRez", rez), + ("AutoRezDPSJobs", rezAsDPS), + ("AutoCleanse", cleanse), + ("HealerAlwaysHardTarget", healerMagicTargeting), + ("ManageKardia", kardia), + ("DPSAoETargets", aoeTargetThreshold))) + { + _logger.LogError("Unable to configure Wrath Auto Rotation " + + "settings: {Result}", + string.Join(", ", failed)); + Stop(); + } + + return true; } - catch (IpcError e) + catch (WrathError.IPCException e) { - _logger.LogError(e, "Unable to use wrath combo for combat"); - return false; + _logger.LogWarning(e, "Problem with Wrath Combo Setup. " + + "Please report to Wrath team."); + } + catch (Exception) + { + // Ignore } + + return false; } public bool Stop() @@ -113,18 +186,25 @@ public bool Stop() try { if (_lease != null) - { - _releaseControl.InvokeAction(_lease.Value); - _lease = null; - } + WrathIPCWrapper.ReleaseControl(_lease.Value); return true; } - catch (IpcError e) + catch (WrathError.IPCException e) { - _logger.LogWarning(e, "Could not turn off wrath combo"); - return false; + _logger.LogWarning(e, "Problem with Wrath Combo stopping. " + + "Please report to Wrath team."); } + catch (Exception) + { + // Ignore + } + finally + { + _lease = null; + } + + return false; } public void Update(IGameObject nextTarget) @@ -137,7 +217,10 @@ public void Update(IGameObject nextTarget) private void Callback(int reason, string additionalInfo) { - _logger.LogWarning("WrathCombo callback: {Reason} ({Info})", reason, additionalInfo); + CancellationReason realReason = (CancellationReason)reason; + _logger.LogWarning("WrathCombo IPC Lease Cancelled: {ReasonDescription} " + + "({Reason}); for: {Info})", + realReason.Description, realReason.ToString(), additionalInfo); _lease = null; } @@ -146,61 +229,25 @@ public void Dispose() Stop(); _callback.UnregisterAction(); } +} - [PublicAPI] - [SuppressMessage("ReSharper", "InconsistentNaming")] - public enum ESetResult - { - Okay = 0, - OkayWorking = 1, - - IpcDisabled = 10, - InvalidLease = 11, - BlacklistedLease = 12, - Duplicate = 13, - PlayerNotAvailable = 14, - InvalidConfiguration = 15, - InvalidValue = 16, - } - - [PublicAPI] - [SuppressMessage("ReSharper", "InconsistentNaming")] - public enum AutoRotationConfigOption +internal static class WrathResultExtensions +{ + public static bool AllSuccessful + (out string failedVariableNames, + params (string name, SetResult result)[] results) { - InCombatOnly = 0, - DPSRotationMode = 1, - HealerRotationMode = 2, - FATEPriority = 3, - QuestPriority = 4, - SingleTargetHPP = 5, - AoETargetHPP = 6, - SingleTargetRegenHPP = 7, - ManageKardia = 8, - AutoRez = 9, - AutoRezDPSJobs = 10, - AutoCleanse = 11, - IncludeNPCs = 12, - OnlyAttackInCombat = 13, - OrbwalkerIntegration = 14, - AutoRezOutOfParty = 15, - DPSAoETargets = 16, - SingleTargetExcogHPP = 17, - } + var failed = results + .Where(r => !r.result.IsSuccess()) + .Select(r => r.name) + .ToArray(); - [PublicAPI] - [SuppressMessage("ReSharper", "InconsistentNaming")] - public enum HealerRotationMode - { - Manual = 0, - Highest_Current = 1, - Lowest_Current = 2, + failedVariableNames = string.Join(", ", failed); + return failed.Length == 0; } -} -internal static class WrathResultExtensions -{ - public static bool IsSuccess(this WrathComboModule.ESetResult result) + public static bool IsSuccess(this SetResult result) { - return result is WrathComboModule.ESetResult.Okay or WrathComboModule.ESetResult.OkayWorking; + return result is SetResult.Okay or SetResult.OkayWorking; } } From 0122a48cd0859c929391de8631dd92402ae26dbd Mon Sep 17 00:00:00 2001 From: Ethan Henderson Date: Mon, 19 Jan 2026 23:22:14 -0700 Subject: [PATCH 3/5] fix(Wrath): Disable rezzing of non-party players --- Questionable/Controller/CombatModules/WrathComboModule.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Questionable/Controller/CombatModules/WrathComboModule.cs b/Questionable/Controller/CombatModules/WrathComboModule.cs index 121fa845c..acc758942 100644 --- a/Questionable/Controller/CombatModules/WrathComboModule.cs +++ b/Questionable/Controller/CombatModules/WrathComboModule.cs @@ -146,6 +146,10 @@ public bool Start(CombatController.CombatData combatData) .SetAutoRotationConfigState(_lease.Value, AutoRotationConfigOption.DPSAoETargets, 2); + SetResult rezNonParty = WrathIPCWrapper + .SetAutoRotationConfigState(_lease.Value, + AutoRotationConfigOption.AutoRezOutOfParty, + false); if (!WrathResultExtensions.AllSuccessful(out string failed, ("HealerRotationMode", healerRotationMode), @@ -158,7 +162,8 @@ public bool Start(CombatController.CombatData combatData) ("AutoCleanse", cleanse), ("HealerAlwaysHardTarget", healerMagicTargeting), ("ManageKardia", kardia), - ("DPSAoETargets", aoeTargetThreshold))) + ("DPSAoETargets", aoeTargetThreshold), + ("AutoRezOutOfParty", rezNonParty))) { _logger.LogError("Unable to configure Wrath Auto Rotation " + "settings: {Result}", From f985b204013dcc68b717e810d6cb03b0bc5ae798 Mon Sep 17 00:00:00 2001 From: Ethan Henderson Date: Mon, 19 Jan 2026 23:24:25 -0700 Subject: [PATCH 4/5] fix(Wrath): Removes some opinion from the settings --- Questionable/Controller/CombatModules/WrathComboModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Questionable/Controller/CombatModules/WrathComboModule.cs b/Questionable/Controller/CombatModules/WrathComboModule.cs index acc758942..7407b21c9 100644 --- a/Questionable/Controller/CombatModules/WrathComboModule.cs +++ b/Questionable/Controller/CombatModules/WrathComboModule.cs @@ -145,7 +145,7 @@ public bool Start(CombatController.CombatData combatData) SetResult aoeTargetThreshold = WrathIPCWrapper .SetAutoRotationConfigState(_lease.Value, AutoRotationConfigOption.DPSAoETargets, - 2); + 3); SetResult rezNonParty = WrathIPCWrapper .SetAutoRotationConfigState(_lease.Value, AutoRotationConfigOption.AutoRezOutOfParty, From 73af149577bc4e834a12bbb0c43130957c29aa4f Mon Sep 17 00:00:00 2001 From: Ethan Henderson Date: Thu, 12 Feb 2026 16:18:03 -0700 Subject: [PATCH 5/5] style(Wrath): Updated formatting --- .../CombatModules/WrathComboModule.cs | 73 +++++++++++-------- 1 file changed, 41 insertions(+), 32 deletions(-) diff --git a/Questionable/Controller/CombatModules/WrathComboModule.cs b/Questionable/Controller/CombatModules/WrathComboModule.cs index 7407b21c9..314a42a38 100644 --- a/Questionable/Controller/CombatModules/WrathComboModule.cs +++ b/Questionable/Controller/CombatModules/WrathComboModule.cs @@ -1,4 +1,6 @@ -using System; +#region + +using System; using System.Data; using System.Linq; using Dalamud.Game.ClientState.Objects.Types; @@ -11,31 +13,37 @@ using WrathCombo.API.Extension; using WrathError = WrathCombo.API.Error; +#endregion + namespace Questionable.Controller.CombatModules; internal sealed class WrathComboModule : ICombatModule, IDisposable { - private const string CallbackPrefix = "Questionable$Wrath"; + private const string CallbackPrefix = "Questionable$Wrath"; + private readonly ICallGateProvider _callback; + private readonly Configuration _configuration; private readonly ILogger _logger; - private readonly Configuration _configuration; - private readonly ICallGateProvider _callback; private Guid? _lease; - public WrathComboModule(ILogger logger, Configuration configuration, + public WrathComboModule(ILogger logger, + Configuration configuration, IDalamudPluginInterface pluginInterface) { - _logger = logger; + _logger = logger; _configuration = configuration; - _callback = pluginInterface.GetIpcProvider($"{CallbackPrefix}.WrathComboCallback"); + _callback = + pluginInterface.GetIpcProvider( + $"{CallbackPrefix}.WrathComboCallback"); _callback.RegisterAction(Callback); } public bool CanHandleFight(CombatController.CombatData combatData) { - if (_configuration.General.CombatModule != Configuration.ECombatModule.WrathCombo) + if (_configuration.General.CombatModule != + Configuration.ECombatModule.WrathCombo) return false; try @@ -46,15 +54,15 @@ public bool CanHandleFight(CombatController.CombatData combatData) return true; } catch (WrathError.Exception e) when (e is WrathError.APIBehindException or - WrathError.UninitializedException) + WrathError.UninitializedException) { _logger.LogWarning(e, "Problem with WrathCombo.API usage. " + - "Please report to Questionable or Wrath team."); + "Please report to Questionable or Wrath team."); } catch (EvaluateException e) { _logger.LogWarning(e, "Problem with WrathCombo usage. " + - "Please report to Wrath team."); + "Please report to Wrath team."); } catch (Exception) { @@ -69,7 +77,7 @@ public bool Start(CombatController.CombatData combatData) try { _lease = WrathIPCWrapper.RegisterForLeaseWithCallback( - "Questionable", + "Questionable", "Questionable", CallbackPrefix); @@ -113,42 +121,43 @@ public bool Start(CombatController.CombatData combatData) false); SetResult combatOnly = WrathIPCWrapper .SetAutoRotationConfigState(_lease.Value, - AutoRotationConfigOption.InCombatOnly,false); + AutoRotationConfigOption.InCombatOnly, + false); // Make Wrath Work well SetResult includeNPCs = WrathIPCWrapper .SetAutoRotationConfigState(_lease.Value, - AutoRotationConfigOption.IncludeNPCs, - true); + AutoRotationConfigOption.IncludeNPCs, + true); SetResult targetCombatOnly = WrathIPCWrapper .SetAutoRotationConfigState(_lease.Value, - AutoRotationConfigOption.OnlyAttackInCombat, - false); + AutoRotationConfigOption.OnlyAttackInCombat, + false); SetResult cleanse = WrathIPCWrapper .SetAutoRotationConfigState(_lease.Value, - AutoRotationConfigOption.AutoCleanse, - true); + AutoRotationConfigOption.AutoCleanse, + true); // Nice-to-haves SetResult rez = WrathIPCWrapper .SetAutoRotationConfigState(_lease.Value, - AutoRotationConfigOption.AutoRez, + AutoRotationConfigOption.AutoRez, true); SetResult rezAsDPS = WrathIPCWrapper .SetAutoRotationConfigState(_lease.Value, - AutoRotationConfigOption.AutoRezDPSJobs, + AutoRotationConfigOption.AutoRezDPSJobs, true); SetResult kardia = WrathIPCWrapper .SetAutoRotationConfigState(_lease.Value, - AutoRotationConfigOption.ManageKardia, + AutoRotationConfigOption.ManageKardia, true); SetResult aoeTargetThreshold = WrathIPCWrapper .SetAutoRotationConfigState(_lease.Value, - AutoRotationConfigOption.DPSAoETargets, + AutoRotationConfigOption.DPSAoETargets, 3); SetResult rezNonParty = WrathIPCWrapper .SetAutoRotationConfigState(_lease.Value, - AutoRotationConfigOption.AutoRezOutOfParty, + AutoRotationConfigOption.AutoRezOutOfParty, false); if (!WrathResultExtensions.AllSuccessful(out string failed, @@ -220,20 +229,20 @@ public void Update(IGameObject nextTarget) public bool CanAttack(IBattleNpc target) => true; + public void Dispose() + { + Stop(); + _callback.UnregisterAction(); + } + private void Callback(int reason, string additionalInfo) { CancellationReason realReason = (CancellationReason)reason; _logger.LogWarning("WrathCombo IPC Lease Cancelled: {ReasonDescription} " + - "({Reason}); for: {Info})", + "({Reason}; for: {Info})", realReason.Description, realReason.ToString(), additionalInfo); _lease = null; } - - public void Dispose() - { - Stop(); - _callback.UnregisterAction(); - } } internal static class WrathResultExtensions @@ -255,4 +264,4 @@ public static bool IsSuccess(this SetResult result) { return result is SetResult.Okay or SetResult.OkayWorking; } -} +} \ No newline at end of file