From cbe5edc9574664ca12515a21513c073ef1a7d900 Mon Sep 17 00:00:00 2001 From: Liza Carvelli Date: Sat, 5 Apr 2025 12:58:44 +0200 Subject: [PATCH] Update combat priority logic --- Directory.Build.targets | 2 +- Questionable/Configuration.cs | 1 + Questionable/Controller/CombatController.cs | 95 ++++++++----------- .../ConfigComponents/DebugConfigComponent.cs | 13 +++ Questionable/Windows/DebugOverlay.cs | 19 +++- 5 files changed, 70 insertions(+), 60 deletions(-) diff --git a/Directory.Build.targets b/Directory.Build.targets index ffc04949..bbfc27ed 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -1,5 +1,5 @@ - 5.4 + 5.5 diff --git a/Questionable/Configuration.cs b/Questionable/Configuration.cs index 6dab0df1..7bf14f68 100644 --- a/Questionable/Configuration.cs +++ b/Questionable/Configuration.cs @@ -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; } diff --git a/Questionable/Controller/CombatController.cs b/Questionable/Controller/CombatController.cs index ea10fa7c..c54dce0f 100644 --- a/Questionable/Controller/CombatController.cs +++ b/Questionable/Controller/CombatController.cs @@ -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 _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) diff --git a/Questionable/Windows/ConfigComponents/DebugConfigComponent.cs b/Questionable/Windows/ConfigComponents/DebugConfigComponent.cs index cc9fe35a..b6d0d2c5 100644 --- a/Questionable/Windows/ConfigComponents/DebugConfigComponent.cs +++ b/Questionable/Windows/ConfigComponents/DebugConfigComponent.cs @@ -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)) { diff --git a/Questionable/Windows/DebugOverlay.cs b/Questionable/Windows/DebugOverlay.cs index 2f5f8916..0d69588c 100644 --- a/Questionable/Windows/DebugOverlay.cs +++ b/Questionable/Windows/DebugOverlay.cs @@ -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}"); } } -- 2.30.2