diff --git a/Questionable/Controller/CombatController.cs b/Questionable/Controller/CombatController.cs index d269a061..8f0f0de8 100644 --- a/Questionable/Controller/CombatController.cs +++ b/Questionable/Controller/CombatController.cs @@ -9,6 +9,7 @@ using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game.Object; +using FFXIVClientStructs.FFXIV.Common.Math; using Microsoft.Extensions.Logging; using Questionable.Controller.CombatModules; using Questionable.Controller.Utils; @@ -75,11 +76,15 @@ internal sealed class CombatController : IDisposable var target = _targetManager.Target; if (target != null) { - if (IsEnemyToKill(target)) + if (GetKillPriority(target) is >= 50) return true; var nextTarget = FindNextTarget(); - if (nextTarget != null) + if (nextTarget != null && nextTarget.Equals(target)) + { + _currentFight.Module.Update(target); + } + else if (nextTarget != null) { _logger.LogInformation("Changing next target to {TargetName} ({TargetId:X8})", nextTarget.Name.ToString(), nextTarget.GameObjectId); @@ -154,10 +159,15 @@ internal sealed class CombatController : IDisposable } } - return _objectTable.Where(IsEnemyToKill).MinBy(x => (x.Position - _clientState.LocalPlayer!.Position).Length()); + return _objectTable.Select(x => (GameObject: x, Priority: GetKillPriority(x))) + .Where(x => x.Priority != null) + .OrderByDescending(x => x.Priority!.Value) + .ThenByDescending(x => Vector3.Distance(x.GameObject.Position, _clientState.LocalPlayer!.Position)) + .Select(x => x.GameObject) + .FirstOrDefault(); } - private unsafe bool IsEnemyToKill(IGameObject gameObject) + private unsafe int? GetKillPriority(IGameObject gameObject) { if (gameObject is IBattleNpc battleNpc) { @@ -167,14 +177,11 @@ internal sealed class CombatController : IDisposable _currentFight.Data.ComplexCombatDatas.Count == 0) { if (battleNpc.IsDead) - return false; + return null; } if (!battleNpc.IsTargetable) - return false; - - if (battleNpc.TargetObjectId == _clientState.LocalPlayer?.GameObjectId) - return true; + return null; if (_currentFight != null) { @@ -187,33 +194,37 @@ internal sealed class CombatController : IDisposable continue; if (complexCombatData[i].DataId == battleNpc.DataId) - return true; + return 100; } } else { if (_currentFight.Data.KillEnemyDataIds.Contains(battleNpc.DataId)) - return true; + return 90; } } + // enemies that we have aggro on if (battleNpc.BattleNpcKind is BattleNpcSubKind.BattleNpcPart or BattleNpcSubKind.Enemy) { var gameObjectStruct = (GameObject*)gameObject.Address; if (gameObjectStruct->NamePlateIconId is 60093 or 60732) // npc that starts a fate or does turn-ins - return false; + return null; var enemyData = _currentFight?.Data.ComplexCombatDatas.FirstOrDefault(x => x.DataId == battleNpc.DataId); if (enemyData is { IgnoreQuestMarker: true }) - return battleNpc.StatusFlags.HasFlag(StatusFlags.InCombat); + return battleNpc.StatusFlags.HasFlag(StatusFlags.InCombat) ? 20 : null; else - return gameObjectStruct->NamePlateIconId != 0; + return gameObjectStruct->NamePlateIconId != 0 ? 30 : null; } - else - return false; + + // stuff trying to kill us + if (battleNpc.TargetObjectId == _clientState.LocalPlayer?.GameObjectId) + return 0; + } - else - return false; + + return null; } public void Stop() diff --git a/Questionable/Controller/CombatModules/ICombatModule.cs b/Questionable/Controller/CombatModules/ICombatModule.cs index 1a8d003d..2553310b 100644 --- a/Questionable/Controller/CombatModules/ICombatModule.cs +++ b/Questionable/Controller/CombatModules/ICombatModule.cs @@ -10,5 +10,7 @@ internal interface ICombatModule bool Stop(); + void Update(IGameObject nextTarget); + void SetTarget(IGameObject nextTarget); } diff --git a/Questionable/Controller/CombatModules/RotationSolverRebornModule.cs b/Questionable/Controller/CombatModules/RotationSolverRebornModule.cs index d1a2bb92..f09ac6ee 100644 --- a/Questionable/Controller/CombatModules/RotationSolverRebornModule.cs +++ b/Questionable/Controller/CombatModules/RotationSolverRebornModule.cs @@ -19,6 +19,8 @@ internal sealed class RotationSolverRebornModule : ICombatModule, IDisposable private readonly ICallGateSubscriber _test; private readonly ICallGateSubscriber _changeOperationMode; + private DateTime _lastDistanceCheck = DateTime.MinValue; + public RotationSolverRebornModule(ILogger logger, MovementController movementController, IClientState clientState, IDalamudPluginInterface pluginInterface) { @@ -51,6 +53,7 @@ internal sealed class RotationSolverRebornModule : ICombatModule, IDisposable try { _changeOperationMode.InvokeAction(StateCommandType.Manual); + _lastDistanceCheck = DateTime.Now; return true; } catch (IpcError e) @@ -82,10 +85,38 @@ internal sealed class RotationSolverRebornModule : ICombatModule, IDisposable float hitboxOffset = player.HitboxRadius + gameObject.HitboxRadius; float actualDistance = Vector3.Distance(player.Position, gameObject.Position); - float maxDistance = player.ClassJob.GameData?.Role is 3 or 4 ? 25f : 3f; - if (actualDistance - hitboxOffset > maxDistance) - _movementController.NavigateTo(EMovementType.Combat, null, [gameObject.Position], false, false, - maxDistance + hitboxOffset - 0.25f, true); + float maxDistance = player.ClassJob.GameData?.Role is 3 or 4 ? 20f : 3f; + 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)) + { + SetTarget(gameObject); + _lastDistanceCheck = DateTime.Now; + } } public void Dispose() => Stop(); diff --git a/Questionable/Controller/QuestController.cs b/Questionable/Controller/QuestController.cs index 62f3a935..b658d006 100644 --- a/Questionable/Controller/QuestController.cs +++ b/Questionable/Controller/QuestController.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.ClientState.Keys; using Dalamud.Plugin.Services; using Microsoft.Extensions.Logging; @@ -22,6 +23,7 @@ internal sealed class QuestController private readonly QuestRegistry _questRegistry; private readonly IKeyState _keyState; private readonly IChatGui _chatGui; + private readonly ICondition _condition; private readonly Configuration _configuration; private readonly YesAlreadyIpc _yesAlreadyIpc; private readonly IReadOnlyList _taskFactories; @@ -44,6 +46,7 @@ internal sealed class QuestController QuestRegistry questRegistry, IKeyState keyState, IChatGui chatGui, + ICondition condition, Configuration configuration, YesAlreadyIpc yesAlreadyIpc, IEnumerable taskFactories) @@ -56,6 +59,7 @@ internal sealed class QuestController _questRegistry = questRegistry; _keyState = keyState; _chatGui = chatGui; + _condition = condition; _configuration = configuration; _yesAlreadyIpc = yesAlreadyIpc; _taskFactories = taskFactories.ToList().AsReadOnly(); @@ -104,7 +108,15 @@ internal sealed class QuestController { UpdateCurrentQuest(); - if (_keyState[VirtualKey.ESCAPE]) + if (!_clientState.IsLoggedIn || _condition[ConditionFlag.Unconscious]) + { + if (_currentTask != null || _taskQueue.Count > 0) + { + Stop("HP = 0"); + _movementController.Stop(); + _combatController.Stop(); + } + } else if (_keyState[VirtualKey.ESCAPE]) { if (_currentTask != null || _taskQueue.Count > 0) {