Give enemies kill priorities (defined inquest → aggro'd), occasionally check if we...
authorLiza Carvelli <liza@carvel.li>
Thu, 25 Jul 2024 15:36:51 +0000 (17:36 +0200)
committerLiza Carvelli <liza@carvel.li>
Thu, 25 Jul 2024 15:36:51 +0000 (17:36 +0200)
Questionable/Controller/CombatController.cs
Questionable/Controller/CombatModules/ICombatModule.cs
Questionable/Controller/CombatModules/RotationSolverRebornModule.cs
Questionable/Controller/QuestController.cs

index d269a061f2c1529ee6566ad28e236eee7359bdc1..8f0f0de8ab0f282c1b899a5a261ed3c52aaac642 100644 (file)
@@ -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()
index 1a8d003d07b8535fc14e9353c080c6d6a4a64e32..2553310b57a94b793b29d2e63378f6e1e8221fee 100644 (file)
@@ -10,5 +10,7 @@ internal interface ICombatModule
 
     bool Stop();
 
+    void Update(IGameObject nextTarget);
+
     void SetTarget(IGameObject nextTarget);
 }
index d1a2bb92563639d85e109d31366970638de9d80a..f09ac6eee1789e7b4dccafd7178a4654d4afcb21 100644 (file)
@@ -19,6 +19,8 @@ internal sealed class RotationSolverRebornModule : ICombatModule, IDisposable
     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)
     {
@@ -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();
index 62f3a9359530f28893aeb8dcaea782cf758f5e8b..b658d006b60ba9a860e8939feb1c310eb4de8d5b 100644 (file)
@@ -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<ITaskFactory> _taskFactories;
@@ -44,6 +46,7 @@ internal sealed class QuestController
         QuestRegistry questRegistry,
         IKeyState keyState,
         IChatGui chatGui,
+        ICondition condition,
         Configuration configuration,
         YesAlreadyIpc yesAlreadyIpc,
         IEnumerable<ITaskFactory> 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)
             {