diff --git a/BasicRotations/Healer/SGE_Default.cs b/BasicRotations/Healer/SGE_Default.cs
index e11fbf3e2..90dd8a9db 100644
--- a/BasicRotations/Healer/SGE_Default.cs
+++ b/BasicRotations/Healer/SGE_Default.cs
@@ -1,3 +1,5 @@
+using static FFXIVClientStructs.FFXIV.Client.UI.Misc.DataCenterHelper;
+
namespace RebornRotations.Healer;
[Rotation("Default", CombatType.PvE, GameVersion = "7.15")]
@@ -62,6 +64,10 @@ public sealed class SGE_Default : SageRotation
#endregion
+ #region Tracking Properties
+ private IBaseAction? _lastEukrasiaActionAim = null;
+ #endregion
+
#region Countdown Logic
protected override IAction? CountDownAction(float remainTime)
{
@@ -84,7 +90,24 @@ protected override bool AttackAbility(IAction nextGCD, out IAction? act)
protected override bool EmergencyAbility(IAction nextGCD, out IAction? act)
{
- if (base.EmergencyAbility(nextGCD, out act)) return true;
+ if ((_EukrasiaActionAim == EukrasianDosisIiiPvE || _EukrasiaActionAim == EukrasianDosisIiPvE || _EukrasiaActionAim == EukrasianDosisPvE)
+ && (DyskrasiaPvE.CanUse(out _) || DyskrasiaIiPvE.CanUse(out _) || EukrasianDyskrasiaPvE.CanUse(out _)))
+ {
+ ClearEukrasia();
+ }
+
+ // If the last action performed matches any of a list of specific actions, it clears the Eukrasia aim.
+ // This serves as a reset/cleanup mechanism to ensure the decision logic starts fresh for the next cycle.
+ if (IsLastGCD(false, EukrasianPrognosisIiPvE, EukrasianPrognosisPvE,
+ EukrasianDiagnosisPvE, EukrasianDyskrasiaPvE, EukrasianDosisIiiPvE, EukrasianDosisIiPvE,
+ EukrasianDosisPvE) || !InCombat)
+ {
+ ClearEukrasia();
+ }
+
+ if (ChoiceEukrasia(out act)) return true;
+
+ //if (base.EmergencyAbility(nextGCD, out act)) return true;
if (nextGCD.IsTheSameTo(false, PneumaPvE, EukrasianPrognosisPvE, EukrasianPrognosisIiPvE))
{
@@ -230,7 +253,7 @@ private void SetEukrasia(IBaseAction act)
{
if (act == null) return;
- if (_EukrasiaActionAim != null && IsLastGCD(false, _EukrasiaActionAim)) return;
+ if (_EukrasiaActionAim != null && IsLastGCD(true, _EukrasiaActionAim)) return;
if (_EukrasiaActionAim != act)
{
@@ -243,6 +266,7 @@ private void ClearEukrasia()
{
if (_EukrasiaActionAim != null)
{
+ _lastEukrasiaActionAim = _EukrasiaActionAim;
_EukrasiaActionAim = null;
}
}
@@ -251,16 +275,8 @@ private bool ChoiceEukrasia(out IAction? act)
{
act = null;
- // If the last action performed matches any of a list of specific actions, it clears the Eukrasia aim.
- // This serves as a reset/cleanup mechanism to ensure the decision logic starts fresh for the next cycle.
- if (IsLastGCD(true, EukrasianPrognosisIiPvE, EukrasianPrognosisPvE,
- EukrasianDiagnosisPvE, EukrasianDyskrasiaPvE, EukrasianDosisIiiPvE, EukrasianDosisIiPvE,
- EukrasianDosisPvE) || !InCombat)
- {
- ClearEukrasia();
- }
-
if (!EukrasiaPvE.CanUse(out _)) return false;
+
// Checks for Eukrasia status.
// Attempts to set correct Eurkrasia action based on availablity and MergedStatus.
if (EukrasianPrognosisIiPvE.CanUse(out _) && EukrasianPrognosisIiPvE.EnoughLevel && MergedStatus.HasFlag(AutoStatus.DefenseArea))
@@ -281,25 +297,25 @@ private bool ChoiceEukrasia(out IAction? act)
return false;
}
- if (EukrasianDyskrasiaPvE.CanUse(out _) && EukrasianDyskrasiaPvE.EnoughLevel && (!MergedStatus.HasFlag(AutoStatus.DefenseSingle) || !MergedStatus.HasFlag(AutoStatus.DefenseSingle)))
+ if (EukrasianDyskrasiaPvE.CanUse(out _) && EukrasianDyskrasiaPvE.EnoughLevel && (!MergedStatus.HasFlag(AutoStatus.DefenseSingle) || !MergedStatus.HasFlag(AutoStatus.DefenseArea)))
{
SetEukrasia(EukrasianDyskrasiaPvE);
return false;
}
- if (EukrasianDosisIiiPvE.CanUse(out _) && EukrasianDosisIiiPvE.EnoughLevel && (!MergedStatus.HasFlag(AutoStatus.DefenseSingle) || !MergedStatus.HasFlag(AutoStatus.DefenseSingle)))
+ if ((!EukrasianDyskrasiaPvE.CanUse(out _) || !DyskrasiaPvE.CanUse(out _)) && EukrasianDosisIiiPvE.CanUse(out _) && EukrasianDosisIiiPvE.EnoughLevel && (!MergedStatus.HasFlag(AutoStatus.DefenseSingle) || !MergedStatus.HasFlag(AutoStatus.DefenseArea)))
{
SetEukrasia(EukrasianDosisIiiPvE);
return false;
}
- if (EukrasianDosisIiPvE.CanUse(out _) && EukrasianDosisIiPvE.EnoughLevel && (!MergedStatus.HasFlag(AutoStatus.DefenseSingle) || !MergedStatus.HasFlag(AutoStatus.DefenseSingle)))
+ if ((!EukrasianDyskrasiaPvE.CanUse(out _) || !DyskrasiaPvE.CanUse(out _)) && EukrasianDosisIiPvE.CanUse(out _) && EukrasianDosisIiPvE.EnoughLevel && (!MergedStatus.HasFlag(AutoStatus.DefenseSingle) || !MergedStatus.HasFlag(AutoStatus.DefenseArea)))
{
SetEukrasia(EukrasianDosisIiPvE);
return false;
}
- if (EukrasianDosisPvE.CanUse(out _) && EukrasianDosisPvE.EnoughLevel && (!MergedStatus.HasFlag(AutoStatus.DefenseSingle) || !MergedStatus.HasFlag(AutoStatus.DefenseSingle)))
+ if ((!EukrasianDyskrasiaPvE.CanUse(out _) || !DyskrasiaPvE.CanUse(out _)) && EukrasianDosisPvE.CanUse(out _) && EukrasianDosisPvE.EnoughLevel && (!MergedStatus.HasFlag(AutoStatus.DefenseSingle) || !MergedStatus.HasFlag(AutoStatus.DefenseArea)))
{
SetEukrasia(EukrasianDosisPvE);
return false;
@@ -364,11 +380,8 @@ protected override bool HealSingleGCD(out IAction? act)
protected override bool GeneralGCD(out IAction? act)
{
act = null;
-
if (HasSwift && SwiftLogic && EgeiroPvE.CanUse(out _)) return false;
- if (PhlegmaIiiPvE.CanUse(out act, usedUp: IsMoving)) return true;
- if (PhlegmaIiPvE.CanUse(out act, usedUp: IsMoving)) return true;
if (PhlegmaPvE.CanUse(out act, usedUp: IsMoving)) return true;
if (PartyMembers.Any(b => b.GetHealthRatio() < PneumaSTPartyHeal && !b.IsDead) || PartyMembers.GetJobCategory(JobRole.Tank).Any(t => t.GetHealthRatio() < PneumaSTTankHeal && !t.IsDead))
@@ -378,10 +391,10 @@ protected override bool GeneralGCD(out IAction? act)
if (IsMoving && ToxikonPvE.CanUse(out act)) return true;
- if (ChoiceEukrasia(out act)) return true;
- if (DoEukrasia(out act)) return true;
+ if ((_EukrasiaActionAim != EukrasianDiagnosisPvE || _EukrasiaActionAim != EukrasianPrognosisPvE || _EukrasiaActionAim != EukrasianPrognosisIiPvE || _EukrasiaActionAim != EukrasianDyskrasiaPvE)
+ && DyskrasiaPvE.CanUse(out act)) return true;
- if (DyskrasiaPvE.CanUse(out act)) return true;
+ if (DoEukrasia(out act)) return true;
if (DosisPvE.CanUse(out act)) return true;
@@ -398,6 +411,7 @@ protected override bool GeneralGCD(out IAction? act)
public override void DisplayStatus()
{
ImGui.Text($"Eukrasian Action: {_EukrasiaActionAim}");
+ ImGui.Text($"Last Eukrasian Action: {_lastEukrasiaActionAim}");
ImGui.Text("HasEukrasia: " + HasEukrasia.ToString());
ImGui.Text("Addersgall: " + Addersgall.ToString());
ImGui.Text("Addersting: " + Addersting.ToString());
diff --git a/BasicRotations/Magical/RDM_Default.cs b/BasicRotations/Magical/RDM_Default.cs
index f96d0453a..a48008678 100644
--- a/BasicRotations/Magical/RDM_Default.cs
+++ b/BasicRotations/Magical/RDM_Default.cs
@@ -106,13 +106,7 @@ protected override bool AttackAbility(IAction nextGCD, out IAction? act)
ActionID.EnchantedMoulinetPvE,
ActionID.MoulinetPvE
//I dont know at this point if nextGCD.IsTheSameTo even working, but stil gonna left it in here.
- }) && !nextGCD.IsTheSameTo(new[]
- {
- ActionID.RipostePvE,
- ActionID.EnchantedRipostePvE,
- ActionID.MoulinetPvE,
- ActionID.EnchantedMoulinetPvE
- });
+ }) && !nextGCD.IsTheSameTo(true, ActionID.RipostePvE, ActionID.EnchantedRipostePvE, ActionID.MoulinetPvE, ActionID.EnchantedMoulinetPvE);
//i really hate this.
bool ambatumelee = Player.HasStatus(true, StatusID.Manafication, StatusID.MagickedSwordplay);
diff --git a/BasicRotations/Melee/NIN_Default.cs b/BasicRotations/Melee/NIN_Default.cs
index 657b4e0df..3cac7a1e1 100644
--- a/BasicRotations/Melee/NIN_Default.cs
+++ b/BasicRotations/Melee/NIN_Default.cs
@@ -317,6 +317,8 @@ protected override bool EmergencyAbility(IAction nextGCD, out IAction? act)
// Initializes the action to null, indicating no action has been chosen yet.
act = null;
+ if ((InCombat || (CommbatMudra && HasHostilesInMaxRange)) && ChoiceNinjutsu(out act)) return true;
+
// If Ninjutsu is available or not in combat, defers to the base class's emergency ability logic.
if (!NoNinjutsu || !InCombat) return base.EmergencyAbility(nextGCD, out act);
@@ -413,7 +415,6 @@ protected override bool GeneralGCD(out IAction? act)
if (ForkedUse && ForkedRaijuPvE.CanUse(out act)) return true;
}
- if ((InCombat || (CommbatMudra && HasHostilesInMaxRange)) && ChoiceNinjutsu(out act)) return true;
if (((!InCombat && CommbatMudra && HasHostilesInMaxRange) || !CombatElapsedLess(7)) && DoNinjutsu(out act)) return true;
//No Ninjutsu
diff --git a/ECommons b/ECommons
index 55bc281dc..3de0ce6a4 160000
--- a/ECommons
+++ b/ECommons
@@ -1 +1 @@
-Subproject commit 55bc281dc97ccaff213dfa3f501e9f0c0ae9ee2f
+Subproject commit 3de0ce6a4acb79e058a2ab09ccbc9113c8b15bfc
diff --git a/RotationSolver.Basic/Actions/ActionBasicInfo.cs b/RotationSolver.Basic/Actions/ActionBasicInfo.cs
index 219d569f9..7681addfd 100644
--- a/RotationSolver.Basic/Actions/ActionBasicInfo.cs
+++ b/RotationSolver.Basic/Actions/ActionBasicInfo.cs
@@ -29,6 +29,16 @@ public readonly struct ActionBasicInfo
///
public readonly uint ID => _action.Action.RowId;
+ ///
+ /// The Range of the action.
+ ///
+ public readonly sbyte Range => _action.Action.Range;
+
+ ///
+ /// The EffectRange of the action.
+ ///
+ public readonly byte EffectRange => _action.Action.EffectRange;
+
///
/// The icon of the action.
///
diff --git a/RotationSolver.Basic/Actions/ActionTargetInfo.cs b/RotationSolver.Basic/Actions/ActionTargetInfo.cs
index 3acfeb55a..cac1a8e1c 100644
--- a/RotationSolver.Basic/Actions/ActionTargetInfo.cs
+++ b/RotationSolver.Basic/Actions/ActionTargetInfo.cs
@@ -467,7 +467,7 @@ private bool CheckTimeToKill(IGameObject gameObject)
{
if (DataCenter.Territory?.ContentType == TerritoryContentType.Trials ||
(DataCenter.Territory?.ContentType == TerritoryContentType.Raids &&
- DataCenter.AllianceMembers.Count(p => p is IPlayerCharacter) == 8))
+ DataCenter.AllianceMembers.Count(p => p is IPlayerCharacter) >= 8))
{
var fallbackPoints = new[] { Vector3.Zero, new Vector3(100, 0, 100) };
var closestFallback = fallbackPoints.MinBy(p => Vector3.Distance(player.Position, p));
diff --git a/RotationSolver.Basic/Configuration/Configs.cs b/RotationSolver.Basic/Configuration/Configs.cs
index a96da4f05..341395275 100644
--- a/RotationSolver.Basic/Configuration/Configs.cs
+++ b/RotationSolver.Basic/Configuration/Configs.cs
@@ -466,13 +466,13 @@ public const string
Filter = HealingActionCondition, Section = 3)]
private static readonly bool _healOutOfCombat = false;
- [ConditionBool, UI("Heal solo instance NPCs", Description = "Experimental.",
+ [ConditionBool, UI("Heal solo instance NPCs (Only enable as needed)", Description = "Experimental.",
Filter = HealingActionCondition, Section = 3)]
private static readonly bool _friendlyBattleNPCHeal = false;
- [ConditionBool, UI("Heal and raise Party NPCs.", Description = "Experimental, only enable as needed.",
+ [ConditionBool, UI("Heal and raise Party NPCs.",
Filter = HealingActionCondition, Section = 3)]
- private static readonly bool _friendlyPartyNPCHealRaise2 = false;
+ private static readonly bool _friendlyPartyNPCHealRaise2 = true;
[ConditionBool, UI("Heal/Dance partner your chocobo", Description = "Experimental.",
Filter = HealingActionCondition, Section = 3)]
diff --git a/RotationSolver.Basic/Data/RaiseType.cs b/RotationSolver.Basic/Data/RaiseType.cs
index a8cf2ac18..548faa82e 100644
--- a/RotationSolver.Basic/Data/RaiseType.cs
+++ b/RotationSolver.Basic/Data/RaiseType.cs
@@ -12,14 +12,20 @@ public enum RaiseType : byte
PartyOnly,
///
- /// Raise party and alliance members.
+ /// Raise party members and alliance supports.
///
- [Description("Raise all.")]
- PartyAndAlliance,
+ [Description("Raise party members and alliance supports.")]
+ PartyAndAllianceSupports,
///
/// Raise party members and alliance healers.
///
- [Description("Raise party members and non-party healers.")]
+ [Description("Raise party members and alliance healers.")]
PartyAndAllianceHealers,
+
+ ///
+ /// Raise all.
+ ///
+ [Description("Raise All.")]
+ All,
}
\ No newline at end of file
diff --git a/RotationSolver.Basic/DataCenter.cs b/RotationSolver.Basic/DataCenter.cs
index 45d633b2b..1692fdaeb 100644
--- a/RotationSolver.Basic/DataCenter.cs
+++ b/RotationSolver.Basic/DataCenter.cs
@@ -804,7 +804,10 @@ internal static void AddDamageRec(float damageRatio)
public static bool IsCastingTankVfx()
{
- return IsCastingVfx(s =>
+ // Create a copy of the VfxDataQueue to avoid modification during enumeration
+ var vfxDataQueueCopy = VfxDataQueue.ToList();
+
+ return IsCastingVfx(vfxDataQueueCopy, s =>
{
if (!s.Path.StartsWith("vfx/lockon/eff/tank_lockon")) return false;
if (!Player.Available) return false;
@@ -816,20 +819,19 @@ public static bool IsCastingTankVfx()
public static bool IsCastingAreaVfx()
{
- return IsCastingVfx(s => s.Path.StartsWith("vfx/lockon/eff/coshare"));
+ var vfxDataQueueCopy = VfxDataQueue.ToArray();
+ return IsCastingVfx([.. vfxDataQueueCopy], s => s.Path.StartsWith("vfx/lockon/eff/coshare"));
}
- public static bool IsCastingVfx(Func isVfx)
+ public static bool IsCastingVfx(List vfxDataQueueCopy, Func isVfx)
{
// Ensure the list is not empty
- if (VfxDataQueue.Count == 0)
+ if (vfxDataQueueCopy.Count == 0)
{
return false;
}
- // Create a copy of the VfxDataQueue to avoid modification during enumeration
- var vfxDataQueueCopy = VfxDataQueue.ToList();
-
+ // Iterate over the copied list
foreach (var vfx in vfxDataQueueCopy)
{
if (isVfx(vfx))
diff --git a/RotationSolver.Basic/Helpers/IActionHelper.cs b/RotationSolver.Basic/Helpers/IActionHelper.cs
index 4eb8f1937..581b848b0 100644
--- a/RotationSolver.Basic/Helpers/IActionHelper.cs
+++ b/RotationSolver.Basic/Helpers/IActionHelper.cs
@@ -112,18 +112,19 @@ public static bool IsLastActionAbility()
/// True if the action is the same as any of the provided actions, otherwise false.
public static bool IsTheSameTo(this IAction action, bool isAdjust, params IAction[] actions)
{
- return actions != null && action.IsTheSameTo(GetIDFromActions(isAdjust, actions));
+ return actions != null && action.IsTheSameTo(isAdjust, GetIDFromActions(isAdjust, actions));
}
///
/// Determines if the action is the same as any of the provided action IDs.
///
/// The action to check.
+ /// Whether to use the adjusted ID.
/// The action IDs to check against.
/// True if the action is the same as any of the provided action IDs, otherwise false.
- public static bool IsTheSameTo(this IAction action, params ActionID[] actions)
+ public static bool IsTheSameTo(this IAction action, bool isAdjust, params ActionID[] actions)
{
- return action != null && actions != null && IsActionID((ActionID)action.AdjustedID, actions);
+ return action != null && actions != null && IsActionID(isAdjust ? (ActionID)action.AdjustedID : (ActionID)action.ID, actions);
}
///
diff --git a/RotationSolver.Basic/Helpers/ObjectHelper.cs b/RotationSolver.Basic/Helpers/ObjectHelper.cs
index f07acbcd6..26e955dc9 100644
--- a/RotationSolver.Basic/Helpers/ObjectHelper.cs
+++ b/RotationSolver.Basic/Helpers/ObjectHelper.cs
@@ -11,7 +11,6 @@
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Common.Component.BGCollision;
using FFXIVClientStructs.FFXIV.Component.GUI;
-using Lumina.Excel.Sheets;
using RotationSolver.Basic.Configuration;
using System.Collections.Concurrent;
using System.Runtime.InteropServices;
@@ -42,7 +41,7 @@ internal static bool CanProvoke(this IGameObject target)
if (DataCenter.FateId != 0 && target.FateId() == DataCenter.FateId) return false;
- //Removed the listed names.
+ // Removed the listed names.
if (OtherConfiguration.NoProvokeNames.TryGetValue(Svc.ClientState.TerritoryType, out var ns1))
{
foreach (var n in ns1)
@@ -54,14 +53,13 @@ internal static bool CanProvoke(this IGameObject target)
}
}
- //Target can move or too big and has a target
+ // Target can move or too big and has a target
var targetNpc = target.GetObjectNPC();
if ((targetNpc?.Unknown0 == 0 || target.HitboxRadius >= 5) // Unknown12 used to be the flag checked for the mobs ability to move, honestly just guessing on this one
&& (target.TargetObject?.IsValid() ?? false))
{
- //the target is not a tank role
- if (Svc.Objects.SearchById(target.TargetObjectId) is IBattleChara battle
- && !battle.IsJobCategory(JobRole.Tank)
+ // The target is not a tank role
+ if (Svc.Objects.SearchById(target.TargetObjectId) is IBattleChara targetObject && !targetObject.IsJobCategory(JobRole.Tank)
&& (Vector3.Distance(target.Position, Player.Object?.Position ?? Vector3.Zero) > 5))
{
return true;
@@ -83,7 +81,7 @@ internal static unsafe bool IsOthersPlayers(this IGameObject obj)
internal static bool IsAttackable(this IBattleChara battleChara)
{
- if (battleChara.IsAllianceMember() == true) return false;
+ if (battleChara.IsAllianceMember()) return false;
// Dead.
if (Service.Config.FilterOneHpInvincible && battleChara.CurrentHp <= 1) return false;
@@ -136,7 +134,7 @@ internal static bool IsAttackable(this IBattleChara battleChara)
TargetHostileType.TargetsHaveTarget => battleChara.TargetObject is IBattleChara,
TargetHostileType.AllTargetsWhenSolo => DataCenter.PartyMembers.Count < 2 || battleChara.TargetObject is IBattleChara,
TargetHostileType.AllTargetsWhenSoloInDuty => (DataCenter.PartyMembers.Count < 2 && (Svc.Condition[ConditionFlag.BoundByDuty] || Svc.Condition[ConditionFlag.BoundByDuty56]))
- || battleChara.TargetObject is IBattleChara,
+ || battleChara.TargetObject is IBattleChara,
TargetHostileType.TargetIsInEnemiesList => battleChara.TargetObject is IBattleChara target && target.IsInEnemiesList(),
TargetHostileType.AllTargetsWhenSoloTargetIsInEnemiesList => (DataCenter.PartyMembers.Count < 2 && (Svc.Condition[ConditionFlag.BoundByDuty] || Svc.Condition[ConditionFlag.BoundByDuty56])) || battleChara.TargetObject is IBattleChara target && target.IsInEnemiesList(),
TargetHostileType.AllTargetsWhenSoloInDutyTargetIsInEnemiesList => DataCenter.PartyMembers.Count < 2 || battleChara.TargetObject is IBattleChara target && target.IsInEnemiesList(),
@@ -167,17 +165,12 @@ internal static unsafe bool IsInEnemiesList(this IBattleChara battleChara)
{
var addons = Service.GetAddons();
- if (!addons.Any())
+ if (addons == null || !addons.Any())
{
return false;
}
- if (!addons.Any())
- {
- return false;
- }
-
- var addon = addons.FirstOrDefault();
+ var addon = addons.First();
if (addon == IntPtr.Zero)
{
return false;
@@ -247,36 +240,67 @@ internal static unsafe bool IsEnemy(this IGameObject obj)
internal static unsafe bool IsAllianceMember(this IGameObject obj)
=> obj.GameObjectId is not 0
- && (!DataCenter.IsPvP && DataCenter.IsInAllianceRaid && obj is IPlayerCharacter
- || DataCenter.IsInAllianceRaid && ActionManager.CanUseActionOnTarget((uint)ActionID.CurePvE, obj.Struct()));
+ && (!DataCenter.IsPvP && DataCenter.IsInAllianceRaid && obj is IPlayerCharacter && (ActionManager.CanUseActionOnTarget((uint)ActionID.RaisePvE, obj.Struct()) || ActionManager.CanUseActionOnTarget((uint)ActionID.CurePvE, obj.Struct())));
- private static readonly object _lock = new object();
+ private static readonly object _lock = new();
internal static bool IsParty(this IGameObject gameObject)
{
if (gameObject == null) return false;
- // Use a lock to ensure thread safety
lock (_lock)
{
- // Accessing Player.Object and Svc.Party within the lock to ensure thread safety
- if (gameObject.GameObjectId == Player.Object?.GameObjectId) return true;
- foreach (var p in Svc.Party)
+ try
{
- if (p.GameObject?.GameObjectId == gameObject.GameObjectId) return true;
+ var playerObject = Player.Object;
+ if (playerObject == null) return false;
+
+ if (gameObject.GameObjectId == playerObject.GameObjectId) return true;
+
+ foreach (var p in Svc.Party)
+ {
+ if (p?.GameObject?.GameObjectId == gameObject.GameObjectId) return true;
+ }
+
+ if (Service.Config.FriendlyPartyNpcHealRaise2 && gameObject.IsNpcPartyMember()) return true;
+ if (Service.Config.ChocoboPartyMember && gameObject.IsPlayerCharacterChocobo()) return true;
+ if (Service.Config.FriendlyBattleNpcHeal && gameObject.IsFriendlyBattleNPC()) return true;
+ if (Service.Config.FocusTargetIsParty && gameObject.IsFocusTarget() && gameObject.IsAllianceMember()) return true;
+ }
+ catch (Exception ex)
+ {
+ Svc.Log.Error($"Error in IsParty: {ex}");
+ return false;
}
- if (Service.Config.FriendlyPartyNpcHealRaise2 && gameObject.GetBattleNPCSubKind() == BattleNpcSubKind.NpcPartyMember) return true;
- if (Service.Config.ChocoboPartyMember && gameObject.GetNameplateKind() == NameplateKind.PlayerCharacterChocobo) return true;
- if (Service.Config.FriendlyBattleNpcHeal && gameObject.GetNameplateKind() == NameplateKind.FriendlyBattleNPC) return true;
- if (Service.Config.FocusTargetIsParty && gameObject.IsFocusTarget() == true && gameObject.IsAllianceMember() == true) return true;
}
return false;
}
- internal static bool IsFocusTarget(this IGameObject gameObject)
+ internal static bool IsNpcPartyMember(this IGameObject gameObj)
+ {
+ return gameObj?.GetBattleNPCSubKind() == BattleNpcSubKind.NpcPartyMember;
+ }
+
+ internal static bool IsPlayerCharacterChocobo(this IGameObject gameObj)
+ {
+ return gameObj?.GetBattleNPCSubKind() == BattleNpcSubKind.Chocobo;
+ }
+
+ internal static bool IsFriendlyBattleNPC(this IGameObject gameObj)
+ {
+ if (gameObj == null)
+ {
+ return false;
+ }
+
+ var nameplateKind = gameObj?.GetNameplateKind();
+ return nameplateKind == NameplateKind.FriendlyBattleNPC;
+ }
+
+ internal static bool IsFocusTarget(this IGameObject gameObj)
{
var focusTarget = Svc.Targets.FocusTarget;
- return focusTarget != null && focusTarget.GameObjectId == gameObject.GameObjectId;
+ return focusTarget != null && focusTarget.GameObjectId == gameObj.GameObjectId;
}
internal static bool IsTargetOnSelf(this IBattleChara IBattleChara)
@@ -290,7 +314,7 @@ internal static bool IsDeathToRaise(this IGameObject obj)
if (obj is IBattleChara b && b.CurrentHp != 0) return false;
if (obj.HasStatus(false, StatusID.Raise)) return false;
if (!Service.Config.RaiseBrinkOfDeath && obj.HasStatus(false, StatusID.BrinkOfDeath)) return false;
- foreach (var c in DataCenter.AllianceMembers)
+ foreach (var c in DataCenter.PartyMembers)
{
if (c.CastTargetObjectId == obj.GameObjectId) return false;
}
@@ -308,8 +332,8 @@ internal static bool IsAlive(this IGameObject obj)
///
///
///
- public static unsafe Dalamud.Game.ClientState.Objects.Enums.ObjectKind GetObjectKind(this IGameObject obj)
- => (Dalamud.Game.ClientState.Objects.Enums.ObjectKind)obj.Struct()->ObjectKind;
+ public static unsafe ObjectKind GetObjectKind(this IGameObject obj)
+ => (ObjectKind)obj.Struct()->ObjectKind;
///
/// Determines whether the specified game object is a top priority hostile target based on its name being listed.
@@ -351,6 +375,7 @@ internal static bool IsTopPriorityHostile(this IGameObject obj)
if (Player.Object == null) return false;
// Check IBattleChara against the priority target list of OIDs
if (PriorityTargetHelper.IsPriorityTarget(b.DataId)) return true;
+
// Ensure StatusList is not null before calling Any
foreach (var status in b.StatusList)
{
@@ -358,9 +383,8 @@ internal static bool IsTopPriorityHostile(this IGameObject obj)
}
}
- if (Service.Config.ChooseAttackMark && MarkingHelper.GetAttackSignTargets().FirstOrDefault(id => id != 0) == (long)obj.GameObjectId && obj.IsEnemy()) return true;
+ if (Service.Config.ChooseAttackMark && MarkingHelper.GetAttackSignTargets().Any(id => id != 0 && id == (long)obj.GameObjectId && obj.IsEnemy())) return true;
- // Fate
if (Service.Config.TargetFatePriority && fateId != 0 && obj.FateId() == fateId) return true;
var icon = obj.GetNamePlateIcon();
@@ -392,7 +416,7 @@ internal static bool IsTopPriorityHostile(this IGameObject obj)
internal static unsafe uint FateId(this IGameObject obj) => obj.Struct()->FateId;
- static readonly ConcurrentDictionary _effectRangeCheck = new();
+ static readonly ConcurrentDictionary _effectRangeCheck = [];
///
/// Determines whether the specified game object can be interrupted.
@@ -432,8 +456,8 @@ public static bool IsJeunoBossImmune(this IBattleChara obj)
if (obj.IsEnemy())
{
- if (obj.HasStatus(true, StatusID.EpicVillain) &&
- (player.HasStatus(true, StatusID.VauntedHero) || player.HasStatus(true, StatusID.FatedHero)))
+ if (obj.HasStatus(false, StatusID.EpicVillain) &&
+ (player.HasStatus(false, StatusID.VauntedHero) || player.HasStatus(false, StatusID.FatedHero)))
{
if (Service.Config.InDebug)
{
@@ -442,8 +466,8 @@ public static bool IsJeunoBossImmune(this IBattleChara obj)
return true;
}
- if (obj.HasStatus(true, StatusID.VauntedVillain) &&
- (player.HasStatus(true, StatusID.EpicHero) || player.HasStatus(true, StatusID.FatedHero)))
+ if (obj.HasStatus(false, StatusID.VauntedVillain) &&
+ (player.HasStatus(false, StatusID.EpicHero) || player.HasStatus(false, StatusID.FatedHero)))
{
if (Service.Config.InDebug)
{
@@ -452,8 +476,8 @@ public static bool IsJeunoBossImmune(this IBattleChara obj)
return true;
}
- if (obj.HasStatus(true, StatusID.FatedVillain) &&
- (player.HasStatus(true, StatusID.EpicHero) || player.HasStatus(true, StatusID.VauntedHero)))
+ if (obj.HasStatus(false, StatusID.FatedVillain) &&
+ (player.HasStatus(false, StatusID.EpicHero) || player.HasStatus(false, StatusID.VauntedHero)))
{
if (Service.Config.InDebug)
{
@@ -606,7 +630,8 @@ internal static float TimeAlive(this IBattleChara b)
_aliveStartTimes[b.GameObjectId] = DateTime.Now;
}
- return (float)(DateTime.Now - _aliveStartTimes[b.GameObjectId]).TotalSeconds;
+ var elapsedTime = (float)(DateTime.Now - _aliveStartTimes[b.GameObjectId]).TotalSeconds;
+ return elapsedTime > 30 ? 30 : elapsedTime;
}
private static readonly ConcurrentDictionary _deadStartTimes = new();
@@ -635,7 +660,8 @@ internal static float TimeDead(this IBattleChara b)
_deadStartTimes[b.GameObjectId] = DateTime.Now;
}
- return (float)(DateTime.Now - _deadStartTimes[b.GameObjectId]).TotalSeconds;
+ var elapsedTime = (float)(DateTime.Now - _deadStartTimes[b.GameObjectId]).TotalSeconds;
+ return elapsedTime > 30 ? 30 : elapsedTime;
}
///
diff --git a/RotationSolver.Basic/Helpers/TargetFilter.cs b/RotationSolver.Basic/Helpers/TargetFilter.cs
index 356360e60..b5b75964a 100644
--- a/RotationSolver.Basic/Helpers/TargetFilter.cs
+++ b/RotationSolver.Basic/Helpers/TargetFilter.cs
@@ -24,7 +24,6 @@ public static IEnumerable GetDeath(this IEnumerable
if (item == null || !item.IsDead || item.CurrentHp != 0 || !item.IsTargetable) return false;
if (item.HasStatus(false, StatusID.Raise)) return false;
if (!Service.Config.RaiseBrinkOfDeath && item.HasStatus(false, StatusID.BrinkOfDeath)) return false;
- if (DataCenter.AllianceMembers.Any(c => c.CastTargetObjectId == item.GameObjectId)) return false;
return true;
});
}
diff --git a/RotationSolver.Basic/Rotations/Basic/SageRotation.cs b/RotationSolver.Basic/Rotations/Basic/SageRotation.cs
index 61a9290db..02ca79acc 100644
--- a/RotationSolver.Basic/Rotations/Basic/SageRotation.cs
+++ b/RotationSolver.Basic/Rotations/Basic/SageRotation.cs
@@ -14,12 +14,12 @@ partial class SageRotation
///
/// Gets the amount of Addersgall available.
///
- public static byte Addersgall => JobGauge.Addersgall;
+ public static byte Addersgall => AddersgallTrait.EnoughLevel ? JobGauge.Addersgall : (byte)0;
///
/// Gets the amount of Addersting available.
///
- public static byte Addersting => JobGauge.Addersting;
+ public static byte Addersting => AdderstingTrait.EnoughLevel ? JobGauge.Addersting : (byte)0;
static float AddersgallTimerRaw => JobGauge.AddersgallTimer / 1000f;
@@ -27,7 +27,7 @@ partial class SageRotation
/// Gets the amount of milliseconds elapsed until the next Addersgall is available.
/// This counts from 0 to 20_000.
///
- public static float AddersgallTime => AddersgallTimerRaw - DataCenter.DefaultGCDRemain;
+ public static float AddersgallTime => AddersgallTrait.EnoughLevel ? AddersgallTimerRaw - DataCenter.DefaultGCDRemain : 0;
///
/// Used to determine if the cooldown for the next Addersgall will end within a specified time frame.
@@ -71,7 +71,8 @@ static partial void ModifyDiagnosisPvE(ref ActionSetting setting)
static partial void ModifyKardiaPvE(ref ActionSetting setting)
{
setting.TargetType = TargetType.Tank;
- setting.ActionCheck = () => !DataCenter.AllianceMembers.Any(m => m.HasStatus(true, StatusID.Kardion));
+ setting.TargetStatusProvide = [StatusID.Kardion];
+ setting.ActionCheck = () => !DataCenter.PartyMembers.Any(m => m.HasStatus(true, StatusID.Kardion));
setting.CreateConfig = () => new ActionConfig()
{
TimeToKill = 0,
diff --git a/RotationSolver.Basic/Rotations/CustomRotation_Ability.cs b/RotationSolver.Basic/Rotations/CustomRotation_Ability.cs
index 0f9f711bf..a48c7e35c 100644
--- a/RotationSolver.Basic/Rotations/CustomRotation_Ability.cs
+++ b/RotationSolver.Basic/Rotations/CustomRotation_Ability.cs
@@ -1,3 +1,5 @@
+using RotationSolver.Basic.Helpers;
+
namespace RotationSolver.Basic.Rotations;
partial class CustomRotation
@@ -416,7 +418,7 @@ protected virtual bool EmergencyAbility(IAction nextGCD, out IAction? act)
if (DataCenter.CommandStatus.HasFlag(AutoStatus.Raise))
{
- if (Role is JobRole.Healer && SwiftcastPvE.CanUse(out act, isFirstAbility: true))
+ if (Role is JobRole.Healer && nextGCD.IsTheSameTo(true, ActionID.RaisePvE, ActionID.EgeiroPvE, ActionID.ResurrectionPvE, ActionID.AscendPvE) && SwiftcastPvE.CanUse(out act))
{
return true;
}
diff --git a/RotationSolver/UI/RotationConfigWindow.cs b/RotationSolver/UI/RotationConfigWindow.cs
index 484a7e5a5..f57eb160c 100644
--- a/RotationSolver/UI/RotationConfigWindow.cs
+++ b/RotationSolver/UI/RotationConfigWindow.cs
@@ -1741,18 +1741,26 @@ static void DrawActionDebug()
if (_activeAction is IBaseAction action)
{
-
try
{
ImGui.Text("ID: " + action.Info.ID.ToString());
#if DEBUG
ImGui.Text("Is Real GCD: " + action.Info.IsRealGCD.ToString());
- ImGui.Text("Resources: " + ActionManager.Instance()->CheckActionResources(ActionType.Action, action.AdjustedID).ToString());
- ImGui.Text("Status: " + ActionManager.Instance()->GetActionStatus(ActionType.Action, action.AdjustedID).ToString());
+
+ // Ensure ActionManager.Instance() is not null
+ if (ActionManager.Instance() != null)
+ {
+ ImGui.Text("Resources: " + ActionManager.Instance()->CheckActionResources(ActionType.Action, action.AdjustedID).ToString());
+ ImGui.Text("Status: " + ActionManager.Instance()->GetActionStatus(ActionType.Action, action.AdjustedID).ToString());
+ }
+
ImGui.Text("Cast Time: " + action.Info.CastTime.ToString());
ImGui.Text("MP: " + action.Info.MPNeed.ToString());
#endif
ImGui.Text("AttackType: " + action.Info.AttackType.ToString());
+ ImGui.Text("Level: " + action.Info.Level.ToString());
+ ImGui.Text("Range: " + action.Info.Range.ToString());
+ ImGui.Text("EffectRange: " + action.Info.EffectRange.ToString());
ImGui.Text("Aspect: " + action.Info.Aspect.ToString());
ImGui.Text("Has One:" + action.Cooldown.HasOneCharge.ToString());
ImGui.Text("Recast One: " + action.Cooldown.RecastTimeOneChargeRaw.ToString());
@@ -1764,19 +1772,24 @@ static void DrawActionDebug()
ImGui.Text("Target Name: " + action.Target.Target?.Name ?? string.Empty);
ImGui.Text($"SpellUnlocked: {action.Info.SpellUnlocked} ({action.Action.UnlockLink.RowId})");
}
- catch
+ catch (Exception ex)
{
-
+ ImGui.TextColored(ImGuiColors.DalamudRed, "Error: " + ex.Message);
}
}
else if (_activeAction is IBaseItem item)
{
try
{
- ImGui.Text("Status: " + ActionManager.Instance()->GetActionStatus(ActionType.Item, item.ID).ToString());
- ImGui.Text("Status HQ: " + ActionManager.Instance()->GetActionStatus(ActionType.Item, item.ID + 1000000).ToString());
- var remain = ActionManager.Instance()->GetRecastTime(ActionType.Item, item.ID) - ActionManager.Instance()->GetRecastTimeElapsed(ActionType.Item, item.ID);
- ImGui.Text("remain: " + remain.ToString());
+ // Ensure ActionManager.Instance() is not null
+ if (ActionManager.Instance() != null)
+ {
+ ImGui.Text("Status: " + ActionManager.Instance()->GetActionStatus(ActionType.Item, item.ID).ToString());
+ ImGui.Text("Status HQ: " + ActionManager.Instance()->GetActionStatus(ActionType.Item, item.ID + 1000000).ToString());
+ var remain = ActionManager.Instance()->GetRecastTime(ActionType.Item, item.ID) - ActionManager.Instance()->GetRecastTimeElapsed(ActionType.Item, item.ID);
+ ImGui.Text("remain: " + remain.ToString());
+ }
+
ImGui.Text("CanUse: " + item.CanUse(out _, true).ToString());
if (item is HpPotionItem healPotionItem)
@@ -1784,9 +1797,9 @@ static void DrawActionDebug()
ImGui.Text("MaxHP:" + healPotionItem.MaxHp.ToString());
}
}
- catch
+ catch (Exception ex)
{
-
+ ImGui.TextColored(ImGuiColors.DalamudRed, "Error: " + ex.Message);
}
}
}
@@ -2775,7 +2788,7 @@ private static unsafe void DrawStatus()
// Display all party members
var partyMembers = DataCenter.PartyMembers;
- if (partyMembers.Any())
+ if (partyMembers.Count != 0)
{
ImGui.Text("Party Members:");
foreach (var member in partyMembers)
@@ -2790,7 +2803,7 @@ private static unsafe void DrawStatus()
// Display all party members
var friendlyNPCMembers = DataCenter.FriendlyNPCMembers;
- if (friendlyNPCMembers.Any())
+ if (friendlyNPCMembers.Count != 0)
{
ImGui.Text("Friendly NPC Members:");
foreach (var member in friendlyNPCMembers)
@@ -2878,6 +2891,9 @@ private static unsafe void DrawTargetData()
ImGui.Text($"Is Boss Icon: {battleChara.IsBossFromIcon()}");
ImGui.Text($"Rank: {battleChara.GetObjectNPC()?.Rank.ToString() ?? string.Empty}");
ImGui.Text($"Has Positional: {battleChara.HasPositional()}");
+ ImGui.Text($"IsNpcPartyMember: {battleChara.IsNpcPartyMember()}");
+ ImGui.Text($"IsPlayerCharacterChocobo: {battleChara.IsPlayerCharacterChocobo()}");
+ ImGui.Text($"IsFriendlyBattleNPC: {battleChara.IsFriendlyBattleNPC()}");
ImGui.Text($"Is Dying: {battleChara.IsDying()}");
ImGui.Text($"Is Alive: {battleChara.IsAlive()}");
ImGui.Text($"Is Party: {battleChara.IsParty()}");
diff --git a/RotationSolver/Updaters/TargetUpdater.cs b/RotationSolver/Updaters/TargetUpdater.cs
index 8c0902f75..e9f8e9e1f 100644
--- a/RotationSolver/Updaters/TargetUpdater.cs
+++ b/RotationSolver/Updaters/TargetUpdater.cs
@@ -47,15 +47,20 @@ private static unsafe List GetPartyMembers()
var partyMembers = new List();
try
{
- if (DataCenter.AllianceMembers != null)
+ if (DataCenter.PartyMembers != null)
{
- foreach (var member in DataCenter.AllianceMembers)
+ foreach (var member in DataCenter.AllTargets)
{
- if (ObjectHelper.IsParty(member) && member.Character() != null &&
- member.Character()->CharacterData.OnlineStatus != 15 &&
- member.Character()->CharacterData.OnlineStatus != 5 && member.IsTargetable)
+ if (member == null) continue;
+
+ if (ObjectHelper.IsParty(member) && member.IsParty() && member.Character() != null)
{
- partyMembers.Add(member);
+ var character = member.Character();
+ if (character != null && character->CharacterData.OnlineStatus != 15 &&
+ character->CharacterData.OnlineStatus != 5 && member.IsTargetable)
+ {
+ partyMembers.Add(member);
+ }
}
}
}
@@ -72,11 +77,11 @@ private static unsafe List GetAllianceMembers()
var allianceMembers = new List();
try
{
- if (DataCenter.AllTargets != null)
+ if (DataCenter.AllianceMembers != null)
{
foreach (var target in DataCenter.AllTargets)
{
- if (ObjectHelper.IsAllianceMember(target) && target.Character() != null &&
+ if (ObjectHelper.IsAllianceMember(target) && !target.IsParty() && target.Character() != null &&
target.Character()->CharacterData.OnlineStatus != 15 &&
target.Character()->CharacterData.OnlineStatus != 5 && target.IsTargetable)
{
@@ -178,19 +183,68 @@ private static List GetAllHostileTargets()
{
try
{
- var deathAll = DataCenter.AllianceMembers?.GetDeath().ToList() ?? new List();
var deathParty = DataCenter.PartyMembers?.GetDeath().ToList() ?? new List();
+ var deathAll = DataCenter.AllTargets?.GetDeath().ToList() ?? new List();
var deathNPC = DataCenter.FriendlyNPCMembers?.GetDeath().ToList() ?? new List();
+ var deathAllianceMembers = DataCenter.AllianceMembers?.GetDeath().ToList() ?? new List();
+ var deathAllianceHealers = DataCenter.AllianceMembers?.Where(member => member.IsJobCategory(JobRole.Healer))
+ .GetDeath().ToList() ?? new List();
+ var deathAllianceSupports = DataCenter.AllianceMembers?
+ .Where(member => member.IsJobCategory(JobRole.Healer) || member.IsJobCategory(JobRole.Tank))
+ .GetDeath()
+ .ToList() ?? new List();
+
+ var raisePartyAndAllianceSupports = deathParty.Concat(deathAllianceSupports).ToList();
+ var raisePartyAndAllianceHealers = deathParty.Concat(deathAllianceHealers).ToList();
+ var raisetype = Service.Config.RaiseType;
+
+ var validRaiseTargets = new List();
+
+ if (raisetype == RaiseType.PartyOnly)
+ {
+ validRaiseTargets.AddRange(deathParty);
+ }
+
+ if (raisetype == RaiseType.PartyAndAllianceSupports)
+ {
+ validRaiseTargets.AddRange(raisePartyAndAllianceSupports);
+ }
- var deathTarget = GetPriorityDeathTarget(deathParty);
+ if (raisetype == RaiseType.PartyAndAllianceHealers)
+ {
+ validRaiseTargets.AddRange(raisePartyAndAllianceHealers);
+ }
+
+ if (raisetype == RaiseType.All)
+ {
+ validRaiseTargets.AddRange(deathAll);
+ }
+
+ if (Service.Config.FriendlyPartyNpcHealRaise2)
+ {
+ validRaiseTargets.AddRange(deathNPC);
+ }
+
+ if (Service.Config.FocusTargetIsParty)
+ {
+ validRaiseTargets.AddRange(deathNPC);
+ }
+
+ var deathTarget = GetPriorityDeathTarget(validRaiseTargets, RaiseType.PartyOnly);
+ if (deathTarget != null) return deathTarget;
+
+ deathTarget = GetPriorityDeathTarget(validRaiseTargets, RaiseType.PartyAndAllianceSupports);
if (deathTarget != null) return deathTarget;
- deathTarget = GetPriorityDeathTarget(deathAll, Service.Config.RaiseType);
+ deathTarget = GetPriorityDeathTarget(validRaiseTargets, RaiseType.PartyAndAllianceHealers);
+ if (deathTarget != null) return deathTarget;
+
+ deathTarget = GetPriorityDeathTarget(validRaiseTargets, RaiseType.All);
if (deathTarget != null) return deathTarget;
if (Service.Config.FriendlyPartyNpcHealRaise2)
{
- deathTarget = GetPriorityDeathTarget(deathNPC);
+ deathTarget = GetPriorityDeathTarget(validRaiseTargets, Service.Config.RaiseType);
if (deathTarget != null) return deathTarget;
}
}
@@ -202,14 +256,15 @@ private static List GetAllHostileTargets()
return null;
}
- private static IBattleChara? GetPriorityDeathTarget(List deathList, RaiseType raiseType = RaiseType.PartyOnly)
+ private static IBattleChara? GetPriorityDeathTarget(List validRaiseTargets, RaiseType raiseType = RaiseType.PartyOnly)
{
- if (deathList.Count == 0) return null;
+ if (validRaiseTargets.Count == 0) return null;
var deathTanks = new List();
var deathHealers = new List();
+ var deathOthers = new List();
- foreach (var chara in deathList)
+ foreach (var chara in validRaiseTargets)
{
if (chara.IsJobCategory(JobRole.Tank))
{
@@ -219,18 +274,51 @@ private static List GetAllHostileTargets()
{
deathHealers.Add(chara);
}
+ else
+ {
+ deathOthers.Add(chara);
+ }
+ }
+
+ if (raiseType == RaiseType.PartyAndAllianceHealers)
+ {
+ foreach (var chara in validRaiseTargets)
+ {
+ if (chara.IsJobCategory(JobRole.Healer))
+ {
+ deathHealers.Add(chara);
+ }
+ }
+ }
+ else if (raiseType == RaiseType.PartyAndAllianceSupports)
+ {
+ foreach (var chara in validRaiseTargets)
+ {
+ if (chara.IsJobCategory(JobRole.Tank))
+ {
+ deathTanks.Add(chara);
+ }
+ else if (chara.IsJobCategory(JobRole.Healer))
+ {
+ deathHealers.Add(chara);
+ }
+ else
+ {
+ deathOthers.Add(chara);
+ }
+ }
}
- if (raiseType == RaiseType.PartyAndAllianceHealers && deathHealers.Count > 0)
+ if (raiseType == RaiseType.PartyAndAllianceHealers)
{
- return deathHealers[0];
+ if (deathHealers.Count > 0) return deathHealers[0];
}
if (deathTanks.Count > 1) return deathTanks[0];
if (deathHealers.Count > 0) return deathHealers[0];
if (deathTanks.Count > 0) return deathTanks[0];
- return deathList[0];
+ return deathOthers.Count > 0 ? deathOthers[0] : null;
}
private static IBattleChara? GetDispelTarget()