From 392e094d0159b989a15d884265d33ea14367055b Mon Sep 17 00:00:00 2001 From: LTS-FFXIV <127939494+LTS-FFXIV@users.noreply.github.com> Date: Sun, 5 Jan 2025 11:43:55 -0600 Subject: [PATCH] Enhance Sage and Paladin rotations with new logic Updated SGE_Default to manage Eukrasia actions with new methods (ChoiceEukrasia, DoEukrasia) and streamlined existing logic. Added DisplayStatus method for status information. Enhanced PLD_Beta with new Clemency configurations, adjusted GeneralAbility and AttackAbility methods, and improved HealSingleGCD logic. Updated AoE and single-target logic for Holy Circle and Holy Spirit actions. Modified PaladinRotation to prevent Clemency targeting units with tank stance status. Minor update to SageRotation: removed AddersgallTimerRaw display and streamlined ModifyEukrasianDiagnosisPvE method. --- BasicRotations/Healer/SGE_Default.cs | 215 ++++++++---------- BasicRotations/Tank/PLD_Beta.cs | 49 +++- .../Rotations/Basic/PaladinRotation.cs | 5 + .../Rotations/Basic/SageRotation.cs | 3 +- 4 files changed, 137 insertions(+), 135 deletions(-) diff --git a/BasicRotations/Healer/SGE_Default.cs b/BasicRotations/Healer/SGE_Default.cs index 4a9cc0d4a..e5ef2b103 100644 --- a/BasicRotations/Healer/SGE_Default.cs +++ b/BasicRotations/Healer/SGE_Default.cs @@ -171,7 +171,6 @@ protected override bool HealSingleAbility(IAction nextGCD, out IAction? act) if (SoteriaPvE.CanUse(out act) && PartyMembers.Any(b => b.HasStatus(true, StatusID.Kardion) && b.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))) { @@ -201,7 +200,6 @@ protected override bool HealSingleAbility(IAction nextGCD, out IAction? act) } [RotationDesc(ActionID.EukrasianPrognosisPvE, ActionID.EukrasianPrognosisIiPvE)] - protected override bool GeneralAbility(IAction nextGCD, out IAction? act) { // If not in combat and lacking the Kardia status, attempt to use KardiaPvE @@ -219,69 +217,113 @@ protected override bool GeneralAbility(IAction nextGCD, out IAction? act) } #endregion - #region GCD Logic - protected override bool DefenseAreaGCD(out IAction? act) + #region Eukrasia Logic + private IBaseAction? _EukrasiaActionAim = null; + + // Sets the target Eukrasia action to be performed next. + // If the action is null, it exits early. + // If the current action aim is not null and the last action matches certain conditions, it exits early. + // 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(false, _EukrasiaActionAim)) return; + + if (_EukrasiaActionAim != act) + { + _EukrasiaActionAim = act; + } + } + + // Clears the Eukrasia action aim, effectively resetting any planned Eukrasia action. + private void ClearEukrasia() + { + if (_EukrasiaActionAim != null) + { + _EukrasiaActionAim = null; + } + } + + private bool ChoiceEukrasia(out IAction? act) { act = null; - if (HasSwift && SwiftLogic && EgeiroPvE.CanUse(out _)) return false; + 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)) + { + SetEukrasia(EukrasianPrognosisIiPvE); + return false; + } - if (EukrasianPrognosisIiPvE.CanUse(out act)) + if (EukrasianPrognosisPvE.CanUse(out _) && EukrasianPrognosisPvE.EnoughLevel && MergedStatus.HasFlag(AutoStatus.DefenseArea)) { - if (EukrasianPrognosisIiPvE.Target.Target?.HasStatus(false, - StatusID.EukrasianDiagnosis, - StatusID.EukrasianPrognosis, - StatusID.Galvanize - ) ?? false) return false; + SetEukrasia(EukrasianPrognosisPvE); + return false; + } - if (EukrasiaPvE.CanUse(out act)) return true; + if (EukrasianDiagnosisPvE.CanUse(out _) && EukrasianDiagnosisPvE.EnoughLevel && MergedStatus.HasFlag(AutoStatus.DefenseSingle)) + { + SetEukrasia(EukrasianDiagnosisPvE); + return false; + } - act = EukrasianPrognosisIiPvE; - return true; + if (EukrasianDyskrasiaPvE.CanUse(out _) && EukrasianDyskrasiaPvE.EnoughLevel && (!MergedStatus.HasFlag(AutoStatus.DefenseSingle) || !MergedStatus.HasFlag(AutoStatus.DefenseSingle))) + { + SetEukrasia(EukrasianDyskrasiaPvE); + return false; } - if (!EukrasianPrognosisIiPvE.EnoughLevel && EukrasianPrognosisPvE.CanUse(out act)) + if (EukrasianDosisIiiPvE.CanUse(out _) && EukrasianDosisIiiPvE.EnoughLevel && (!MergedStatus.HasFlag(AutoStatus.DefenseSingle) || !MergedStatus.HasFlag(AutoStatus.DefenseSingle))) { - if (EukrasianPrognosisPvE.Target.Target?.HasStatus(false, - StatusID.EukrasianDiagnosis, - StatusID.EukrasianPrognosis, - StatusID.Galvanize - ) ?? false) return false; + SetEukrasia(EukrasianDosisIiiPvE); + return false; + } - if (EukrasiaPvE.CanUse(out act)) return true; + if (EukrasianDosisIiPvE.CanUse(out _) && EukrasianDosisIiPvE.EnoughLevel && (!MergedStatus.HasFlag(AutoStatus.DefenseSingle) || !MergedStatus.HasFlag(AutoStatus.DefenseSingle))) + { + SetEukrasia(EukrasianDosisIiPvE); + return false; + } - act = EukrasianPrognosisPvE; - return true; + if (EukrasianDosisPvE.CanUse(out _) && EukrasianDosisPvE.EnoughLevel && (!MergedStatus.HasFlag(AutoStatus.DefenseSingle) || !MergedStatus.HasFlag(AutoStatus.DefenseSingle))) + { + SetEukrasia(EukrasianDosisPvE); + return false; } - return base.DefenseAreaGCD(out act); + // 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(); + } + return false; // Indicates that no specific Eukrasia action was chosen in this cycle. } + #endregion - [RotationDesc(ActionID.EukrasianDiagnosisPvE)] - protected override bool DefenseSingleGCD(out IAction? act) + #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 (HasSwift && SwiftLogic && EgeiroPvE.CanUse(out _)) return false; - - if (EukrasianDiagnosisPvE.CanUse(out act)) + if (_EukrasiaActionAim != null && _EukrasiaActionAim.CanUse(out act)) { - if (EukrasianDiagnosisPvE.Target.Target?.HasStatus(true, - StatusID.EukrasianDiagnosis, - StatusID.DifferentialDiagnosis, - StatusID.EukrasianPrognosis, - StatusID.Galvanize - ) ?? false) return false; - if (EukrasiaPvE.CanUse(out act)) return true; - act = EukrasianDiagnosisPvE; + act = _EukrasiaActionAim; return true; } - - return base.DefenseSingleGCD(out act); + return false; } + #endregion + #region GCD Logic [RotationDesc(ActionID.PneumaPvE, ActionID.PrognosisPvE, ActionID.EukrasianPrognosisPvE, ActionID.EukrasianPrognosisIiPvE)] protected override bool HealAreaGCD(out IAction? act) { @@ -339,49 +381,8 @@ protected override bool GeneralGCD(out IAction? act) if (HasSwift && SwiftLogic && EgeiroPvE.CanUse(out _)) return false; - if (EukrasianDiagnosisPvE.CanUse(out act) && MergedStatus.HasFlag(AutoStatus.DefenseSingle)) - { - if (EukrasianDiagnosisPvE.Target.Target?.HasStatus(true, - StatusID.EukrasianDiagnosis, - StatusID.DifferentialDiagnosis, - StatusID.EukrasianPrognosis, - StatusID.Galvanize - ) ?? false) return false; - - if (EukrasiaPvE.CanUse(out act)) return true; - - act = EukrasianDiagnosisPvE; - return true; - } - if (!InCombat && !Player.HasStatus(true, StatusID.Eukrasia) && EukrasiaPvE.CanUse(out act)) return true; - if (HostileTarget?.IsBossFromTTK() ?? false) - { - if (EukrasianDyskrasiaPvE.CanUse(out _, skipCastingCheck: true)) - { - if (EukrasiaPvE.CanUse(out act, skipCastingCheck: true)) return true; - if (EukrasianDyskrasiaPvE.CanUse(out act)) - { - DosisPvE.Target = EukrasianDyskrasiaPvE.Target; - return true; - } - } - } - - if (HostileTarget?.IsBossFromTTK() ?? false) - { - if (EukrasianDosisPvE.CanUse(out _, skipCastingCheck: true)) - { - if (EukrasiaPvE.CanUse(out act, skipCastingCheck: true)) return true; - if (DosisPvE.CanUse(out act)) - { - DosisPvE.Target = EukrasianDosisPvE.Target; - return true; - } - } - } - 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; @@ -393,49 +394,12 @@ protected override bool GeneralGCD(out IAction? act) if (IsMoving && ToxikonPvE.CanUse(out act)) return true; - if (EukrasianDyskrasiaPvE.CanUse(out _, skipCastingCheck: true)) - { - if (EukrasiaPvE.CanUse(out act, skipCastingCheck: true)) return true; - if (DyskrasiaPvE.CanUse(out act)) - { - DyskrasiaPvE.Target = EukrasianDyskrasiaPvE.Target; - return true; - } - } + if (ChoiceEukrasia(out act)) return true; + if (DoEukrasia(out act)) return true; - if (DyskrasiaPvE.CanUse(out _)) - { - if (EukrasianDyskrasiaPvE.EnoughLevel && (EukrasianDyskrasiaPvE.Target.Target?.WillStatusEnd(3, true, EukrasianDyskrasiaPvE.Setting.TargetStatusProvide ?? []) ?? false) && InCombat && (!MergedStatus.HasFlag(AutoStatus.DefenseArea) || !MergedStatus.HasFlag(AutoStatus.DefenseSingle))) - { - StatusHelper.StatusOff(StatusID.Eukrasia); - } - if (DyskrasiaPvE.CanUse(out act)) - { - return true; - } - } + if (DyskrasiaPvE.CanUse(out act)) return true; - if (EukrasianDosisPvE.CanUse(out _, skipCastingCheck: true)) - { - if (EukrasiaPvE.CanUse(out act, skipCastingCheck: true)) return true; - if (DosisPvE.CanUse(out act)) - { - DosisPvE.Target = EukrasianDosisPvE.Target; - return true; - } - } - - if (DosisPvE.CanUse(out _)) - { - if ((EukrasianDosisPvE.Target.Target?.WillStatusEnd(3, true, EukrasianDosisPvE.Setting.TargetStatusProvide ?? []) ?? false) && InCombat && (!MergedStatus.HasFlag(AutoStatus.DefenseArea) || !MergedStatus.HasFlag(AutoStatus.DefenseSingle))) - { - StatusHelper.StatusOff(StatusID.Eukrasia); - } - if (DosisPvE.CanUse(out act)) - { - return true; - } - } + if (DosisPvE.CanUse(out act)) return true; return base.GeneralGCD(out act); } @@ -444,5 +408,14 @@ protected override bool GeneralGCD(out IAction? act) #region Extra Methods public override bool CanHealSingleSpell => base.CanHealSingleSpell && (GCDHeal || PartyMembers.GetJobCategory(JobRole.Healer).Count() < 2); public override bool CanHealAreaSpell => base.CanHealAreaSpell && (GCDHeal || PartyMembers.GetJobCategory(JobRole.Healer).Count() < 2); + + public override void DisplayStatus() + { + ImGui.Text($"Eukrasian Action: {_EukrasiaActionAim}"); + ImGui.Text("HasEukrasia: " + HasEukrasia.ToString()); + ImGui.Text("Addersgall: " + Addersgall.ToString()); + ImGui.Text("Addersting: " + Addersting.ToString()); + ImGui.Text("AddersgallTime: " + AddersgallTime.ToString()); + } #endregion } diff --git a/BasicRotations/Tank/PLD_Beta.cs b/BasicRotations/Tank/PLD_Beta.cs index eb17adbc5..cc1b656a4 100644 --- a/BasicRotations/Tank/PLD_Beta.cs +++ b/BasicRotations/Tank/PLD_Beta.cs @@ -31,6 +31,17 @@ public sealed class PLD_Beta : PaladinRotation [RotationConfig(CombatType.PvE, Name = "Use Holy Spirit when out of melee range")] private bool UseHolyWhenAway { get; set; } = false; + + [Range(0, 1, ConfigUnitType.Percent)] + [RotationConfig(CombatType.PvE, Name = "Minimum HP threshold party member needs to be to use Clemency with Requiescat")] + public float ClemencyRequi { get; set; } = 0.2f; + + [RotationConfig(CombatType.PvE, Name = "Use Clemency without Requiescat")] + private bool HealBot { get; set; } = true; + + [Range(0, 1, ConfigUnitType.Percent)] + [RotationConfig(CombatType.PvE, Name = "Minimum HP threshold party member needs to be to use Clemency without Requiescat")] + public float ClemencyNoRequi { get; set; } = 0.4f; #endregion private const ActionID ConfiteorPvEActionId = (ActionID)16459; @@ -128,7 +139,7 @@ protected override bool DefenseSingleAbility(IAction nextGCD, out IAction? act) #region oGCD Logic - [RotationDesc(ActionID.FightOrFlightPvE)] + [RotationDesc(ActionID.SheltronPvE, ActionID.HolySheltronPvE)] protected override bool GeneralAbility(IAction nextGCD, out IAction? act) { act = null; @@ -137,7 +148,8 @@ protected override bool GeneralAbility(IAction nextGCD, out IAction? act) if (InCombat && OathGauge >= WhenToSheltron && WhenToSheltron > 0 && UseOath(out act)) return true; return base.GeneralAbility(nextGCD, out act); } - [RotationDesc(ActionID.SpiritsWithinPvE)] + + [RotationDesc(ActionID.IntervenePvE, ActionID.SpiritsWithinPvE, ActionID.ExpiacionPvE, ActionID.CircleOfScornPvE, ActionID.RequiescatPvE, ActionID.ImperatorPvE, ActionID.FightOrFlightPvE)] protected override bool AttackAbility(IAction nextGCD, out IAction? act) { act = null; @@ -145,14 +157,26 @@ protected override bool AttackAbility(IAction nextGCD, out IAction? act) if (BladeOfHonorPvE.CanUse(out act, skipAoeCheck: true)) return true; - if (!RiotBladePvE.EnoughLevel && FightOrFlightPvE.CanUse(out act)) return true; - if (!RageOfHalonePvE.EnoughLevel && nextGCD.IsTheSameTo(true, RiotBladePvE) && FightOrFlightPvE.CanUse(out act)) return true; - if (!SwordOathTrait.EnoughLevel && nextGCD.IsTheSameTo(true, RoyalAuthorityPvE) && FightOrFlightPvE.CanUse(out act)) return true; - if (SwordOathTrait.EnoughLevel && Player.HasStatus(true, StatusID.AtonementReady) && FightOrFlightPvE.CanUse(out act)) return true; + if (!RiotBladePvE.EnoughLevel && nextGCD.IsTheSameTo(true, FastBladePvE) && FightOrFlightPvE.CanUse(out act)) return true; + if (!RageOfHalonePvE.EnoughLevel && nextGCD.IsTheSameTo(true, RiotBladePvE, TotalEclipsePvE) && FightOrFlightPvE.CanUse(out act)) return true; + if (!ProminencePvE.EnoughLevel && nextGCD.IsTheSameTo(true, RageOfHalonePvE, TotalEclipsePvE) && FightOrFlightPvE.CanUse(out act)) return true; + if (!AtonementPvE.EnoughLevel && nextGCD.IsTheSameTo(true, RoyalAuthorityPvE, ProminencePvE) && FightOrFlightPvE.CanUse(out act)) return true; + if (AtonementPvE.EnoughLevel && (Player.HasStatus(true, StatusID.AtonementReady) || nextGCD.IsTheSameTo(true, ProminencePvE)) && FightOrFlightPvE.CanUse(out act)) return true; + + // if requiscat is able to proc confiteor, use it immediately after Fight or Flight + if (RequiescatMasteryTrait.EnoughLevel) + { + if (IsLastAbility(true, FightOrFlightPvE) && ImperatorPvE.CanUse(out act, skipAoeCheck: true, usedUp: true)) return true; + if (IsLastAbility(true, FightOrFlightPvE) && RequiescatPvE.CanUse(out act, skipAoeCheck: true, usedUp: true)) return true; + } + + // if requiscat is not able to proc confiteor, use it as AOE tool if able, otherwise as Single Target + if (!RequiescatMasteryTrait.EnoughLevel) + { + if (HolyCirclePvE.EnoughLevel && NumberOfHostilesInRange > 1 && RequiescatPvE.CanUse(out act, skipAoeCheck: true, usedUp: true)) return true; + if (!HolyCirclePvE.EnoughLevel && (NumberOfHostilesInRange == 1 || (RequiescatPvE.Target.Target?.IsBossFromIcon() ?? false)) && RequiescatPvE.CanUse(out act, skipAoeCheck: true, usedUp: true)) return true; + } - if (IsLastAbility(true, FightOrFlightPvE) && ImperatorPvE.CanUse(out act, skipAoeCheck: true, usedUp: true)) return true; - if (IsLastAbility(true, FightOrFlightPvE) && RequiescatPvE.CanUse(out act, skipAoeCheck: true, usedUp: true)) return true; - if (FightOrFlightPvE.Cooldown.IsCoolingDown && CircleOfScornPvE.CanUse(out act, skipAoeCheck: true)) return true; if (FightOrFlightPvE.Cooldown.IsCoolingDown && ExpiacionPvE.CanUse(out act, skipAoeCheck: true)) return true; if (FightOrFlightPvE.Cooldown.IsCoolingDown && SpiritsWithinPvE.CanUse(out act)) return true; @@ -168,7 +192,8 @@ protected override bool HealSingleGCD(out IAction? act) { act = null; if (PassageProtec && Player.HasStatus(true, StatusID.PassageOfArms)) return false; - if (ClemencyPvE.CanUse(out act)) return true; + if (ClemencyPvE.Target.Target?.GetHealthRatio() < ClemencyRequi && RequiescatStacks > 0 && ClemencyPvE.CanUse(out act, skipCastingCheck: true)) return true; + if (HealBot && ClemencyPvE.Target.Target?.GetHealthRatio() < ClemencyNoRequi && ClemencyPvE.CanUse(out act)) return true; return base.HealSingleGCD(out act); } @@ -200,12 +225,12 @@ protected override bool GeneralGCD(out IAction? act) if (AtonementPvE.CanUse(out act)) return true; //AoE - if (Player.HasStatus(true, StatusID.DivineMight) && HolyCirclePvE.CanUse(out act)) return true; + if ((Player.HasStatus(true, StatusID.DivineMight) || RequiescatStacks > 0) && HolyCirclePvE.CanUse(out act, skipCastingCheck: true)) return true; if (ProminencePvE.CanUse(out act)) return true; if (TotalEclipsePvE.CanUse(out act)) return true; //Single Target - if (Player.HasStatus(true, StatusID.DivineMight) && HolySpiritPvE.CanUse(out act)) return true; + if ((Player.HasStatus(true, StatusID.DivineMight) || RequiescatStacks > 0) && HolySpiritPvE.CanUse(out act, skipCastingCheck: true)) return true; if (RoyalAuthorityPvE.CanUse(out act)) return true; if (RageOfHalonePvE.CanUse(out act)) return true; diff --git a/RotationSolver.Basic/Rotations/Basic/PaladinRotation.cs b/RotationSolver.Basic/Rotations/Basic/PaladinRotation.cs index 2f049c7bb..34a7a6d34 100644 --- a/RotationSolver.Basic/Rotations/Basic/PaladinRotation.cs +++ b/RotationSolver.Basic/Rotations/Basic/PaladinRotation.cs @@ -227,6 +227,11 @@ static partial void ModifyDivineVeilPvE(ref ActionSetting setting) static partial void ModifyClemencyPvE(ref ActionSetting setting) { setting.UnlockedByQuestID = 67572; + setting.CanTarget = t => + { + if (t.HasStatus(false, StatusHelper.TankStanceStatus)) return false; + return true; + }; } static partial void ModifyRoyalAuthorityPvE(ref ActionSetting setting) diff --git a/RotationSolver.Basic/Rotations/Basic/SageRotation.cs b/RotationSolver.Basic/Rotations/Basic/SageRotation.cs index 76771991a..bcb5621db 100644 --- a/RotationSolver.Basic/Rotations/Basic/SageRotation.cs +++ b/RotationSolver.Basic/Rotations/Basic/SageRotation.cs @@ -52,7 +52,6 @@ public override void DisplayStatus() ImGui.Text("Addersgall: " + Addersgall.ToString()); ImGui.Text("Addersting: " + Addersting.ToString()); ImGui.Text("AddersgallTime: " + AddersgallTime.ToString()); - ImGui.Text("AddersgallTimerRaw: " + AddersgallTimerRaw.ToString()); } #endregion @@ -118,7 +117,7 @@ static partial void ModifyEukrasiaPvE(ref ActionSetting setting) static partial void ModifyEukrasianDiagnosisPvE(ref ActionSetting setting) { - setting.ActionCheck = () => HasEukrasia && !DataCenter.AllianceMembers.Any(m => m.HasStatus(true, StatusID.EukrasianDiagnosis)); + setting.ActionCheck = () => !DataCenter.AllianceMembers.Any(m => m.HasStatus(true, StatusID.EukrasianDiagnosis)); setting.TargetStatusProvide = [StatusID.EukrasianDiagnosis, StatusID.Galvanize]; // Effect cannot be stacked with scholar's Galvanize. setting.TargetType = TargetType.BeAttacked; setting.StatusFromSelf = false;