Add Wrath Combo as combat module

This commit is contained in:
Liza 2024-12-27 17:48:14 +01:00
parent aa70f4c55a
commit 1578c5d197
Signed by: liza
GPG Key ID: 2C41B84815CF6445
12 changed files with 203 additions and 121 deletions

View File

@ -7,7 +7,7 @@ namespace Questionable;
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; }
@ -23,7 +23,7 @@ internal sealed class Configuration : IPluginConfiguration
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;
@ -51,6 +51,7 @@ internal sealed class Configuration : IPluginConfiguration
{
None,
BossMod,
WrathCombo,
RotationSolverReborn,
}
}

View File

@ -13,8 +13,10 @@ using FFXIVClientStructs.FFXIV.Client.Game.UI;
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;
@ -75,6 +77,7 @@ internal sealed class CombatController : IDisposable
{
Module = combatModule,
Data = combatData,
LastDistanceCheck = DateTime.Now,
};
_wasInCombat = combatData.SpawnType is EEnemySpawnType.QuestInterruption or EEnemySpawnType.FinishCombatIfAny;
return true;
@ -129,7 +132,18 @@ internal sealed class CombatController : IDisposable
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)
{
@ -323,14 +337,57 @@ internal sealed class CombatController : IDisposable
{
_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);
}
}
}
@ -359,6 +416,7 @@ internal sealed class CombatController : IDisposable
{
public required ICombatModule Module { get; init; }
public required CombatData Data { get; init; }
public required DateTime LastDistanceCheck { get; set; }
}
public sealed class CombatData

View File

@ -16,8 +16,6 @@ internal sealed class BossModModule : ICombatModule, IDisposable
{
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;
@ -25,18 +23,13 @@ internal sealed class BossModModule : ICombatModule, IDisposable
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");
@ -70,7 +63,6 @@ internal sealed class BossModModule : ICombatModule, IDisposable
_logger.LogInformation("Loading Questionable BossMod Preset: {LoadedState}", _createPreset.InvokeFunc(reader.ReadToEnd(), true));
}
_setPreset.InvokeFunc("Questionable");
_lastDistanceCheck = DateTime.Now;
return true;
}
catch (IpcError e)
@ -94,46 +86,8 @@ internal sealed class BossModModule : ICombatModule, IDisposable
}
}
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;

View File

@ -12,7 +12,5 @@ internal interface ICombatModule
void Update(IGameObject nextTarget);
void MoveToTarget(IGameObject nextTarget);
bool CanAttack(IBattleNpc target);
}

View File

@ -152,7 +152,5 @@ internal sealed class ItemUseModule : ICombatModule
return false;
}
public void MoveToTarget(IGameObject nextTarget) => _delegate!.MoveToTarget(nextTarget);
public bool CanAttack(IBattleNpc target) => _delegate!.CanAttack(target);
}

View File

@ -15,13 +15,10 @@ internal sealed class Mount128Module : ICombatModule
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;
}
@ -33,9 +30,6 @@ internal sealed class Mount128Module : ICombatModule
public void Update(IGameObject gameObject)
{
if (_movementController.IsPathfinding || _movementController.IsPathRunning)
return;
foreach (EAction action in _actions)
{
if (_gameFunctions.UseAction(gameObject, action, checkCanUse: false))
@ -43,9 +37,5 @@ internal sealed class Mount128Module : ICombatModule
}
}
public void MoveToTarget(IGameObject gameObject)
{
}
public bool CanAttack(IBattleNpc target) => target.DataId is 7504 or 7505 or 14107;
}

View File

@ -15,13 +15,11 @@ internal sealed class Mount147Module : ICombatModule
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;
}
@ -33,9 +31,6 @@ internal sealed class Mount147Module : ICombatModule
public void Update(IGameObject gameObject)
{
if (_movementController.IsPathfinding || _movementController.IsPathRunning)
return;
foreach (EAction action in _actions)
{
if (_gameFunctions.UseAction(gameObject, action, checkCanUse: false))
@ -43,9 +38,5 @@ internal sealed class Mount147Module : ICombatModule
}
}
public void MoveToTarget(IGameObject gameObject)
{
}
public bool CanAttack(IBattleNpc target) => target.DataId is 8593;
}

View File

@ -14,19 +14,15 @@ namespace Questionable.Controller.CombatModules;
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");
@ -55,7 +51,6 @@ internal sealed class RotationSolverRebornModule : ICombatModule, IDisposable
try
{
_changeOperationMode.InvokeAction(StateCommandType.Manual);
_lastDistanceCheck = DateTime.Now;
return true;
}
catch (IpcError e)
@ -67,6 +62,9 @@ internal sealed class RotationSolverRebornModule : ICombatModule, IDisposable
public bool Stop()
{
if (!_changeOperationMode.HasAction)
return true;
try
{
_changeOperationMode.InvokeAction(StateCommandType.Off);
@ -79,46 +77,8 @@ internal sealed class RotationSolverRebornModule : ICombatModule, IDisposable
}
}
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;

View File

@ -0,0 +1,123 @@
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();
}
}

View File

@ -19,7 +19,6 @@ using Questionable.Controller.Steps.Common;
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;
@ -249,6 +248,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
serviceCollection.AddSingleton<ICombatModule, Mount147Module>();
serviceCollection.AddSingleton<ICombatModule, ItemUseModule>();
serviceCollection.AddSingleton<ICombatModule, BossModModule>();
serviceCollection.AddSingleton<ICombatModule, WrathComboModule>();
serviceCollection.AddSingleton<ICombatModule, RotationSolverRebornModule>();
}

View File

@ -29,7 +29,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[] _combatModuleNames = ["None", "Boss Mod (VBM)", "Wrath Combo", "Rotation Solver Reborn"];
private readonly string[] _grandCompanyNames =
["None (manually pick quest)", "Maelstrom", "Twin Adder", "Immortal Flames"];

View File

@ -54,6 +54,14 @@ internal sealed class OneTimeSetupWindow : LWindow
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",
@ -143,6 +151,7 @@ internal sealed class OneTimeSetupWindow : LWindow
}
DrawCombatPlugin(Configuration.ECombatModule.BossMod, checklistPadding);
DrawCombatPlugin(Configuration.ECombatModule.WrathCombo, checklistPadding);
DrawCombatPlugin(Configuration.ECombatModule.RotationSolverReborn, checklistPadding);
}