Update combat priority logic v5.5
authorLiza Carvelli <liza@carvel.li>
Sat, 5 Apr 2025 10:58:44 +0000 (12:58 +0200)
committerLiza Carvelli <liza@carvel.li>
Sat, 5 Apr 2025 10:58:44 +0000 (12:58 +0200)
Directory.Build.targets
Questionable/Configuration.cs
Questionable/Controller/CombatController.cs
Questionable/Windows/ConfigComponents/DebugConfigComponent.cs
Questionable/Windows/DebugOverlay.cs

index ffc0494..bbfc27e 100644 (file)
@@ -1,5 +1,5 @@
 <Project>
     <PropertyGroup Condition="$(MSBuildProjectName) != 'GatheringPathRenderer'">
-        <Version>5.4</Version>
+        <Version>5.5</Version>
     </PropertyGroup>
 </Project>
index 6dab0df..7bf14f6 100644 (file)
@@ -71,6 +71,7 @@ internal sealed class Configuration : IPluginConfiguration
     internal sealed class AdvancedConfiguration
     {
         public bool DebugOverlay { get; set; }
+        public bool CombatDataOverlay { get; set; }
         public bool NeverFly { get; set; }
         public bool AdditionalStatusInformation { get; set; }
         public bool DisableAutoDutyBareMode { get; set; }
index ea10fa7..c54dce0 100644 (file)
@@ -26,7 +26,6 @@ namespace Questionable.Controller;
 internal sealed class CombatController : IDisposable
 {
     private const float MaxTargetRange = 55f;
-    private const float MaxNameplateRange = 50f;
 
     private readonly List<ICombatModule> _combatModules;
     private readonly MovementController _movementController;
@@ -155,9 +154,9 @@ internal sealed class CombatController : IDisposable
         var target = _targetManager.Target;
         if (target != null)
         {
-            int currentTargetPriority = GetKillPriority(target);
+            int currentTargetPriority = GetKillPriority(target).Priority;
             var nextTarget = FindNextTarget();
-            int nextTargetPriority = nextTarget != null ? GetKillPriority(nextTarget) : 0;
+            int nextTargetPriority = nextTarget != null ? GetKillPriority(nextTarget).Priority : 0;
 
             if (nextTarget != null && nextTarget.Equals(target))
             {
@@ -251,7 +250,7 @@ internal sealed class CombatController : IDisposable
         return _objectTable.Select(x => new
             {
                 GameObject = x,
-                Priority = GetKillPriority(x),
+                GetKillPriority(x).Priority,
                 Distance = Vector3.Distance(x.Position, _clientState.LocalPlayer!.Position),
             })
             .Where(x => x.Priority > 0)
@@ -261,24 +260,50 @@ internal sealed class CombatController : IDisposable
             .FirstOrDefault();
     }
 
-    public unsafe int GetKillPriority(IGameObject gameObject)
+    public unsafe (int Priority, string Reason) GetKillPriority(IGameObject gameObject)
+    {
+        (int? rawPriority, string reason) = GetRawKillPriority(gameObject);
+        if (rawPriority == null)
+            return (0, reason);
+
+        // priority is a value between 0 and 100 inclusive; we want to always kill enemies we have fight with on first
+        if (gameObject is IBattleNpc battleNpc && battleNpc.StatusFlags.HasFlag(StatusFlags.InCombat))
+        {
+            // stuff trying to kill us
+            if (gameObject.TargetObjectId == _clientState.LocalPlayer?.GameObjectId)
+                return (rawPriority.Value + 150, reason + "/Targeted");
+
+            // stuff on our enmity list that's not necessarily targeting us
+            var haters = UIState.Instance()->Hater;
+            for (int i = 0; i < haters.HaterCount; ++i)
+            {
+                var hater = haters.Haters[i];
+                if (hater.EntityId == gameObject.GameObjectId)
+                    return (rawPriority.Value + 125, reason + "/Enmity");
+            }
+        }
+
+        return (rawPriority.Value, reason);
+    }
+
+    private unsafe (int? Priority, string Reason) GetRawKillPriority(IGameObject gameObject)
     {
         if (_currentFight == null)
-            return 0;
+            return (null, "Not Fighting");
 
         if (gameObject is IBattleNpc battleNpc)
         {
             if (!_currentFight.Module.CanAttack(battleNpc))
-                return 0;
+                return (null, "Can't attack");
 
             if (battleNpc.IsDead)
-                return 0;
+                return (null, "Dead");
 
             if (!battleNpc.IsTargetable)
-                return 0;
+                return (null, "Untargetable");
 
             var complexCombatData = _currentFight.Data.ComplexCombatDatas;
-            if (complexCombatData.Count >= 0)
+            if (complexCombatData.Count > 0)
             {
                 for (int i = 0; i < complexCombatData.Count; ++i)
                 {
@@ -287,13 +312,13 @@ internal sealed class CombatController : IDisposable
 
                     if (complexCombatData[i].DataId == battleNpc.DataId &&
                         (complexCombatData[i].NameId == null || complexCombatData[i].NameId == battleNpc.NameId))
-                        return 100;
+                        return (100, "CCD");
                 }
             }
             else
             {
                 if (_currentFight.Data.KillEnemyDataIds.Contains(battleNpc.DataId))
-                    return 90;
+                    return (90, "KED");
             }
 
             // enemies that we have aggro on
@@ -303,53 +328,15 @@ internal sealed class CombatController : IDisposable
 
                 // npc that starts a fate or does turn-ins; not sure why they're marked as hostile
                 if (gameObjectStruct->NamePlateIconId is 60093 or 60732)
-                    return 0;
+                    return (null, "FATE NPC");
 
-                var enemyData = _currentFight.Data.ComplexCombatDatas
-                    .FirstOrDefault(x => x.DataId == battleNpc.DataId &&
-                                         (x.NameId == null || x.NameId == battleNpc.NameId));
-                if (enemyData is { IgnoreQuestMarker: true })
-                {
-                    if (battleNpc.StatusFlags.HasFlag(StatusFlags.InCombat))
-                        return 20;
-                }
-                else if (enemyData != null)
-                {
-                    if (gameObjectStruct->NamePlateIconId != 0)
-                        return 30;
-
-                    // for enemies that are very far away, their nameplate doesn't render but they're in the object table
-                    if (_currentFight?.Data.SpawnType == EEnemySpawnType.OverworldEnemies &&
-                        Vector3.Distance(_clientState.LocalPlayer?.Position ?? Vector3.Zero, battleNpc.Position) >
-                        MaxNameplateRange)
-                        return 25;
-                }
-                else
-                {
-                    // as part of KillEnemyDataIds, not ComplexCombatData
-                    // TODO maybe remove KillEnemyDataIds, rename ComplexCombatData to CombatData
-                    if (gameObjectStruct->NamePlateIconId != 0)
-                        return 29;
-                }
-            }
-
-            // stuff trying to kill us
-            if (battleNpc.TargetObjectId == _clientState.LocalPlayer?.GameObjectId)
-                return 10;
-
-            // stuff on our enmity list that's not necessarily targeting us
-            var haters = UIState.Instance()->Hater;
-            for (int i = 0; i < haters.HaterCount; ++i)
-            {
-                var hater = haters.Haters[i];
-                if (hater.EntityId == battleNpc.GameObjectId)
-                    return 5;
+                return (0, "Not part of quest");
             }
 
-            return 0;
+            return (null, "Wrong BattleNpcKind");
         }
         else
-            return 0;
+            return (null, "Not BattleNpc");
     }
 
     private void SetTarget(IGameObject? target)
index cc9fe35..b6d0d2c 100644 (file)
@@ -32,6 +32,19 @@ internal sealed class DebugConfigComponent : ConfigComponent
             Save();
         }
 
+        using (ImRaii.Disabled(!debugOverlay))
+        {
+            using (ImRaii.PushIndent())
+            {
+                bool combatDataOverlay = Configuration.Advanced.CombatDataOverlay;
+                if (ImGui.Checkbox("Enable combat data overlay", ref combatDataOverlay))
+                {
+                    Configuration.Advanced.CombatDataOverlay = combatDataOverlay;
+                    Save();
+                }
+            }
+        }
+
         bool neverFly = Configuration.Advanced.NeverFly;
         if (ImGui.Checkbox("Disable flying (even if unlocked for the zone)", ref neverFly))
         {
index 2f5f891..0d69588 100644 (file)
@@ -1,8 +1,10 @@
 using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 using System.Globalization;
+using System.Linq;
 using System.Numerics;
 using Dalamud.Game.ClientState.Conditions;
+using Dalamud.Game.ClientState.Objects.Types;
 using Dalamud.Interface.Windowing;
 using Dalamud.Plugin.Services;
 using ImGuiNET;
@@ -69,7 +71,9 @@ internal sealed class DebugOverlay : Window
 
         DrawCurrentQuest();
         DrawHighlightedQuest();
-        DrawCombatTargets();
+
+        if (_configuration.Advanced.CombatDataOverlay)
+            DrawCombatTargets();
     }
 
     private void DrawCurrentQuest()
@@ -128,18 +132,23 @@ internal sealed class DebugOverlay : Window
             $"{counter}: {step.InteractionType}\n{position.ToString("G", CultureInfo.InvariantCulture)} [{(position - _clientState.LocalPlayer!.Position).Length():N2}]\n{step.Comment}");
     }
 
-    [Conditional("false")]
     private void DrawCombatTargets()
     {
-        foreach (var x in _objectTable)
+        if (!_combatController.IsRunning)
+            return;
+
+        foreach (var x in _objectTable.Skip(1))
         {
+            if (x is not IBattleNpc)
+                continue;
+
             bool visible = _gameGui.WorldToScreen(x.Position, out Vector2 screenPos);
             if (!visible)
                 continue;
 
-            int priority = _combatController.GetKillPriority(x);
+            var (priority, reason) = _combatController.GetKillPriority(x);
             ImGui.GetWindowDrawList().AddText(screenPos + new Vector2(10, -8), priority > 0 ? 0xFF00FF00 : 0xFFFFFFFF,
-                $"{x.Name}/{x.GameObjectId:X}, {x.DataId}, {priority}, {Vector3.Distance(x.Position, _clientState.LocalPlayer!.Position):N2}, {x.IsTargetable}");
+                $"{x.Name}/{x.GameObjectId:X}, {x.DataId}, {priority} - {reason}, {Vector3.Distance(x.Position, _clientState.LocalPlayer!.Position):N2}, {x.IsTargetable}");
         }
     }