From 4919e1ad5ea6f82eeee2c6505b2e960883998ae3 Mon Sep 17 00:00:00 2001
From: LTS-FFXIV <127939494+LTS-FFXIV@users.noreply.github.com>
Date: Wed, 15 Jan 2025 07:54:53 -0600
Subject: [PATCH] Refactor and enhance rotation logic and configurations
Refactored SCH, SGE, and WHM rotation logic for better readability and maintainability. Updated `SCH_Default.cs` and `SGE_Default.cs` to iterate over party members and tanks for specific actions. Simplified and refactored methods in `SGE_Default.cs` to remove LINQ. Updated `ECommons` subproject commit.
Removed `IsLastAbilityUsable` and `IsFirstAbilityUsable` checks in `BaseAction.cs`. Added new configuration options in `Configs.cs` for raising players and adjusted `_action4head` value. Updated `DataCenter.cs` with XML documentation comments and adjusted `CalculatedActionAhead`.
Refactored `StatusHelper` and `TargetFilter` methods to use explicit loops instead of LINQ. Improved `CustomRotation` logic with additional checks and conditions. Commented out `UseLimitBreak` method in `CustomRotation_GCD.cs`. Added null checks and early returns in `CustomRotation` methods. Cleaned up debug text in `RotationConfigWindow`.
Simplified `RemoveExpiredVfxData` method in `MajorUpdater`. Enhanced `TargetUpdater` with explicit loops, new configuration checks, and improved logic for determining valid raise targets.
---
BasicRotations/Healer/SCH_Default.cs | 4 +-
BasicRotations/Healer/SGE_Default.cs | 140 +++++++++---------
BasicRotations/Healer/WHM_Default.cs | 6 +-
ECommons | 2 +-
RotationSolver.Basic/Actions/BaseAction.cs | 31 ----
RotationSolver.Basic/Configuration/Configs.cs | 22 ++-
RotationSolver.Basic/DataCenter.cs | 51 ++++---
RotationSolver.Basic/Helpers/StatusHelper.cs | 51 +++++--
RotationSolver.Basic/Helpers/TargetFilter.cs | 73 +++++++--
.../Rotations/CustomRotation_Ability.cs | 4 +-
.../Rotations/CustomRotation_GCD.cs | 47 +++---
RotationSolver/UI/RotationConfigWindow.cs | 30 ----
RotationSolver/Updaters/MajorUpdater.cs | 19 ++-
RotationSolver/Updaters/TargetUpdater.cs | 47 ++++--
14 files changed, 295 insertions(+), 232 deletions(-)
diff --git a/BasicRotations/Healer/SCH_Default.cs b/BasicRotations/Healer/SCH_Default.cs
index 41e4c30e6..ae24b15b0 100644
--- a/BasicRotations/Healer/SCH_Default.cs
+++ b/BasicRotations/Healer/SCH_Default.cs
@@ -180,7 +180,7 @@ protected override bool HealAreaGCD(out IAction? act)
return base.HealAreaGCD(out act);
}
- [RotationDesc(ActionID.AdloquiumPvE, ActionID.PhysickPvE)]
+ [RotationDesc(ActionID.AdloquiumPvE, ActionID.ManifestationPvE, ActionID.PhysickPvE)]
protected override bool HealSingleGCD(out IAction? act)
{
act = null;
@@ -193,7 +193,7 @@ protected override bool HealSingleGCD(out IAction? act)
return base.HealSingleGCD(out act);
}
- [RotationDesc(ActionID.SuccorPvE)]
+ [RotationDesc(ActionID.SuccorPvE, ActionID.ConcitationPvE, ActionID.AccessionPvE)]
protected override bool DefenseAreaGCD(out IAction? act)
{
act = null;
diff --git a/BasicRotations/Healer/SGE_Default.cs b/BasicRotations/Healer/SGE_Default.cs
index 6b6382bb5..91668c716 100644
--- a/BasicRotations/Healer/SGE_Default.cs
+++ b/BasicRotations/Healer/SGE_Default.cs
@@ -6,9 +6,6 @@ namespace RebornRotations.Healer;
public sealed class SGE_Default : SageRotation
{
#region Config Options
- [RotationConfig(CombatType.PvE, Name = "Use new Eukrasian Logic")]
- public bool NewELogic { get; set; } = true;
-
[RotationConfig(CombatType.PvE, Name = "Use spells with cast times to heal. (Ignored if you are the only healer in party)")]
public bool GCDHeal { get; set; } = false;
@@ -93,7 +90,6 @@ protected override bool EmergencyAbility(IAction nextGCD, out IAction? act)
}
if (ChoiceEukrasia(out act)) return true;
-
//if (base.EmergencyAbility(nextGCD, out act)) return true;
if (nextGCD.IsTheSameTo(false, PneumaPvE, EukrasianPrognosisPvE, EukrasianPrognosisIiPvE))
@@ -193,29 +189,49 @@ protected override bool HealSingleAbility(IAction nextGCD, out IAction? act)
if ((!TaurocholePvE.EnoughLevel || TaurocholePvE.Cooldown.IsCoolingDown) && DruocholePvE.CanUse(out act)) return true;
- if (SoteriaPvE.CanUse(out act) && PartyMembers.Any(b => b.HasStatus(true, StatusID.Kardion) && b.GetHealthRatio() < SoteriaHeal)) return true;
+ foreach (var member in PartyMembers)
+ {
+ if (SoteriaPvE.CanUse(out act) && member.HasStatus(true, StatusID.Kardion) && member.GetHealthRatio() < SoteriaHeal)
+ {
+ return true;
+ }
+ }
var tank = PartyMembers.GetJobCategory(JobRole.Tank);
- if (Addersgall < 1 && (tank.Any(t => t.GetHealthRatio() < OGCDTankHeal) || PartyMembers.Any(b => b.GetHealthRatio() < OGCDHeal)))
+ foreach (var t in tank)
{
- if (HaimaPvE.CanUse(out act)) return true;
-
- if (PhysisIiPvE.CanUse(out act)) return true;
- if (!PhysisIiPvE.EnoughLevel && PhysisPvE.CanUse(out act)) return true;
-
- if (HolosPvE.CanUse(out act)) return true;
+ if (Addersgall < 1 && t.GetHealthRatio() < OGCDTankHeal)
+ {
+ if (HaimaPvE.CanUse(out act)) return true;
+ if (PhysisIiPvE.CanUse(out act)) return true;
+ if (!PhysisIiPvE.EnoughLevel && PhysisPvE.CanUse(out act)) return true;
+ if (HolosPvE.CanUse(out act)) return true;
+ if ((!HaimaPvE.EnoughLevel || HaimaPvE.Cooldown.ElapsedAfter(20)) && PanhaimaPvE.CanUse(out act)) return true;
+ }
+ }
- if ((!HaimaPvE.EnoughLevel || HaimaPvE.Cooldown.ElapsedAfter(20)) && PanhaimaPvE.CanUse(out act)) return true;
+ foreach (var t in tank)
+ {
+ if (t.GetHealthRatio() < ZoeHeal)
+ {
+ if (ZoePvE.CanUse(out act)) return true;
+ }
}
- if (tank.Any(t => t.GetHealthRatio() < ZoeHeal))
+ foreach (var t in tank)
{
- if (ZoePvE.CanUse(out act)) return true;
+ if (t.GetHealthRatio() < KrasisTankHeal)
+ {
+ if (KrasisPvE.CanUse(out act)) return true;
+ }
}
- if (tank.Any(t => t.GetHealthRatio() < KrasisTankHeal) || PartyMembers.Any(b => b.GetHealthRatio() < KrasisHeal))
+ foreach (var member in PartyMembers)
{
- if (KrasisPvE.CanUse(out act)) return true;
+ if (member.GetHealthRatio() < KrasisHeal)
+ {
+ if (KrasisPvE.CanUse(out act)) return true;
+ }
}
return base.HealSingleAbility(nextGCD, out act);
@@ -246,14 +262,9 @@ protected override bool GeneralAbility(IAction nextGCD, out IAction? act)
// Finally, updates the current Eukrasia action aim if it's different from the incoming action.
private void SetEukrasia(IBaseAction act)
{
- if (act == null) return;
-
- if (_EukrasiaActionAim != null && IsLastGCD(true, _EukrasiaActionAim)) return;
+ if (act == null || (_EukrasiaActionAim != null && IsLastGCD(true, _EukrasiaActionAim))) return;
- if (_EukrasiaActionAim != act)
- {
- _EukrasiaActionAim = act;
- }
+ _EukrasiaActionAim = act;
}
// Clears the Eukrasia action aim, effectively resetting any planned Eukrasia action.
@@ -273,69 +284,45 @@ private bool ChoiceEukrasia(out IAction? act)
if (!EukrasiaPvE.CanUse(out _)) return false;
// Checks for Eukrasia status.
- // Attempts to set correct Eurkrasia action based on availablity and MergedStatus.
+ // Attempts to set correct Eurkrasia action based on availability and MergedStatus.
if (EukrasianPrognosisIiPvE.CanUse(out _) && EukrasianPrognosisIiPvE.EnoughLevel && MergedStatus.HasFlag(AutoStatus.DefenseArea))
{
SetEukrasia(EukrasianPrognosisIiPvE);
- return false;
}
-
- if (EukrasianPrognosisPvE.CanUse(out _) && EukrasianPrognosisPvE.EnoughLevel && MergedStatus.HasFlag(AutoStatus.DefenseArea))
+ else if (EukrasianPrognosisPvE.CanUse(out _) && EukrasianPrognosisPvE.EnoughLevel && MergedStatus.HasFlag(AutoStatus.DefenseArea))
{
SetEukrasia(EukrasianPrognosisPvE);
- return false;
}
-
- if (EukrasianDiagnosisPvE.CanUse(out _) && EukrasianDiagnosisPvE.EnoughLevel && MergedStatus.HasFlag(AutoStatus.DefenseSingle))
+ else if (EukrasianDiagnosisPvE.CanUse(out _) && EukrasianDiagnosisPvE.EnoughLevel && MergedStatus.HasFlag(AutoStatus.DefenseSingle))
{
SetEukrasia(EukrasianDiagnosisPvE);
- return false;
}
-
- if (EukrasianDyskrasiaPvE.CanUse(out _) && EukrasianDyskrasiaPvE.EnoughLevel && (!MergedStatus.HasFlag(AutoStatus.DefenseSingle) || !MergedStatus.HasFlag(AutoStatus.DefenseArea)))
+ else if (EukrasianDyskrasiaPvE.CanUse(out _) && EukrasianDyskrasiaPvE.EnoughLevel && (!MergedStatus.HasFlag(AutoStatus.DefenseSingle) || !MergedStatus.HasFlag(AutoStatus.DefenseArea)))
{
SetEukrasia(EukrasianDyskrasiaPvE);
- return false;
}
-
- if ((!EukrasianDyskrasiaPvE.CanUse(out _) || !DyskrasiaPvE.CanUse(out _)) && EukrasianDosisIiiPvE.CanUse(out _) && EukrasianDosisIiiPvE.EnoughLevel && (!MergedStatus.HasFlag(AutoStatus.DefenseSingle) || !MergedStatus.HasFlag(AutoStatus.DefenseArea)))
+ else 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 ((!EukrasianDyskrasiaPvE.CanUse(out _) || !DyskrasiaPvE.CanUse(out _)) && EukrasianDosisIiPvE.CanUse(out _) && EukrasianDosisIiPvE.EnoughLevel && (!MergedStatus.HasFlag(AutoStatus.DefenseSingle) || !MergedStatus.HasFlag(AutoStatus.DefenseArea)))
+ else 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 ((!EukrasianDyskrasiaPvE.CanUse(out _) || !DyskrasiaPvE.CanUse(out _)) && EukrasianDosisPvE.CanUse(out _) && EukrasianDosisPvE.EnoughLevel && (!MergedStatus.HasFlag(AutoStatus.DefenseSingle) || !MergedStatus.HasFlag(AutoStatus.DefenseArea)))
+ else if ((!EukrasianDyskrasiaPvE.CanUse(out _) || !DyskrasiaPvE.CanUse(out _)) && EukrasianDosisPvE.CanUse(out _) && EukrasianDosisPvE.EnoughLevel && (!MergedStatus.HasFlag(AutoStatus.DefenseSingle) || !MergedStatus.HasFlag(AutoStatus.DefenseArea)))
{
SetEukrasia(EukrasianDosisPvE);
- return false;
}
-
- return false; // Indicates that no specific Eukrasia action was chosen in this cycle.
- }
- #endregion
-
- #region Eukrasia Execution
- // Attempts to perform a Eukrasia action, based on the current game state and conditions.
- private bool DoEukrasia(out IAction? act)
- {
- act = null;
-
- if (_EukrasiaActionAim != null && _EukrasiaActionAim.CanUse(out act))
+ else
{
- if (EukrasiaPvE.CanUse(out act)) return true;
-
- act = _EukrasiaActionAim;
- return true;
+ return false; // Indicates that no specific Eukrasia action was chosen in this cycle.
}
+
return false;
}
+ #endregion
+ #region Eukrasia Execution
// Attempts to perform a Eukrasia action, based on the current game state and conditions.
private bool DoEukrasianPrognosis(out IAction? act)
{
@@ -402,7 +389,7 @@ private bool DoEukrasianDosis(out IAction? act)
protected override bool HealAreaGCD(out IAction? act)
{
act = null;
- if (HasSwift && SwiftLogic && MergedStatus.HasFlag(AutoStatus.Raise)) return false;
+ if (IsLastAction(ActionID.SwiftcastPvE) && SwiftLogic && MergedStatus.HasFlag(AutoStatus.Raise)) return false;
if (PartyMembersAverHP < PneumaAOEPartyHeal || DyskrasiaPvE.CanUse(out _) && PartyMembers.GetJobCategory(JobRole.Tank).Any(t => t.GetHealthRatio() < PneumaAOETankHeal))
{
@@ -421,8 +408,7 @@ protected override bool HealAreaGCD(out IAction? act)
protected override bool HealSingleGCD(out IAction? act)
{
act = null;
- if (HasSwift && SwiftLogic && MergedStatus.HasFlag(AutoStatus.Raise)) return false;
-
+ if (IsLastAction(ActionID.SwiftcastPvE) && SwiftLogic && MergedStatus.HasFlag(AutoStatus.Raise)) return false;
if (_EukrasiaActionAim != null && DiagnosisPvE.CanUse(out act))
{
return true;
@@ -433,27 +419,37 @@ protected override bool HealSingleGCD(out IAction? act)
protected override bool GeneralGCD(out IAction? act)
{
act = null;
- if (HasSwift && SwiftLogic && MergedStatus.HasFlag(AutoStatus.Raise)) return false;
+ if (IsLastAction(ActionID.SwiftcastPvE) && SwiftLogic && MergedStatus.HasFlag(AutoStatus.Raise)) return false;
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))
+ foreach (var member in PartyMembers)
{
- if (PneumaPvE.CanUse(out act)) return true;
+ if (member.GetHealthRatio() < PneumaSTPartyHeal && !member.IsDead)
+ {
+ if (PneumaPvE.CanUse(out act)) return true;
+ }
+ }
+
+ foreach (var tank in PartyMembers.GetJobCategory(JobRole.Tank))
+ {
+ if (tank.GetHealthRatio() < PneumaSTTankHeal && !tank.IsDead)
+ {
+ if (PneumaPvE.CanUse(out act)) return true;
+ }
}
if (IsMoving && ToxikonPvE.CanUse(out act)) return true;
- if (NewELogic && DoEukrasianDyskrasia(out act)) return true;
+ if (DoEukrasianDyskrasia(out act)) return true;
- if ((_EukrasiaActionAim != EukrasianDiagnosisPvE || _EukrasiaActionAim != EukrasianPrognosisPvE || _EukrasiaActionAim != EukrasianPrognosisIiPvE || _EukrasiaActionAim != EukrasianDyskrasiaPvE)
+ if ((_EukrasiaActionAim != EukrasianDiagnosisPvE || _EukrasiaActionAim != EukrasianPrognosisPvE || _EukrasiaActionAim != EukrasianPrognosisIiPvE || _EukrasiaActionAim != EukrasianDyskrasiaPvE)
&& DyskrasiaPvE.CanUse(out act)) return true;
- if (NewELogic && DoEukrasianPrognosis(out act)) return true;
- if (NewELogic && DoEukrasianDiagnosis(out act)) return true;
- if (!NewELogic && DoEukrasia(out act)) return true;
+ if (DoEukrasianPrognosis(out act)) return true;
+ if (DoEukrasianDiagnosis(out act)) return true;
- if (NewELogic && DoEukrasianDosis(out act)) return true;
+ if ( DoEukrasianDosis(out act)) return true;
if (DosisPvE.CanUse(out act)) return true;
if (!InCombat && !Player.HasStatus(true, StatusID.Eukrasia) && EukrasiaPvE.CanUse(out act)) return true;
diff --git a/BasicRotations/Healer/WHM_Default.cs b/BasicRotations/Healer/WHM_Default.cs
index 2530725bd..7faecc9fc 100644
--- a/BasicRotations/Healer/WHM_Default.cs
+++ b/BasicRotations/Healer/WHM_Default.cs
@@ -170,7 +170,7 @@ protected override bool AttackAbility(IAction nextGCD, out IAction? act)
protected override bool HealAreaGCD(out IAction? act)
{
act = null;
- if (HasSwift && SwiftLogic && MergedStatus.HasFlag(AutoStatus.Raise)) return false;
+ if ((HasSwift || IsLastAction(ActionID.SwiftcastPvE)) && SwiftLogic && MergedStatus.HasFlag(AutoStatus.Raise)) return false;
if (AfflatusRapturePvE.CanUse(out act)) return true;
@@ -189,7 +189,7 @@ protected override bool HealAreaGCD(out IAction? act)
protected override bool HealSingleGCD(out IAction? act)
{
act = null;
- if (HasSwift && SwiftLogic && MergedStatus.HasFlag(AutoStatus.Raise)) return false;
+ if ((HasSwift || IsLastAction(ActionID.SwiftcastPvE)) && SwiftLogic && MergedStatus.HasFlag(AutoStatus.Raise)) return false;
if (AfflatusSolacePvE.CanUse(out act)) return true;
@@ -205,7 +205,7 @@ protected override bool HealSingleGCD(out IAction? act)
protected override bool GeneralGCD(out IAction? act)
{
act = null;
- if (HasSwift && SwiftLogic && MergedStatus.HasFlag(AutoStatus.Raise)) return false;
+ if ((HasSwift || IsLastAction(ActionID.SwiftcastPvE)) && SwiftLogic && MergedStatus.HasFlag(AutoStatus.Raise)) return false;
//if (NotInCombatDelay && RegenDefense.CanUse(out act)) return true;
diff --git a/ECommons b/ECommons
index 3de0ce6a4..2c01433db 160000
--- a/ECommons
+++ b/ECommons
@@ -1 +1 @@
-Subproject commit 3de0ce6a4acb79e058a2ab09ccbc9113c8b15bfc
+Subproject commit 2c01433dba269cd7624b88e86cc8b74f904b34c7
diff --git a/RotationSolver.Basic/Actions/BaseAction.cs b/RotationSolver.Basic/Actions/BaseAction.cs
index c1d0ddd95..1f2608fd7 100644
--- a/RotationSolver.Basic/Actions/BaseAction.cs
+++ b/RotationSolver.Basic/Actions/BaseAction.cs
@@ -142,9 +142,6 @@ public bool CanUse(out IAction act, bool isLastAbility = false, bool isFirstAbil
usedUp = true;
}
- if (isLastAbility && !IsLastAbilityUsable()) return false;
- if (isFirstAbility && !IsFirstAbilityUsable()) return false;
-
if (!Info.BasicCheck(skipStatusProvideCheck, skipComboCheck, skipCastingCheck)) return false;
if (!Cooldown.CooldownCheck(usedUp, gcdCountForAbility)) return false;
@@ -167,34 +164,6 @@ public bool CanUse(out IAction act, bool isLastAbility = false, bool isFirstAbil
return true;
}
- private bool IsLastAbilityUsable()
- {
- if (Service.Config.UseV2AbilityChecks)
- {
- return IsLastAbilityv2Usable();
- }
- return DataCenter.InCombat && (DataCenter.NextAbilityToNextGCD <= Math.Max(ActionManagerHelper.GetCurrentAnimationLock(), DataCenter.MinAnimationLock) + Service.Config.IsLastAbilityTimer);
- }
-
- private bool IsFirstAbilityUsable()
- {
- if (Service.Config.UseV2AbilityChecks)
- {
- return IsFirstAbilityv2Usable();
- }
- return DataCenter.InCombat && (DataCenter.NextAbilityToNextGCD >= Math.Max(ActionManagerHelper.GetCurrentAnimationLock(), DataCenter.MinAnimationLock) + Service.Config.IsFirstAbilityTimer);
- }
-
- private bool IsLastAbilityv2Usable()
- {
- return DataCenter.InCombat && (DataCenter.DefaultGCDElapsed >= DataCenter.DefaultGCDRemain);
- }
-
- private bool IsFirstAbilityv2Usable()
- {
- return DataCenter.InCombat && (DataCenter.DefaultGCDRemain >= DataCenter.DefaultGCDElapsed);
- }
-
private bool IsTimeToKillValid()
{
return DataCenter.AverageTimeToKill >= Config.TimeToKill && DataCenter.AverageTimeToKill >= Config.TimeToUntargetable;
diff --git a/RotationSolver.Basic/Configuration/Configs.cs b/RotationSolver.Basic/Configuration/Configs.cs
index 341395275..aab446155 100644
--- a/RotationSolver.Basic/Configuration/Configs.cs
+++ b/RotationSolver.Basic/Configuration/Configs.cs
@@ -347,10 +347,6 @@ public const string
Filter = Extra)]
private static readonly bool _autoOpenChest = true;
- [ConditionBool, UI("Use experimental FirstAbility and LastAbility checks",
- Filter = Extra)]
- private static readonly bool _useV2AbilityChecks = false;
-
[ConditionBool, UI("Enable RSR click counter in main menu",
Filter = Extra)]
private static readonly bool _enableClickingCount = true;
@@ -445,6 +441,22 @@ public const string
PvEFilter = JobFilterType.Raise, PvPFilter = JobFilterType.NoJob)]
private static readonly bool _raiseBrinkOfDeath = true;
+ [JobConfig, UI("Raise non-Healers from bottom of party list to the top (Light Party 2 Healer Behaviour, Experimental)",
+ Filter = HealingActionCondition, Section = 2,
+ PvEFilter = JobFilterType.Raise, PvPFilter = JobFilterType.NoJob)]
+ private static readonly bool _h2 = false;
+
+ [JobConfig, UI("Raise Red Mage and Summoners first if no Tanks or Healers are dead (Experimental)",
+ Filter = HealingActionCondition, Section = 2,
+ PvEFilter = JobFilterType.Raise, PvPFilter = JobFilterType.NoJob)]
+ private static readonly bool _offRaiserRaise = false;
+
+ [JobConfig, UI("How early before next GCD should RSR use swiftcast for raise (Experimental)",
+ Filter = HealingActionCondition, Section = 2,
+ PvEFilter = JobFilterType.Raise, PvPFilter = JobFilterType.NoJob)]
+ [Range(0, 1.0f, ConfigUnitType.Seconds, 0.01f)]
+ public float SwiftcastBuffer { get; set; } = 0.6f;
+
[UI("Random delay range for resurrecting players.",
Filter = HealingActionCondition, Section = 2,
PvEFilter = JobFilterType.Raise, PvPFilter = JobFilterType.NoJob)]
@@ -706,7 +718,7 @@ public const string
[UI("Action Ahead (How far in advance of GCD being available RSR will try to queue the next GCD)",
Description = "This setting controls how many oGCDs RSR will try to fit in a single GCD window\nLower numbers mean more oGCDs, but potentially more GCD clipping",
Parent = nameof(OverrideActionAheadTimer))]
- private readonly float _action4head = 0.4f;
+ private readonly float _action4head = 0.3f;
[JobConfig]
private readonly string _PvPRotationChoice = string.Empty;
diff --git a/RotationSolver.Basic/DataCenter.cs b/RotationSolver.Basic/DataCenter.cs
index f93b4c629..9b6e6b06e 100644
--- a/RotationSolver.Basic/DataCenter.cs
+++ b/RotationSolver.Basic/DataCenter.cs
@@ -244,37 +244,46 @@ public static unsafe ushort FateId
}
#region GCD
- // Returns the time remaining until the next GCD (Global Cooldown) after considering the current animation lock.
- public static float NextAbilityToNextGCD => DefaultGCDRemain - Math.Max(ActionManagerHelper.GetCurrentAnimationLock(), DataCenter.MinAnimationLock);
+ ///
+ /// Returns the time remaining until the next GCD (Global Cooldown) after considering the current animation lock.
+ ///
+ public static float NextAbilityToNextGCD => DefaultGCDRemain - Math.Min(ActionManagerHelper.GetCurrentAnimationLock(), MinAnimationLock);
- // Returns the total duration of the default GCD.
+ ///
+ /// Returns the total duration of the default GCD.
+ ///
public static float DefaultGCDTotal => ActionManagerHelper.GetDefaultRecastTime();
- // Returns the remaining time for the default GCD by subtracting the elapsed time from the total recast time.
- public static float DefaultGCDRemain =>
- ActionManagerHelper.GetDefaultRecastTime() - ActionManagerHelper.GetDefaultRecastTimeElapsed();
+ ///
+ /// Returns the remaining time for the default GCD by subtracting the elapsed time from the total recast time.
+ ///
+ public static float DefaultGCDRemain => DefaultGCDTotal - DefaultGCDElapsed;
- // Returns the elapsed time since the start of the default GCD.
+ ///
+ /// Returns the elapsed time since the start of the default GCD.
+ ///
public static float DefaultGCDElapsed => ActionManagerHelper.GetDefaultRecastTimeElapsed();
- // Returns the action ahead time, which can be overridden by a configuration setting.
- public static float ActionAhead =>
- Service.Config.OverrideActionAheadTimer ? Service.Config.Action4Head : CalculatedActionAhead;
-
- // Returns the calculated action ahead time as 25% of the total GCD time.
- public static float CalculatedActionAhead => Math.Min(DefaultGCDTotal * 0.25f, DataCenter.MinAnimationLock);
-
- // Calculates the total GCD time for a given number of GCDs and an optional offset.
- public static float GCDTime(uint gcdCount = 0, float offset = 0)
- => ActionManagerHelper.GetDefaultRecastTime() * gcdCount + offset;
+ ///
+ /// Returns the action ahead time, which can be overridden by a configuration setting.
+ ///
+ public static float ActionAhead => Service.Config.OverrideActionAheadTimer ? Service.Config.Action4Head : CalculatedActionAhead;
- public static bool LastAbilityv2 => DataCenter.InCombat && !ActionHelper.CanUseGCD && (ActionManagerHelper.GetCurrentAnimationLock() == 0) && !Player.Object.IsCasting && (DataCenter.DefaultGCDElapsed >= DataCenter.DefaultGCDRemain);
- public static bool FirstAbilityv2 => DataCenter.InCombat && !ActionHelper.CanUseGCD && (ActionManagerHelper.GetCurrentAnimationLock() == 0) && !Player.Object.IsCasting && (DataCenter.DefaultGCDRemain >= DataCenter.DefaultGCDElapsed);
+ ///
+ /// Calculates the action ahead time based on the default GCD total and minimum animation lock.
+ ///
+ public static float CalculatedActionAhead => Math.Min(DefaultGCDTotal * 0.20f, MinAnimationLock);
- public static bool LastAbilityorNot => DataCenter.InCombat && (DataCenter.NextAbilityToNextGCD <= Math.Max(ActionManagerHelper.GetCurrentAnimationLock(), DataCenter.MinAnimationLock) + Service.Config.IsLastAbilityTimer);
- public static bool FirstAbilityorNot => DataCenter.InCombat && (DataCenter.NextAbilityToNextGCD >= Math.Max(ActionManagerHelper.GetCurrentAnimationLock(), DataCenter.MinAnimationLock) + Service.Config.IsFirstAbilityTimer);
+ ///
+ /// Calculates the total GCD time for a given number of GCDs and an optional offset.
+ ///
+ /// The number of GCDs.
+ /// The optional offset.
+ /// The total GCD time.
+ public static float GCDTime(uint gcdCount = 0, float offset = 0) => DefaultGCDTotal * gcdCount + offset;
#endregion
+
public static uint[] BluSlots { get; internal set; } = new uint[24];
public static uint[] DutyActions { get; internal set; } = new uint[2];
diff --git a/RotationSolver.Basic/Helpers/StatusHelper.cs b/RotationSolver.Basic/Helpers/StatusHelper.cs
index 4c1ab3e9f..063af48de 100644
--- a/RotationSolver.Basic/Helpers/StatusHelper.cs
+++ b/RotationSolver.Basic/Helpers/StatusHelper.cs
@@ -206,12 +206,19 @@ internal static IEnumerable StatusTimes(this IGameObject obj, bool isFrom
{
if (obj == null)
{
- // Log or handle the case where obj is null
PluginLog.Error("IGameObject is null. Cannot get status times.");
return Enumerable.Empty();
}
- return obj.GetStatus(isFromSelf, statusIDs).Select(status => status.RemainingTime == 0 ? float.MaxValue : status.RemainingTime);
+ var statuses = obj.GetStatus(isFromSelf, statusIDs);
+ var result = new List();
+
+ foreach (var status in statuses)
+ {
+ result.Add(status.RemainingTime == 0 ? float.MaxValue : status.RemainingTime);
+ }
+
+ return result;
}
///
@@ -240,12 +247,19 @@ private static IEnumerable StatusStacks(this IGameObject obj, bool isFromS
{
if (obj == null)
{
- // Log or handle the case where obj is null
PluginLog.Error("IGameObject is null. Cannot get status stacks.");
return Enumerable.Empty();
}
- return obj.GetStatus(isFromSelf, statusIDs).Select(status => status.StackCount == 0 ? byte.MaxValue : status.StackCount);
+ var statuses = obj.GetStatus(isFromSelf, statusIDs);
+ var result = new List();
+
+ foreach (var status in statuses)
+ {
+ result.Add(status.StackCount == 0 ? byte.MaxValue : status.StackCount);
+ }
+
+ return result;
}
///
@@ -323,10 +337,19 @@ internal static string GetStatusName(StatusID id)
/// An enumerable of statuses.
private static IEnumerable GetStatus(this IGameObject obj, bool isFromSelf, params StatusID[] statusIDs)
{
- // Convert statusIDs to a HashSet for faster lookups
var newEffects = new HashSet(statusIDs.Select(a => (uint)a));
var allStatuses = obj.GetAllStatus(isFromSelf);
- return allStatuses.Where(status => newEffects.Contains(status.StatusId));
+ var result = new List();
+
+ foreach (var status in allStatuses)
+ {
+ if (newEffects.Contains(status.StatusId))
+ {
+ result.Add(status);
+ }
+ }
+
+ return result;
}
///
@@ -340,24 +363,28 @@ private static IEnumerable GetAllStatus(this IGameObject obj, bool isFro
if (obj is not IBattleChara b) return Enumerable.Empty();
var playerId = Player.Object?.GameObjectId ?? 0;
+ var result = new List();
try
{
- // Ensure b.StatusList is not null
if (b.StatusList == null)
{
PluginLog.Error("StatusList is null. Cannot get statuses.");
return Enumerable.Empty();
}
- return b.StatusList.Where(status => !isFromSelf
- || status.SourceId == playerId
- || status.SourceObject?.OwnerId == playerId)
- ?? Enumerable.Empty();
+ foreach (var status in b.StatusList)
+ {
+ if (!isFromSelf || status.SourceId == playerId || status.SourceObject?.OwnerId == playerId)
+ {
+ result.Add(status);
+ }
+ }
+
+ return result;
}
catch (Exception ex)
{
- // Log the exception
Svc.Log.Error($"Failed to get statuses: {ex.Message}");
return Enumerable.Empty();
}
diff --git a/RotationSolver.Basic/Helpers/TargetFilter.cs b/RotationSolver.Basic/Helpers/TargetFilter.cs
index b5b75964a..4937db902 100644
--- a/RotationSolver.Basic/Helpers/TargetFilter.cs
+++ b/RotationSolver.Basic/Helpers/TargetFilter.cs
@@ -1,6 +1,5 @@
using ECommons.ExcelServices;
using Lumina.Excel.Sheets;
-using System.Data;
namespace RotationSolver.Basic.Helpers;
@@ -19,13 +18,15 @@ public static IEnumerable GetDeath(this IEnumerable
{
if (charas == null) return Enumerable.Empty();
- return charas.Where(item =>
+ var result = new List();
+ foreach (var item in charas)
{
- 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;
- return true;
- });
+ if (item == null || !item.IsDead || item.CurrentHp != 0 || !item.IsTargetable) continue;
+ if (item.HasStatus(false, StatusID.Raise)) continue;
+ if (!Service.Config.RaiseBrinkOfDeath && item.HasStatus(false, StatusID.BrinkOfDeath)) continue;
+ result.Add(item);
+ }
+ return result;
}
///
@@ -38,11 +39,30 @@ public static IEnumerable GetJobCategory(this IEnumerable();
- var validJobs = new HashSet(roles.SelectMany(role => Service.GetSheet()
- .Where(job => role == job.GetJobRole())
- .Select(job => (byte)job.RowId)));
+ var validJobs = new HashSet();
+ var classJobs = Service.GetSheet();
- return objects.Where(obj => obj.IsJobs(validJobs));
+ foreach (var role in roles)
+ {
+ foreach (var job in classJobs)
+ {
+ if (role == job.GetJobRole())
+ {
+ validJobs.Add((byte)job.RowId);
+ }
+ }
+ }
+
+ var result = new List();
+ foreach (var obj in objects)
+ {
+ if (obj.IsJobs(validJobs))
+ {
+ result.Add(obj);
+ }
+ }
+
+ return result;
}
///
@@ -55,9 +75,16 @@ public static bool IsJobCategory(this IGameObject obj, JobRole role)
{
if (obj == null) return false;
- var validJobs = new HashSet(Service.GetSheet()
- .Where(job => role == job.GetJobRole())
- .Select(job => (byte)job.RowId));
+ var validJobs = new HashSet();
+ var classJobs = Service.GetSheet();
+
+ foreach (var job in classJobs)
+ {
+ if (role == job.GetJobRole())
+ {
+ validJobs.Add((byte)job.RowId);
+ }
+ }
return obj.IsJobs(validJobs);
}
@@ -72,7 +99,13 @@ public static bool IsJobs(this IGameObject obj, params Job[] validJobs)
{
if (obj == null || validJobs == null || validJobs.Length == 0) return false;
- return obj.IsJobs(new HashSet(validJobs.Select(j => (byte)(uint)j)));
+ var validJobSet = new HashSet();
+ foreach (var job in validJobs)
+ {
+ validJobSet.Add((byte)(uint)job);
+ }
+
+ return obj.IsJobs(validJobSet);
}
private static bool IsJobs(this IGameObject obj, HashSet validJobs)
@@ -93,6 +126,14 @@ public static IEnumerable GetObjectInRadius(this IEnumerable objects, f
{
if (objects == null) return Enumerable.Empty();
- return objects.Where(o => o.DistanceToPlayer() <= radius);
+ var result = new List();
+ foreach (var obj in objects)
+ {
+ if (obj.DistanceToPlayer() <= radius)
+ {
+ result.Add(obj);
+ }
+ }
+ return result;
}
}
\ No newline at end of file
diff --git a/RotationSolver.Basic/Rotations/CustomRotation_Ability.cs b/RotationSolver.Basic/Rotations/CustomRotation_Ability.cs
index 55e853131..0a540dfb9 100644
--- a/RotationSolver.Basic/Rotations/CustomRotation_Ability.cs
+++ b/RotationSolver.Basic/Rotations/CustomRotation_Ability.cs
@@ -410,7 +410,7 @@ protected virtual bool EmergencyAbility(IAction nextGCD, out IAction? act)
if (nextGCD is BaseAction action)
{
if (Role is JobRole.RangedMagical &&
- action.Info.CastTime >= 5 && SwiftcastPvE.CanUse(out act, isFirstAbility: true))
+ action.Info.CastTime >= 5 && IActionHelper.IsLastActionGCD() && SwiftcastPvE.CanUse(out act))
{
return true;
}
@@ -418,7 +418,7 @@ protected virtual bool EmergencyAbility(IAction nextGCD, out IAction? act)
if (DataCenter.CommandStatus.HasFlag(AutoStatus.Raise))
{
- if (Role is JobRole.Healer && nextGCD.IsTheSameTo(true, ActionID.RaisePvE, ActionID.EgeiroPvE, ActionID.ResurrectionPvE, ActionID.AscendPvE) && SwiftcastPvE.CanUse(out act))
+ if (Role is JobRole.Healer && IActionHelper.IsLastActionGCD() && (DataCenter.DefaultGCDRemain > Service.Config.SwiftcastBuffer) && nextGCD.IsTheSameTo(true, ActionID.RaisePvE, ActionID.EgeiroPvE, ActionID.ResurrectionPvE, ActionID.AscendPvE) && SwiftcastPvE.CanUse(out act))
{
return true;
}
diff --git a/RotationSolver.Basic/Rotations/CustomRotation_GCD.cs b/RotationSolver.Basic/Rotations/CustomRotation_GCD.cs
index 773396c65..48fe62035 100644
--- a/RotationSolver.Basic/Rotations/CustomRotation_GCD.cs
+++ b/RotationSolver.Basic/Rotations/CustomRotation_GCD.cs
@@ -113,9 +113,18 @@ partial class CustomRotation
{
IBaseAction.TargetOverride = TargetType.Heal;
- if (DataCenter.PartyMembersDifferHP < Service.Config.HealthDifference
- && DataCenter.PartyMembersHP.Count(i => i < 1) > 2
- && HealAreaGCD(out act)) return act;
+ if (DataCenter.PartyMembersDifferHP < Service.Config.HealthDifference)
+ {
+ int count = 0;
+ foreach (var hp in DataCenter.PartyMembersHP)
+ {
+ if (hp < 1)
+ {
+ count++;
+ }
+ }
+ if (count > 2 && HealAreaGCD(out act)) return act;
+ }
if (HealSingleGCD(out act)) return act;
IBaseAction.TargetOverride = null;
@@ -139,18 +148,18 @@ partial class CustomRotation
}
- private bool UseLimitBreak(out IAction? act)
- {
- act = null;
+ //private bool UseLimitBreak(out IAction? act)
+ //{
+ // act = null;
- return LimitBreakLevel switch
- {
- 1 => (DataCenter.IsPvP ? LimitBreakPvP?.CanUse(out act, skipAoeCheck: true) : LimitBreak1?.CanUse(out act, skipAoeCheck: true)) ?? false,
- 2 => LimitBreak2?.CanUse(out act, skipAoeCheck: true) ?? false,
- 3 => LimitBreak3?.CanUse(out act, skipAoeCheck: true) ?? false,
- _ => false,
- };
- }
+ // return LimitBreakLevel switch
+ // {
+ // 1 => (DataCenter.IsPvP ? LimitBreakPvP?.CanUse(out act, skipAoeCheck: true) : LimitBreak1?.CanUse(out act, skipAoeCheck: true)) ?? false,
+ // 2 => LimitBreak2?.CanUse(out act, skipAoeCheck: true) ?? false,
+ // 3 => LimitBreak3?.CanUse(out act, skipAoeCheck: true) ?? false,
+ // _ => false,
+ // };
+ //}
private bool RaiseSpell(out IAction? act, bool mustUse)
{
@@ -169,7 +178,7 @@ private bool RaiseSpell(out IAction? act, bool mustUse)
if (RaiseAction(out act, true))
{
- if (HasSwift) return true;
+ if (HasSwift || IsLastAction(ActionID.SwiftcastPvE)) return true;
if (Service.Config.RaisePlayerBySwift && !SwiftcastPvE.Cooldown.IsCoolingDown && SwiftcastPvE.CanUse(out act))
{
@@ -291,6 +300,8 @@ protected virtual bool MoveForwardGCD(out IAction? act)
[RotationDesc(DescType.HealSingleGCD)]
protected virtual bool HealSingleGCD(out IAction? act)
{
+ act = null;
+ if (ShouldSkipAction()) return false;
if (DataCenter.CommandStatus.HasFlag(AutoStatus.HealSingleSpell))
{
IBaseAction.ShouldEndSpecial = true;
@@ -369,20 +380,18 @@ protected virtual bool DefenseAreaGCD(out IAction? act)
protected virtual bool GeneralGCD(out IAction? act)
{
act = null;
-
+ if (ShouldSkipAction()) return false;
if (DataCenter.MergedStatus.HasFlag(AutoStatus.NoCasting))
{
return false;
}
- if (ShouldSkipAction()) return false;
-
if (DataCenter.RightNowDutyRotation?.GeneralGCD(out act) ?? false) return true;
act = null; return false;
}
private bool ShouldSkipAction()
{
- return DataCenter.CommandStatus.HasFlag(AutoStatus.Raise) && Role is JobRole.Healer && HasSwift;
+ return DataCenter.CommandStatus.HasFlag(AutoStatus.Raise) && Role is JobRole.Healer && (HasSwift || IsLastAction(ActionID.SwiftcastPvE));
}
}
diff --git a/RotationSolver/UI/RotationConfigWindow.cs b/RotationSolver/UI/RotationConfigWindow.cs
index f57eb160c..3fef4a70d 100644
--- a/RotationSolver/UI/RotationConfigWindow.cs
+++ b/RotationSolver/UI/RotationConfigWindow.cs
@@ -2952,10 +2952,6 @@ private static void DrawNextAction()
ImGui.Text(DataCenter.SpecialType.ToString());
ImGui.Text(ActionUpdater.NextAction?.Name ?? "null");
- ImGui.Text($"LastAbilityorNot: {DataCenter.LastAbilityorNot}");
- ImGui.Text($"FirstAbilityorNot: {DataCenter.FirstAbilityorNot}");
- ImGui.Text($"LastAbilityv2: {DataCenter.LastAbilityv2}");
- ImGui.Text($"FirstAbilityv2: {DataCenter.FirstAbilityv2}");
ImGui.Text($"GCD Total: {DataCenter.DefaultGCDTotal}");
ImGui.Text($"GCD Remain: {DataCenter.DefaultGCDRemain}");
ImGui.Text($"GCD Elapsed: {DataCenter.DefaultGCDElapsed}");
@@ -2990,32 +2986,6 @@ private static void DrawGCDCooldownStuff()
ImGui.Text($"GCD Remain: {DataCenter.DefaultGCDRemain}");
ImGui.Text($"GCD Total: {DataCenter.DefaultGCDTotal}");
- // Change text color based on LastAbilityv2
- if (DataCenter.LastAbilityv2)
- {
- ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.ParsedGreen);
- ImGui.Text("LastAbilityv2: true");
- }
- else
- {
- ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed);
- ImGui.Text("LastAbilityv2: false");
- }
- ImGui.PopStyleColor();
-
- // Change text color based on FirstAbilityv2
- if (DataCenter.FirstAbilityv2)
- {
- ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.ParsedGreen);
- ImGui.Text("FirstAbilityv2: true");
- }
- else
- {
- ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed);
- ImGui.Text("FirstAbilityv2: false");
- }
- ImGui.PopStyleColor();
-
// Visualize the GCD and oGCD slots
float gcdTotal = DataCenter.DefaultGCDElapsed + DataCenter.DefaultGCDRemain;
float gcdProgress = DataCenter.DefaultGCDElapsed / gcdTotal;
diff --git a/RotationSolver/Updaters/MajorUpdater.cs b/RotationSolver/Updaters/MajorUpdater.cs
index 8e1014bbd..2543f5e49 100644
--- a/RotationSolver/Updaters/MajorUpdater.cs
+++ b/RotationSolver/Updaters/MajorUpdater.cs
@@ -186,18 +186,21 @@ private static void UpdateWork()
private static void RemoveExpiredVfxData()
{
var expiredVfx = new List();
- for (int i = 0; i < DataCenter.VfxDataQueue.Count; i++)
+ lock (DataCenter.VfxDataQueue)
{
- var vfx = DataCenter.VfxDataQueue[i];
- if (vfx.TimeDuration > TimeSpan.FromSeconds(10))
+ for (int i = 0; i < DataCenter.VfxDataQueue.Count; i++)
{
- expiredVfx.Add(vfx);
+ var vfx = DataCenter.VfxDataQueue[i];
+ if (vfx.TimeDuration > TimeSpan.FromSeconds(10))
+ {
+ expiredVfx.Add(vfx);
+ }
}
- }
- foreach (var vfx in expiredVfx)
- {
- DataCenter.VfxDataQueue.Remove(vfx);
+ foreach (var vfx in expiredVfx)
+ {
+ DataCenter.VfxDataQueue.Remove(vfx);
+ }
}
}
diff --git a/RotationSolver/Updaters/TargetUpdater.cs b/RotationSolver/Updaters/TargetUpdater.cs
index 3c3220853..2b717f889 100644
--- a/RotationSolver/Updaters/TargetUpdater.cs
+++ b/RotationSolver/Updaters/TargetUpdater.cs
@@ -194,15 +194,30 @@ private static List GetAllHostileTargets()
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 deathAllianceHealers = new List();
+ var deathAllianceSupports = new List();
+
+ if (DataCenter.AllianceMembers != null)
+ {
+ foreach (var member in DataCenter.AllianceMembers)
+ {
+ if (member.IsJobCategory(JobRole.Healer))
+ {
+ deathAllianceHealers.Add(member);
+ }
+ if (member.IsJobCategory(JobRole.Healer) || member.IsJobCategory(JobRole.Tank))
+ {
+ deathAllianceSupports.Add(member);
+ }
+ }
+ }
+
+ var raisePartyAndAllianceSupports = new List(deathParty);
+ raisePartyAndAllianceSupports.AddRange(deathAllianceSupports);
+
+ var raisePartyAndAllianceHealers = new List(deathParty);
+ raisePartyAndAllianceHealers.AddRange(deathAllianceHealers);
+
var raisetype = Service.Config.RaiseType;
var validRaiseTargets = new List();
@@ -229,7 +244,7 @@ private static List GetAllHostileTargets()
validRaiseTargets.AddRange(deathNPC);
}
- foreach (var type in Enum.GetValues(typeof(RaiseType)).Cast())
+ foreach (RaiseType type in Enum.GetValues(typeof(RaiseType)))
{
var deathTarget = GetPriorityDeathTarget(validRaiseTargets, type);
if (deathTarget != null) return deathTarget;
@@ -249,6 +264,7 @@ private static List GetAllHostileTargets()
var deathTanks = new List();
var deathHealers = new List();
+ var deathOffHealers = new List();
var deathOthers = new List();
foreach (var chara in validRaiseTargets)
@@ -261,6 +277,10 @@ private static List GetAllHostileTargets()
{
deathHealers.Add(chara);
}
+ else if (Service.Config.OffRaiserRaise && chara.IsJobs(Job.SMN, Job.RDM))
+ {
+ deathOffHealers.Add(chara);
+ }
else
{
deathOthers.Add(chara);
@@ -272,9 +292,16 @@ private static List GetAllHostileTargets()
return deathHealers[0];
}
+ if (Service.Config.H2)
+ {
+ deathOffHealers.Reverse();
+ deathOthers.Reverse();
+ }
+
if (deathTanks.Count > 1) return deathTanks[0];
if (deathHealers.Count > 0) return deathHealers[0];
if (deathTanks.Count > 0) return deathTanks[0];
+ if (Service.Config.OffRaiserRaise && deathOffHealers.Count > 0) return deathOffHealers[0];
return deathOthers.Count > 0 ? deathOthers[0] : null;
}