From 4d542a83909a6947c35557e8298756ec7b19f861 Mon Sep 17 00:00:00 2001 From: LTS-FFXIV <127939494+LTS-FFXIV@users.noreply.github.com> Date: Mon, 7 Oct 2024 21:21:47 -0500 Subject: [PATCH] Enhance targeting logic and update configurations Updated subproject commit in ECommons. Enhanced `FindTargetAreaFriend` in `ActionTargetInfo.cs` to handle zero-range actions, use fallback points, and apply random offsets. Updated `Configs.cs` to remove PvE filter for healers and added `_targetFatePriority` setting. Added configuration checks and exception handling in `DataCenter.cs`. Added `IsTopPriorityNamedHostile` method in `ObjectHelper.cs` and updated `IsTopPriorityHostile` method. Modified `ModifyStarryMusePvE` in `PictomancerRotation.cs` to set `TargetType` to `Self`. Removed `Obsolete` attribute from `Target` property in `CustomRotation_OtherInfo.cs`. Update subproject and enhance targeting logic Updated ECommons subproject commit to `200096e34ef20e9f8d2249086c85e72d1b54f35d`. In `ActionTargetInfo.cs`, modified `FindTargetAreaFriend` to handle zero-range actions by targeting the player and added logic for beneficial area strategies. In `Configs.cs`, removed PvE filter for healers and added "Target Fate priority" setting. In `DataCenter.cs`, added config checks for friendly NPCs, improved error handling, initialized `_lastHp` with a dictionary constructor, simplified HP calculations, and reduced action record queue capacity. In `ObjectHelper.cs`, split `IsTopPriorityHostile` into `IsTopPriorityNamedHostile` and `IsTopPriorityHostile`, adding checks for status effects, attack marks, and fate priorities. In `PictomancerRotation.cs`, updated `ModifyStarryMusePvE` to target `Self`. In `CustomRotation_OtherInfo.cs`, marked `Target` property as obsolete with a warning. --- ECommons | 2 +- .../Actions/ActionTargetInfo.cs | 70 +++++++++++-------- RotationSolver.Basic/Configuration/Configs.cs | 7 +- RotationSolver.Basic/DataCenter.cs | 41 +++++++---- RotationSolver.Basic/Helpers/ObjectHelper.cs | 33 +++++++-- .../Rotations/Basic/PictomancerRotation.cs | 1 + .../Rotations/CustomRotation_OtherInfo.cs | 1 - 7 files changed, 104 insertions(+), 51 deletions(-) diff --git a/ECommons b/ECommons index b39bbc68f..200096e34 160000 --- a/ECommons +++ b/ECommons @@ -1 +1 @@ -Subproject commit b39bbc68ff7ee632d3121388e6f4e4aad6753dcd +Subproject commit 200096e34ef20e9f8d2249086c85e72d1b54f35d diff --git a/RotationSolver.Basic/Actions/ActionTargetInfo.cs b/RotationSolver.Basic/Actions/ActionTargetInfo.cs index f4c187cbd..2052df350 100644 --- a/RotationSolver.Basic/Actions/ActionTargetInfo.cs +++ b/RotationSolver.Basic/Actions/ActionTargetInfo.cs @@ -450,6 +450,12 @@ private bool CheckTimeToKill(IGameObject gameObject) { if (canAffects == null || player == null) return null; + // Check if the action's range is zero and handle it as targeting self + if (range == 0) + { + return new TargetResult(player, GetAffects(player.Position, canAffects).ToArray(), player.Position); + } + var strategy = Service.Config.BeneficialAreaStrategy; switch (strategy) { @@ -458,6 +464,7 @@ private bool CheckTimeToKill(IGameObject gameObject) OtherConfiguration.BeneficialPositions.TryGetValue(Svc.ClientState.TerritoryType, out var pts); pts ??= Array.Empty(); + // Use fallback points if no beneficial positions are found if (pts.Length == 0) { if (DataCenter.TerritoryContentType == TerritoryContentType.Trials || @@ -470,6 +477,7 @@ private bool CheckTimeToKill(IGameObject gameObject) } } + // Find the closest point and apply a random offset if (pts.Length > 0) { var closest = pts.MinBy(p => Vector3.Distance(player.Position, p)); @@ -478,56 +486,62 @@ private bool CheckTimeToKill(IGameObject gameObject) var radius = random.NextDouble(); closest.X += (float)(Math.Sin(rotation) * radius); closest.Z += (float)(Math.Cos(rotation) * radius); + + // Check if the closest point is within the effect range if (Vector3.Distance(player.Position, closest) < player.HitboxRadius + EffectRange) { return new TargetResult(player, GetAffects(closest, canAffects).ToArray(), closest); } } + // Return null if strategy is OnlyOnLocations and no valid point is found if (strategy == BeneficialAreaStrategy.OnlyOnLocations) return null; break; case BeneficialAreaStrategy.OnTarget: // Target - if (Svc.Targets.Target != null && range != 0 && Svc.Targets.Target.DistanceToPlayer() < range) + if (Svc.Targets.Target != null && Svc.Targets.Target.DistanceToPlayer() < range) { var target = Svc.Targets.Target as IBattleChara; return new TargetResult(target, GetAffects(target?.Position, canAffects).ToArray(), target?.Position); } break; - } - - if (Svc.Targets.Target is IBattleChara b && range != 0 && b.DistanceToPlayer() < range && - b.IsBossFromIcon() && b.HasPositional() && b.HitboxRadius <= 8) - { - return new TargetResult(b, GetAffects(b.Position, canAffects).ToArray(), b.Position); - } - else - { - var effectRange = EffectRange; - var attackT = FindTargetByType(DataCenter.AllianceMembers.GetObjectInRadius(range + effectRange), - TargetType.BeAttacked, action.Config.AutoHealRatio, action.Setting.SpecialType); - if (attackT == null) - { - return new TargetResult(player, GetAffects(player.Position, canAffects).ToArray(), player.Position); - } - else - { - var disToTankRound = Vector3.Distance(player.Position, attackT.Position) + attackT.HitboxRadius; - - if (disToTankRound < effectRange - || disToTankRound > 2 * effectRange - player.HitboxRadius) + case BeneficialAreaStrategy.OnCalculated: // OnCalculated + if (Svc.Targets.Target is IBattleChara b && b.DistanceToPlayer() < range && + b.IsBossFromIcon() && b.HasPositional() && b.HitboxRadius <= 8) { - return new TargetResult(player, GetAffects(player.Position, canAffects).ToArray(), player.Position); + return new TargetResult(b, GetAffects(b.Position, canAffects).ToArray(), b.Position); } else { - Vector3 directionToTank = attackT.Position - player.Position; - var moveDirection = directionToTank / directionToTank.Length() * Math.Max(0, disToTankRound - effectRange); - return new TargetResult(player, GetAffects(player.Position, canAffects).ToArray(), player.Position + moveDirection); + var effectRange = EffectRange; + var attackT = FindTargetByType(DataCenter.AllianceMembers.GetObjectInRadius(range + effectRange), + TargetType.BeAttacked, action.Config.AutoHealRatio, action.Setting.SpecialType); + + if (attackT == null) + { + return new TargetResult(player, GetAffects(player.Position, canAffects).ToArray(), player.Position); + } + else + { + var disToTankRound = Vector3.Distance(player.Position, attackT.Position) + attackT.HitboxRadius; + + if (disToTankRound < effectRange + || disToTankRound > 2 * effectRange - player.HitboxRadius) + { + return new TargetResult(player, GetAffects(player.Position, canAffects).ToArray(), player.Position); + } + else + { + Vector3 directionToTank = attackT.Position - player.Position; + var moveDirection = directionToTank / directionToTank.Length() * Math.Max(0, disToTankRound - effectRange); + return new TargetResult(player, GetAffects(player.Position, canAffects).ToArray(), player.Position + moveDirection); + } + } } - } } + + return null; } diff --git a/RotationSolver.Basic/Configuration/Configs.cs b/RotationSolver.Basic/Configuration/Configs.cs index cc9f5a05a..31d899cf6 100644 --- a/RotationSolver.Basic/Configuration/Configs.cs +++ b/RotationSolver.Basic/Configuration/Configs.cs @@ -310,8 +310,7 @@ public const string [ConditionBool, UI("Use movement speed increase abilities when out of combat.", Parent = nameof(UseAbility))] private static readonly bool _autoSpeedOutOfCombat = true; - [ConditionBool, UI("Use beneficial ground-targeted actions", Parent = nameof(UseAbility), - PvEFilter = JobFilterType.Healer)] + [ConditionBool, UI("Use beneficial ground-targeted actions", Parent = nameof(UseAbility))] private static readonly bool _useGroundBeneficialAbility = true; [ConditionBool, UI("Use beneficial AoE actions when moving.", Parent = nameof(UseGroundBeneficialAbility))] @@ -323,6 +322,10 @@ public const string [ConditionBool, UI("Record AOE actions", Filter = List)] private static readonly bool _recordCastingArea = true; + [ConditionBool, UI("Target Fate priority", + Filter = TargetConfig, Section = 1)] + private static readonly bool _targetFatePriority = true; + [ConditionBool, UI("Auto turn off RSR when combat is over more for more then...", Filter = BasicAutoSwitch)] private static readonly bool _autoOffAfterCombat = true; diff --git a/RotationSolver.Basic/DataCenter.cs b/RotationSolver.Basic/DataCenter.cs index 3388ee100..4ecf15c48 100644 --- a/RotationSolver.Basic/DataCenter.cs +++ b/RotationSolver.Basic/DataCenter.cs @@ -318,6 +318,12 @@ public unsafe static IBattleChara[] FriendlyNPCMembers { get { + // Check if the configuration setting is true + if (!Service.Config.FriendlyBattleNpcHeal && !Service.Config.FriendlyPartyNpcHealRaise) + { + return Array.Empty(); + } + try { // Ensure Svc.Objects is not null @@ -329,7 +335,20 @@ public unsafe static IBattleChara[] FriendlyNPCMembers // Filter and cast objects safely var friendlyNpcs = Svc.Objects .Where(obj => obj != null && obj.ObjectKind == ObjectKind.BattleNpc) - .Where(obj => obj.GetNameplateKind() == NameplateKind.FriendlyBattleNPC || obj.GetBattleNPCSubKind() == BattleNpcSubKind.NpcPartyMember) + .Where(obj => + { + try + { + return obj.GetNameplateKind() == NameplateKind.FriendlyBattleNPC || + obj.GetBattleNPCSubKind() == BattleNpcSubKind.NpcPartyMember; + } + catch (Exception ex) + { + // Log the exception for debugging purposes + Svc.Log.Error($"Error filtering object in get_FriendlyNPCMembers: {ex.Message}"); + return false; + } + }) .OfType() .ToArray(); @@ -589,9 +608,9 @@ public static unsafe bool HasCompanion #region HP public static Dictionary RefinedHP => PartyMembers - .ToDictionary(p => p.GameObjectId, GetPartyMemberHPRatio); + .ToDictionary(p => p.GameObjectId, GetPartyMemberHPRatio); - private static Dictionary _lastHp = []; + private static Dictionary _lastHp = new Dictionary(); private static float GetPartyMemberHPRatio(IBattleChara member) { @@ -605,10 +624,7 @@ private static float GetPartyMemberHPRatio(IBattleChara member) var currentHp = member.CurrentHp; if (currentHp > 0) { - if (!_lastHp.TryGetValue(member.GameObjectId, out var lastHp)) - { - lastHp = currentHp; - } + _lastHp.TryGetValue(member.GameObjectId, out var lastHp); if (currentHp - lastHp == healedHp) { @@ -629,7 +645,7 @@ public static float PartyMembersMinHP get { var partyMembersHP = PartyMembersHP.ToList(); - return partyMembersHP.Any() ? partyMembersHP.Min() : 0; + return partyMembersHP.Count > 0 ? partyMembersHP.Min() : 0; } } @@ -638,7 +654,7 @@ public static float PartyMembersAverHP get { var partyMembersHP = PartyMembersHP.ToList(); - return partyMembersHP.Any() ? partyMembersHP.Average() : 0; + return partyMembersHP.Count > 0 ? partyMembersHP.Average() : 0; } } @@ -647,10 +663,11 @@ public static float PartyMembersDifferHP get { var partyMembersHP = PartyMembersHP.ToList(); - if (!partyMembersHP.Any()) return 0; + if (partyMembersHP.Count == 0) return 0; var averageHP = partyMembersHP.Average(); - return (float)Math.Sqrt(partyMembersHP.Average(d => Math.Pow(d - averageHP, 2))); + var variance = partyMembersHP.Average(d => (d - averageHP) * (d - averageHP)); + return (float)Math.Sqrt(variance); } } @@ -664,7 +681,7 @@ public static float PartyMembersDifferHP #region Action Record public const float MinAnimationLock = 0.6f; - const int QUEUECAPACITY = 32; + const int QUEUECAPACITY = 16; private static readonly Queue _actions = new(QUEUECAPACITY); private static readonly Queue _damages = new(QUEUECAPACITY); diff --git a/RotationSolver.Basic/Helpers/ObjectHelper.cs b/RotationSolver.Basic/Helpers/ObjectHelper.cs index 030af8d23..5fc3ebb64 100644 --- a/RotationSolver.Basic/Helpers/ObjectHelper.cs +++ b/RotationSolver.Basic/Helpers/ObjectHelper.cs @@ -100,6 +100,8 @@ internal static bool IsAttackable(this IBattleChara battleChara) if (Service.Config.TargetQuestThings && battleChara.IsOthersPlayers()) return false; + if (battleChara.IsTopPriorityNamedHostile()) return true; + if (battleChara.IsTopPriorityHostile()) return true; if (Service.CountDownTime > 0 || DataCenter.IsPvP) return true; @@ -278,18 +280,16 @@ internal static bool IsAlive(this IGameObject obj) public static unsafe ObjectKind GetObjectKind(this IGameObject obj) => (ObjectKind)obj.Struct()->ObjectKind; /// - /// Determines whether the specified game object is a top priority hostile target. + /// Determines whether the specified game object is a top priority hostile target based on its name being listed. /// /// The game object to check. /// - /// true if the game object is a top priority hostile target; otherwise, false. + /// true if the game object is a top priority named hostile, target; otherwise, false. /// - internal static bool IsTopPriorityHostile(this IGameObject obj) + internal static bool IsTopPriorityNamedHostile(this IGameObject obj) { if (obj == null) return false; - var fateId = DataCenter.FateId; - // Fetch prioritized target names if (OtherConfiguration.PrioTargetNames.TryGetValue(Svc.ClientState.TerritoryType, out var prioTargetNames)) { @@ -300,10 +300,31 @@ internal static bool IsTopPriorityHostile(this IGameObject obj) } } + if (obj is IBattleChara npc && DataCenter.PrioritizedNameIds.Contains(npc.NameId)) return true; + + return false; + } + + /// + /// Determines whether the specified game object is a top priority hostile target. + /// + /// The game object to check. + /// + /// true if the game object is a top priority hostile target; otherwise, false. + /// + internal static bool IsTopPriorityHostile(this IGameObject obj) + { + if (obj == null) return false; + + var fateId = DataCenter.FateId; + if (obj is IBattleChara b && b.StatusList?.Any(StatusHelper.IsPriority) == true) return true; if (Service.Config.ChooseAttackMark && MarkingHelper.AttackSignTargets.FirstOrDefault(id => id != 0) == (long)obj.GameObjectId) return true; + // Fate + if (Service.Config.TargetFatePriority && fateId != 0 && obj.FateId() == fateId) return true; + var icon = obj.GetNamePlateIcon(); // Hunting log and weapon @@ -319,8 +340,6 @@ internal static bool IsTopPriorityHostile(this IGameObject obj) //71224 Other Quest //71344 Major Quest - if (obj is IBattleChara npc && DataCenter.PrioritizedNameIds.Contains(npc.NameId)) return true; - // Check if the object is a BattleNpcPart if (Service.Config.PrioEnemyParts && obj.GetBattleNPCSubKind() == BattleNpcSubKind.BattleNpcPart) return true; diff --git a/RotationSolver.Basic/Rotations/Basic/PictomancerRotation.cs b/RotationSolver.Basic/Rotations/Basic/PictomancerRotation.cs index 188bf750c..3631109be 100644 --- a/RotationSolver.Basic/Rotations/Basic/PictomancerRotation.cs +++ b/RotationSolver.Basic/Rotations/Basic/PictomancerRotation.cs @@ -429,6 +429,7 @@ static partial void ModifyStarrySkyMotifPvE(ref ActionSetting setting) static partial void ModifyStarryMusePvE(ref ActionSetting setting) { setting.ActionCheck = () => isStarryMuseReady && InCombat; + setting.TargetType = TargetType.Self; setting.StatusProvide = [StatusID.Starstruck, StatusID.SubtractiveSpectrum, StatusID.Inspiration, StatusID.Hyperphantasia, StatusID.RainbowBright]; setting.CreateConfig = () => new ActionConfig() { diff --git a/RotationSolver.Basic/Rotations/CustomRotation_OtherInfo.cs b/RotationSolver.Basic/Rotations/CustomRotation_OtherInfo.cs index b74138379..9906ae4a4 100644 --- a/RotationSolver.Basic/Rotations/CustomRotation_OtherInfo.cs +++ b/RotationSolver.Basic/Rotations/CustomRotation_OtherInfo.cs @@ -103,7 +103,6 @@ partial class CustomRotation /// The player's target. ///
WARNING: Do not use if there is more than one target, this is not the actions target, it is the players current hard target. Try to use or instead after using this.
/// - [Obsolete("You'd better not use it. More information in summary.")] protected static IBattleChara Target => Svc.Targets.Target is IBattleChara b ? b : Player; ///