internal sealed class Configuration : IPluginConfiguration
{
- public const int PluginSetupVersion = 3;
+ public const int PluginSetupVersion = 4;
public int Version { get; set; } = 1;
public int PluginSetupCompleteVersion { get; set; }
internal sealed class GeneralConfiguration
{
- public ECombatModule CombatModule { get; set; } = ECombatModule.BossMod;
+ public ECombatModule CombatModule { get; set; } = ECombatModule.None;
public uint MountId { get; set; } = 71;
public GrandCompany GrandCompany { get; set; } = GrandCompany.None;
public bool HideInAllInstances { get; set; } = true;
{
None,
BossMod,
+ WrathCombo,
RotationSolverReborn,
}
}
using FFXIVClientStructs.FFXIV.Common.Math;
using Microsoft.Extensions.Logging;
using Questionable.Controller.CombatModules;
+using Questionable.Controller.Steps;
using Questionable.Controller.Utils;
using Questionable.Functions;
+using Questionable.Model;
using Questionable.Model.Questing;
namespace Questionable.Controller;
{
Module = combatModule,
Data = combatData,
+ LastDistanceCheck = DateTime.Now,
};
_wasInCombat = combatData.SpawnType is EEnemySpawnType.QuestInterruption or EEnemySpawnType.FinishCombatIfAny;
return true;
if (nextTarget != null && nextTarget.Equals(target))
{
- _currentFight.Module.Update(target);
+ if (!IsMovingOrShouldMove(target))
+ {
+ try
+ {
+ _currentFight.Module.Update(target);
+ }
+ catch (TaskException e)
+ {
+ _logger.LogWarning(e, "Combat was interrupted, stopping: {Exception}", e.Message);
+ SetTarget(null);
+ }
+ }
}
else if (nextTarget != null)
{
{
_logger.LogInformation("Moving to target, distance: {Distance:N2}",
Vector3.Distance(_clientState.LocalPlayer!.Position, target.Position));
- _currentFight!.Module.MoveToTarget(target);
+ MoveToTarget(target);
}
else
{
_logger.LogInformation("Setting target to {TargetName} ({TargetId:X8})", target.Name.ToString(),
target.GameObjectId);
_targetManager.Target = target;
- _currentFight!.Module.MoveToTarget(target);
+ MoveToTarget(target);
+ }
+ }
+
+ private bool IsMovingOrShouldMove(IGameObject gameObject)
+ {
+ if (_movementController.IsPathfinding || _movementController.IsPathRunning)
+ return true;
+
+ if (DateTime.Now > _currentFight!.LastDistanceCheck.AddSeconds(10))
+ {
+ MoveToTarget(gameObject);
+ _currentFight!.LastDistanceCheck = DateTime.Now;
+ return true;
+ }
+
+ return false;
+ }
+
+ private void MoveToTarget(IGameObject gameObject)
+ {
+ var player = _clientState.LocalPlayer;
+ if (player == null)
+ return; // uh oh
+
+ float hitboxOffset = player.HitboxRadius + gameObject.HitboxRadius;
+ float actualDistance = Vector3.Distance(player.Position, gameObject.Position);
+ float maxDistance = player.ClassJob.ValueNullable?.Role is 3 or 4 ? 20f : 2.9f;
+ if (actualDistance - hitboxOffset >= maxDistance)
+ {
+ if (actualDistance - hitboxOffset <= 5)
+ {
+ _logger.LogInformation("Moving to {TargetName} ({DataId}) to attack", gameObject.Name,
+ gameObject.DataId);
+ _movementController.NavigateTo(EMovementType.Combat, null, [gameObject.Position], false, false,
+ maxDistance + hitboxOffset - 0.25f, true);
+ }
+ else
+ {
+ _logger.LogInformation("Moving to {TargetName} ({DataId}) to attack (with navmesh)", gameObject.Name,
+ gameObject.DataId);
+ _movementController.NavigateTo(EMovementType.Combat, null, gameObject.Position, false, false,
+ maxDistance + hitboxOffset - 0.25f, true);
+ }
}
}
{
public required ICombatModule Module { get; init; }
public required CombatData Data { get; init; }
+ public required DateTime LastDistanceCheck { get; set; }
}
public sealed class CombatData
{
private const string Name = "BossMod";
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<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");
_logger.LogInformation("Loading Questionable BossMod Preset: {LoadedState}", _createPreset.InvokeFunc(reader.ReadToEnd(), true));
}
_setPreset.InvokeFunc("Questionable");
- _lastDistanceCheck = DateTime.Now;
return true;
}
catch (IpcError e)
}
}
- public void MoveToTarget(IGameObject gameObject)
- {
- var player = _clientState.LocalPlayer;
- if (player == null)
- return; // uh oh
-
- float hitboxOffset = player.HitboxRadius + gameObject.HitboxRadius;
- float actualDistance = Vector3.Distance(player.Position, gameObject.Position);
- float maxDistance = player.ClassJob.ValueNullable?.Role is 3 or 4 ? 20f : 2.9f;
- if (actualDistance - hitboxOffset >= maxDistance)
- {
- if (actualDistance - hitboxOffset <= 5)
- {
- _logger.LogInformation("Moving to {TargetName} ({DataId}) to attack", gameObject.Name,
- gameObject.DataId);
- _movementController.NavigateTo(EMovementType.Combat, null, [gameObject.Position], false, false,
- maxDistance + hitboxOffset - 0.25f, true);
- }
- else
- {
- _logger.LogInformation("Moving to {TargetName} ({DataId}) to attack (with navmesh)", gameObject.Name,
- gameObject.DataId);
- _movementController.NavigateTo(EMovementType.Combat, null, gameObject.Position, false, false,
- maxDistance + hitboxOffset - 0.25f, true);
- }
- }
-
- _lastDistanceCheck = DateTime.Now;
- }
-
public void Update(IGameObject gameObject)
{
- if (_movementController.IsPathfinding || _movementController.IsPathRunning)
- return;
-
- if (DateTime.Now > _lastDistanceCheck.AddSeconds(10))
- {
- MoveToTarget(gameObject);
- _lastDistanceCheck = DateTime.Now;
- }
}
public bool CanAttack(IBattleNpc target) => true;
void Update(IGameObject nextTarget);
- void MoveToTarget(IGameObject nextTarget);
-
bool CanAttack(IBattleNpc target);
}
return false;
}
- public void MoveToTarget(IGameObject nextTarget) => _delegate!.MoveToTarget(nextTarget);
-
public bool CanAttack(IBattleNpc target) => _delegate!.CanAttack(target);
}
public const ushort MountId = 128;
private readonly EAction[] _actions = [EAction.MagitekThunder, EAction.MagitekPulse];
- private readonly MovementController _movementController;
private readonly GameFunctions _gameFunctions;
-
- public Mount128Module(MovementController movementController, GameFunctions gameFunctions)
+ public Mount128Module(GameFunctions gameFunctions)
{
- _movementController = movementController;
_gameFunctions = gameFunctions;
}
public void Update(IGameObject gameObject)
{
- if (_movementController.IsPathfinding || _movementController.IsPathRunning)
- return;
-
foreach (EAction action in _actions)
{
if (_gameFunctions.UseAction(gameObject, action, checkCanUse: false))
}
}
- public void MoveToTarget(IGameObject gameObject)
- {
- }
-
public bool CanAttack(IBattleNpc target) => target.DataId is 7504 or 7505 or 14107;
}
public const ushort MountId = 147;
private readonly EAction[] _actions = [EAction.Trample];
- private readonly MovementController _movementController;
private readonly GameFunctions _gameFunctions;
- public Mount147Module(MovementController movementController, GameFunctions gameFunctions)
+ public Mount147Module(GameFunctions gameFunctions)
{
- _movementController = movementController;
_gameFunctions = gameFunctions;
}
public void Update(IGameObject gameObject)
{
- if (_movementController.IsPathfinding || _movementController.IsPathRunning)
- return;
-
foreach (EAction action in _actions)
{
if (_gameFunctions.UseAction(gameObject, action, checkCanUse: false))
}
}
- public void MoveToTarget(IGameObject gameObject)
- {
- }
-
public bool CanAttack(IBattleNpc target) => target.DataId is 8593;
}
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, Configuration configuration)
{
_logger = logger;
- _movementController = movementController;
_clientState = clientState;
_configuration = configuration;
_test = pluginInterface.GetIpcSubscriber<string, object>("RotationSolverReborn.Test");
try
{
_changeOperationMode.InvokeAction(StateCommandType.Manual);
- _lastDistanceCheck = DateTime.Now;
return true;
}
catch (IpcError e)
public bool Stop()
{
+ if (!_changeOperationMode.HasAction)
+ return true;
+
try
{
_changeOperationMode.InvokeAction(StateCommandType.Off);
}
}
- public void MoveToTarget(IGameObject gameObject)
- {
- var player = _clientState.LocalPlayer;
- if (player == null)
- return; // uh oh
-
- float hitboxOffset = player.HitboxRadius + gameObject.HitboxRadius;
- float actualDistance = Vector3.Distance(player.Position, gameObject.Position);
- float maxDistance = player.ClassJob.ValueNullable?.Role is 3 or 4 ? 20f : 2.9f;
- if (actualDistance - hitboxOffset >= maxDistance)
- {
- if (actualDistance - hitboxOffset <= 5)
- {
- _logger.LogInformation("Moving to {TargetName} ({DataId}) to attack", gameObject.Name,
- gameObject.DataId);
- _movementController.NavigateTo(EMovementType.Combat, null, [gameObject.Position], false, false,
- maxDistance + hitboxOffset - 0.25f, true);
- }
- else
- {
- _logger.LogInformation("Moving to {TargetName} ({DataId}) to attack (with navmesh)", gameObject.Name,
- gameObject.DataId);
- _movementController.NavigateTo(EMovementType.Combat, null, gameObject.Position, false, false,
- maxDistance + hitboxOffset - 0.25f, true);
- }
- }
-
- _lastDistanceCheck = DateTime.Now;
- }
-
public void Update(IGameObject gameObject)
{
- if (_movementController.IsPathfinding || _movementController.IsPathRunning)
- return;
-
- if (DateTime.Now > _lastDistanceCheck.AddSeconds(10))
- {
- MoveToTarget(gameObject);
- _lastDistanceCheck = DateTime.Now;
- }
}
public bool CanAttack(IBattleNpc target) => true;
--- /dev/null
+using System;
+using Dalamud.Game.ClientState.Objects.Types;
+using Dalamud.Plugin;
+using Dalamud.Plugin.Ipc;
+using Dalamud.Plugin.Ipc.Exceptions;
+using Microsoft.Extensions.Logging;
+using Questionable.Controller.Steps;
+
+namespace Questionable.Controller.CombatModules;
+
+internal sealed class WrathComboModule : ICombatModule, IDisposable
+{
+ private const string CallbackPrefix = "Questionable$Wrath";
+
+ private readonly ILogger<WrathComboModule> _logger;
+ private readonly Configuration _configuration;
+ private readonly ICallGateSubscriber<object> _test;
+ private readonly ICallGateSubscriber<string, string, string, Guid?> _registerForLeaseWithCallback;
+ private readonly ICallGateSubscriber<Guid, object> _releaseControl;
+ private readonly ICallGateSubscriber<Guid,bool,object> _setAutoRotationState;
+ private readonly ICallGateSubscriber<Guid,object> _setCurrentJobAutoRotationReady;
+ private readonly ICallGateProvider<int, string, object> _callback;
+
+ private Guid? _lease;
+
+ public WrathComboModule(ILogger<WrathComboModule> logger, Configuration configuration,
+ IDalamudPluginInterface pluginInterface)
+ {
+ _logger = logger;
+ _configuration = configuration;
+ _test = pluginInterface.GetIpcSubscriber<object>("WrathCombo.Test");
+ _registerForLeaseWithCallback =
+ pluginInterface.GetIpcSubscriber<string, string, string, Guid?>("WrathCombo.RegisterForLeaseWithCallback");
+ _releaseControl = pluginInterface.GetIpcSubscriber<Guid, object>("WrathCombo.ReleaseControl");
+ _setAutoRotationState = pluginInterface.GetIpcSubscriber<Guid, bool, object>("WrathCombo.SetAutoRotationState");
+ _setCurrentJobAutoRotationReady =
+ pluginInterface.GetIpcSubscriber<Guid, object>("WrathCombo.SetCurrentJobAutoRotationReady");
+
+ _callback = pluginInterface.GetIpcProvider<int, string, object>($"{CallbackPrefix}.WrathComboCallback");
+ _callback.RegisterAction(Callback);
+ }
+
+ public bool CanHandleFight(CombatController.CombatData combatData)
+ {
+ if (_configuration.General.CombatModule != Configuration.ECombatModule.WrathCombo)
+ return false;
+
+ try
+ {
+ _test.InvokeAction();
+ return true;
+ }
+ catch (IpcError)
+ {
+ return false;
+ }
+ }
+
+ public bool Start(CombatController.CombatData combatData)
+ {
+ try
+ {
+ _lease = _registerForLeaseWithCallback.InvokeFunc("Questionable", "Questionable", CallbackPrefix);
+ if (_lease != null)
+ {
+ _logger.LogDebug("Wrath combo lease: {Lease}", _lease.Value);
+
+ _setAutoRotationState.InvokeAction(_lease.Value, true);
+ _setCurrentJobAutoRotationReady.InvokeAction(_lease.Value);
+ return true;
+ }
+ else
+ {
+ _logger.LogError("Wrath combo did not return a lease");
+ return false;
+ }
+ }
+ catch (IpcError e)
+ {
+ _logger.LogError(e, "Unable to use wrath combo for combat");
+ return false;
+ }
+ }
+
+ public bool Stop()
+ {
+ try
+ {
+ if (_lease != null)
+ {
+ _releaseControl.InvokeAction(_lease.Value);
+ _lease = null;
+ }
+
+ return true;
+ }
+ catch (IpcError e)
+ {
+ _logger.LogWarning(e, "Could not turn off wrath combo");
+ return false;
+ }
+ }
+
+ public void Update(IGameObject nextTarget)
+ {
+ if (_lease == null)
+ throw new TaskException("Wrath Combo Lease is cancelled");
+ }
+
+ public bool CanAttack(IBattleNpc target) => true;
+
+ private void Callback(int reason, string additionalInfo)
+ {
+ _logger.LogWarning("WrathCombo callback: {Reason} ({Info})", reason, additionalInfo);
+ _lease = null;
+ }
+
+ public void Dispose()
+ {
+ Stop();
+ _callback.UnregisterAction();
+ }
+}
using Questionable.Controller.Steps.Gathering;
using Questionable.Controller.Steps.Interactions;
using Questionable.Controller.Steps.Leves;
-using Questionable.Controller.Utils;
using Questionable.Data;
using Questionable.External;
using Questionable.Functions;
serviceCollection.AddSingleton<ICombatModule, Mount147Module>();
serviceCollection.AddSingleton<ICombatModule, ItemUseModule>();
serviceCollection.AddSingleton<ICombatModule, BossModModule>();
+ serviceCollection.AddSingleton<ICombatModule, WrathComboModule>();
serviceCollection.AddSingleton<ICombatModule, RotationSolverRebornModule>();
}
private readonly uint[] _mountIds;
private readonly string[] _mountNames;
- private readonly string[] _combatModuleNames = ["None", "Boss Mod (VBM)", "Rotation Solver Reborn"];
+ private readonly string[] _combatModuleNames = ["None", "Boss Mod (VBM)", "Wrath Combo", "Rotation Solver Reborn"];
private readonly string[] _grandCompanyNames =
["None (manually pick quest)", "Maelstrom", "Twin Adder", "Immortal Flames"];
new Uri("https://github.com/awgil/ffxiv_bossmod"),
new Uri("https://puni.sh/api/repository/veyn"))
},
+ {
+ Configuration.ECombatModule.WrathCombo,
+ new PluginInfo("Wrath Combo",
+ "WrathCombo",
+ string.Empty,
+ new Uri("https://github.com/PunishXIV/WrathCombo"),
+ new Uri("https://puni.sh/api/plugins"))
+ },
{
Configuration.ECombatModule.RotationSolverReborn,
new("Rotation Solver Reborn",
}
DrawCombatPlugin(Configuration.ECombatModule.BossMod, checklistPadding);
+ DrawCombatPlugin(Configuration.ECombatModule.WrathCombo, checklistPadding);
DrawCombatPlugin(Configuration.ECombatModule.RotationSolverReborn, checklistPadding);
}