Skip to content

Commit

Permalink
Merge pull request #429 from FFXIV-CombatReborn/PCTstarryfix
Browse files Browse the repository at this point in the history
Enhance targeting logic and update configurations
  • Loading branch information
LTS-FFXIV authored Oct 8, 2024
2 parents 0eed881 + 4d542a8 commit 71f1dea
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 51 deletions.
70 changes: 42 additions & 28 deletions RotationSolver.Basic/Actions/ActionTargetInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -458,6 +464,7 @@ private bool CheckTimeToKill(IGameObject gameObject)
OtherConfiguration.BeneficialPositions.TryGetValue(Svc.ClientState.TerritoryType, out var pts);
pts ??= Array.Empty<Vector3>();

// Use fallback points if no beneficial positions are found
if (pts.Length == 0)
{
if (DataCenter.TerritoryContentType == TerritoryContentType.Trials ||
Expand All @@ -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));
Expand All @@ -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;
}


Expand Down
7 changes: 5 additions & 2 deletions RotationSolver.Basic/Configuration/Configs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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))]
Expand All @@ -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;
Expand Down
41 changes: 29 additions & 12 deletions RotationSolver.Basic/DataCenter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<IBattleChara>();
}

try
{
// Ensure Svc.Objects is not null
Expand All @@ -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<IBattleChara>()
.ToArray();

Expand Down Expand Up @@ -589,9 +608,9 @@ public static unsafe bool HasCompanion
#region HP

public static Dictionary<ulong, float> RefinedHP => PartyMembers
.ToDictionary(p => p.GameObjectId, GetPartyMemberHPRatio);
.ToDictionary(p => p.GameObjectId, GetPartyMemberHPRatio);

private static Dictionary<ulong, uint> _lastHp = [];
private static Dictionary<ulong, uint> _lastHp = new Dictionary<ulong, uint>();

private static float GetPartyMemberHPRatio(IBattleChara member)
{
Expand All @@ -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)
{
Expand All @@ -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;
}
}

Expand All @@ -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;
}
}

Expand All @@ -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);
}
}

Expand All @@ -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<ActionRec> _actions = new(QUEUECAPACITY);
private static readonly Queue<DamageRec> _damages = new(QUEUECAPACITY);

Expand Down
33 changes: 26 additions & 7 deletions RotationSolver.Basic/Helpers/ObjectHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -278,18 +280,16 @@ internal static bool IsAlive(this IGameObject obj)
public static unsafe ObjectKind GetObjectKind(this IGameObject obj) => (ObjectKind)obj.Struct()->ObjectKind;

/// <summary>
/// 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.
/// </summary>
/// <param name="obj">The game object to check.</param>
/// <returns>
/// <c>true</c> if the game object is a top priority hostile target; otherwise, <c>false</c>.
/// <c>true</c> if the game object is a top priority named hostile, target; otherwise, <c>false</c>.
/// </returns>
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))
{
Expand All @@ -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;
}

/// <summary>
/// Determines whether the specified game object is a top priority hostile target.
/// </summary>
/// <param name="obj">The game object to check.</param>
/// <returns>
/// <c>true</c> if the game object is a top priority hostile target; otherwise, <c>false</c>.
/// </returns>
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
Expand All @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down
1 change: 0 additions & 1 deletion RotationSolver.Basic/Rotations/CustomRotation_OtherInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,6 @@ partial class CustomRotation
/// The player's target.
/// <br> 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 <see cref="IBaseAction.Target"/> or <seealso cref="HostileTarget"/> instead after using this.</br>
/// </summary>
[Obsolete("You'd better not use it. More information in summary.")]
protected static IBattleChara Target => Svc.Targets.Target is IBattleChara b ? b : Player;

/// <summary>
Expand Down

0 comments on commit 71f1dea

Please sign in to comment.