From 141f9b27a43d390b7ab285baba39450b7767c6de Mon Sep 17 00:00:00 2001 From: Thomas Brooks <34015422+NostraThomas99@users.noreply.github.com> Date: Sat, 23 Nov 2024 00:29:42 -0600 Subject: [PATCH] Bake in default rotations Default rotations are now baked into the plugin Removed annoying "First Time Setup" --- .github/workflows/publish.yaml | 2 + BasicRotations/AssemblyInfo.cs | 1 + BasicRotations/Duty/BozjaDefault | 42 ++ BasicRotations/Duty/EmanationDefault | 26 ++ BasicRotations/Duty/VariantDefault.cs | 37 ++ BasicRotations/Healer/AST_Default.cs | 276 +++++++++++ BasicRotations/Healer/SCH_Default.cs | 237 ++++++++++ BasicRotations/Healer/SGE_Default.cs | 433 ++++++++++++++++++ BasicRotations/Healer/WHM_Default.cs | 255 +++++++++++ BasicRotations/Limited Jobs/BLU_Default.cs | 70 +++ BasicRotations/Limited Jobs/BSM_Default.cs | 76 +++ BasicRotations/Magical/BLM_Default.cs | 414 +++++++++++++++++ BasicRotations/Magical/ICWA_PCT_BETA.cs | 261 +++++++++++ BasicRotations/Magical/PCT_Default.cs | 183 ++++++++ BasicRotations/Magical/RDM_Default.cs | 273 +++++++++++ BasicRotations/Magical/SMN_Archive | 159 +++++++ BasicRotations/Magical/SMN_Default.cs | 229 +++++++++ BasicRotations/Melee/DRG_Default.cs | 127 +++++ BasicRotations/Melee/MNK_Default.cs | 252 ++++++++++ BasicRotations/Melee/NIN_Default.cs | 432 +++++++++++++++++ BasicRotations/Melee/RPR_Default.cs | 218 +++++++++ BasicRotations/Melee/SAM_Default.cs | 198 ++++++++ BasicRotations/Melee/VPR_Default.cs | 211 +++++++++ .../PVPRotations/Healer/AST_Default.PVP.cs | 143 ++++++ .../PVPRotations/Healer/SCH_Default.PVP.cs | 117 +++++ .../PVPRotations/Healer/SGE_Default.PVP.cs | 116 +++++ .../PVPRotations/Healer/WHM_Default.PVP.cs | 121 +++++ .../PVPRotations/Magical/BLM_Default.PVP.cs | 116 +++++ .../PVPRotations/Magical/RDM_Default.PvP.cs | 130 ++++++ .../PVPRotations/Magical/SMN_Default.PVP.cs | 116 +++++ .../PVPRotations/Melee/DRG_Default.PVP.cs | 120 +++++ .../PVPRotations/Melee/MNK_Default.PVP.cs | 128 ++++++ .../PVPRotations/Melee/NIN_Default.PVP.cs | 111 +++++ .../PVPRotations/Melee/RPR_Default.PVP.cs | 124 +++++ .../PVPRotations/Melee/SAM_Default.PVP.cs | 109 +++++ .../PVPRotations/Melee/VPR_Default.PVP.cs | 138 ++++++ .../PVPRotations/Ranged/BRD_Default.PVP.cs | 131 ++++++ .../PVPRotations/Ranged/DNC_Default.PVP.cs | 117 +++++ .../PVPRotations/Ranged/MCH_Default.PvP.cs | 138 ++++++ .../PVPRotations/Tank/DRK_Default.PVP.cs | 114 +++++ .../PVPRotations/Tank/GNB_Default.PVP.cs | 110 +++++ .../PVPRotations/Tank/PLD_Default.PVP.cs | 121 +++++ .../PVPRotations/Tank/WAR_Default.PVP.cs | 120 +++++ BasicRotations/Ranged/BRD_Default.cs | 295 ++++++++++++ BasicRotations/Ranged/DNC_Default.cs | 323 +++++++++++++ BasicRotations/Ranged/MCH_Default.cs | 228 +++++++++ BasicRotations/Ranged/Queen Timings | 30 ++ BasicRotations/Ranged/zMCH_Beta.cs | 223 +++++++++ BasicRotations/Ranged/zMCH_Beta_2.cs | 269 +++++++++++ BasicRotations/RebornRotations.csproj | 72 +++ BasicRotations/Tank/DRK_Default.cs | 215 +++++++++ BasicRotations/Tank/GNB_Default.cs | 246 ++++++++++ BasicRotations/Tank/PLD_Default.cs | 252 ++++++++++ BasicRotations/Tank/WAR_Default.cs | 197 ++++++++ RotationSolver.Basic/Configuration/Configs.cs | 10 +- .../Configuration/RotationSolverRecord.cs | 5 - RotationSolver.sln | 6 + RotationSolver/Data/UiString.cs | 21 - RotationSolver/RotationSolver.csproj | 6 + RotationSolver/RotationSolverPlugin.cs | 2 +- RotationSolver/UI/RotationConfigWindow.cs | 15 +- RotationSolver/UI/WelcomeWindow.cs | 53 --- RotationSolver/Updaters/RotationUpdater.cs | 26 +- 63 files changed, 9248 insertions(+), 98 deletions(-) create mode 100644 BasicRotations/AssemblyInfo.cs create mode 100644 BasicRotations/Duty/BozjaDefault create mode 100644 BasicRotations/Duty/EmanationDefault create mode 100644 BasicRotations/Duty/VariantDefault.cs create mode 100644 BasicRotations/Healer/AST_Default.cs create mode 100644 BasicRotations/Healer/SCH_Default.cs create mode 100644 BasicRotations/Healer/SGE_Default.cs create mode 100644 BasicRotations/Healer/WHM_Default.cs create mode 100644 BasicRotations/Limited Jobs/BLU_Default.cs create mode 100644 BasicRotations/Limited Jobs/BSM_Default.cs create mode 100644 BasicRotations/Magical/BLM_Default.cs create mode 100644 BasicRotations/Magical/ICWA_PCT_BETA.cs create mode 100644 BasicRotations/Magical/PCT_Default.cs create mode 100644 BasicRotations/Magical/RDM_Default.cs create mode 100644 BasicRotations/Magical/SMN_Archive create mode 100644 BasicRotations/Magical/SMN_Default.cs create mode 100644 BasicRotations/Melee/DRG_Default.cs create mode 100644 BasicRotations/Melee/MNK_Default.cs create mode 100644 BasicRotations/Melee/NIN_Default.cs create mode 100644 BasicRotations/Melee/RPR_Default.cs create mode 100644 BasicRotations/Melee/SAM_Default.cs create mode 100644 BasicRotations/Melee/VPR_Default.cs create mode 100644 BasicRotations/PVPRotations/Healer/AST_Default.PVP.cs create mode 100644 BasicRotations/PVPRotations/Healer/SCH_Default.PVP.cs create mode 100644 BasicRotations/PVPRotations/Healer/SGE_Default.PVP.cs create mode 100644 BasicRotations/PVPRotations/Healer/WHM_Default.PVP.cs create mode 100644 BasicRotations/PVPRotations/Magical/BLM_Default.PVP.cs create mode 100644 BasicRotations/PVPRotations/Magical/RDM_Default.PvP.cs create mode 100644 BasicRotations/PVPRotations/Magical/SMN_Default.PVP.cs create mode 100644 BasicRotations/PVPRotations/Melee/DRG_Default.PVP.cs create mode 100644 BasicRotations/PVPRotations/Melee/MNK_Default.PVP.cs create mode 100644 BasicRotations/PVPRotations/Melee/NIN_Default.PVP.cs create mode 100644 BasicRotations/PVPRotations/Melee/RPR_Default.PVP.cs create mode 100644 BasicRotations/PVPRotations/Melee/SAM_Default.PVP.cs create mode 100644 BasicRotations/PVPRotations/Melee/VPR_Default.PVP.cs create mode 100644 BasicRotations/PVPRotations/Ranged/BRD_Default.PVP.cs create mode 100644 BasicRotations/PVPRotations/Ranged/DNC_Default.PVP.cs create mode 100644 BasicRotations/PVPRotations/Ranged/MCH_Default.PvP.cs create mode 100644 BasicRotations/PVPRotations/Tank/DRK_Default.PVP.cs create mode 100644 BasicRotations/PVPRotations/Tank/GNB_Default.PVP.cs create mode 100644 BasicRotations/PVPRotations/Tank/PLD_Default.PVP.cs create mode 100644 BasicRotations/PVPRotations/Tank/WAR_Default.PVP.cs create mode 100644 BasicRotations/Ranged/BRD_Default.cs create mode 100644 BasicRotations/Ranged/DNC_Default.cs create mode 100644 BasicRotations/Ranged/MCH_Default.cs create mode 100644 BasicRotations/Ranged/Queen Timings create mode 100644 BasicRotations/Ranged/zMCH_Beta.cs create mode 100644 BasicRotations/Ranged/zMCH_Beta_2.cs create mode 100644 BasicRotations/RebornRotations.csproj create mode 100644 BasicRotations/Tank/DRK_Default.cs create mode 100644 BasicRotations/Tank/GNB_Default.cs create mode 100644 BasicRotations/Tank/PLD_Default.cs create mode 100644 BasicRotations/Tank/WAR_Default.cs diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 347a4864f..f777d59b5 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -38,6 +38,8 @@ jobs: - name: Restore Nuget Packages run: dotnet restore RotationSolver/RotationSolver.csproj + - name: Build Rotations + run: dotnet build --no-restore -c Release BasicRotations/RebornRotations.csproj -p:AssemblyVersion=${{ env.tag }} -p:FileVersion=${{ env.tag }} -p:PackageVersion=${{ env.tag }} -p:InformationalVersion=${{ env.tag }} --output .\build - name: Build Plugin run: dotnet build --no-restore -c Release RotationSolver/RotationSolver.csproj -p:AssemblyVersion=${{ env.tag }} -p:FileVersion=${{ env.tag }} -p:PackageVersion=${{ env.tag }} -p:InformationalVersion=${{ env.tag }} --output .\build - name: Push Nuget Package diff --git a/BasicRotations/AssemblyInfo.cs b/BasicRotations/AssemblyInfo.cs new file mode 100644 index 000000000..f8a63d455 --- /dev/null +++ b/BasicRotations/AssemblyInfo.cs @@ -0,0 +1 @@ +[assembly: AssemblyLink(Donate = "", UserName = "FFXIV-CombatReborn", Repository = "RotationSolverReborn")] \ No newline at end of file diff --git a/BasicRotations/Duty/BozjaDefault b/BasicRotations/Duty/BozjaDefault new file mode 100644 index 000000000..acf1ff0e7 --- /dev/null +++ b/BasicRotations/Duty/BozjaDefault @@ -0,0 +1,42 @@ +using RotationSolver.Basic.Rotations.Duties; + +namespace DefaultRotations.Duty; + +[Rotation("Bozja Default", CombatType.PvE)] +internal class BozjaDefault : BozjaRotation +{ + public override bool DefenseSingleGCD(out IAction? act) + { + if (LostStoneskinPvE.CanUse(out act)) return true; + return base.DefenseSingleGCD(out act); + } + + public override bool DefenseAreaGCD(out IAction? act) + { + if (LostStoneskinIiPvE.CanUse(out act)) return true; + return base.DefenseAreaGCD(out act); + } + + public override bool EmergencyGCD(out IAction? act) + { + #region Bozja + //if (LostSpellforge.CanUse(out act)) return true; + //if (LostSteelsting.CanUse(out act)) return true; + //if (LostRampage.CanUse(out act)) return true; + //if (LostBurst.CanUse(out act)) return true; + + //if (LostBravery.CanUse(out act)) return true; + //if (LostBubble.CanUse(out act)) return true; + //if (LostShell2.CanUse(out act)) return true; + //if (LostShell.CanUse(out act)) return true; + //if (LostProtect2.CanUse(out act)) return true; + //if (LostProtect.CanUse(out act)) return true; + + ////Add your own logic here. + //if (LostFlarestar.CanUse(out act)) return true; + //if (LostSeraphStrike.CanUse(out act)) return true; + + #endregion + return base.EmergencyGCD(out act); + } +} diff --git a/BasicRotations/Duty/EmanationDefault b/BasicRotations/Duty/EmanationDefault new file mode 100644 index 000000000..8401d1b07 --- /dev/null +++ b/BasicRotations/Duty/EmanationDefault @@ -0,0 +1,26 @@ +using RotationSolver.Basic.Rotations.Duties; + +namespace DefaultRotations.Duty; + +[Rotation("Emanation Default", CombatType.PvE)] + +internal class EmanationDefault : EmanationRotation +{ + public override bool EmergencyAbility(IAction nextGCD, out IAction? act) + { + // 8521 8522 8523 + bool Lol1 = HostileTarget?.CastActionId == 8521; + bool Lol2 = HostileTarget?.CastActionId == 8522; + bool Lol3 = HostileTarget?.CastActionId == 8523; + + if (Lol1 || Lol2 || Lol3) + { + if (VrilPvE.CanUse(out act)) return true; // Normal + if (VrilPvE_9345.CanUse(out act)) return true; // Extreme + return base.EmergencyAbility(nextGCD, out act); + } + + act = null; + return base.EmergencyAbility(nextGCD, out act); + } +} \ No newline at end of file diff --git a/BasicRotations/Duty/VariantDefault.cs b/BasicRotations/Duty/VariantDefault.cs new file mode 100644 index 000000000..ffdea7477 --- /dev/null +++ b/BasicRotations/Duty/VariantDefault.cs @@ -0,0 +1,37 @@ +using RotationSolver.Basic.Rotations.Duties; + +namespace DefaultRotations.Duty; + +[Rotation("Variant Default", CombatType.PvE)] + +internal class VariantDefault : VariantRotation +{ + public override bool ProvokeAbility(IAction nextGCD, out IAction? act) + { + if (VariantUltimatumPvE.CanUse(out act)) return true; + return base.ProvokeAbility(nextGCD, out act); + } + + public override bool AttackAbility(IAction nextGCD, out IAction? act) + { + if (VariantSpiritDartPvE.CanUse(out act, skipAoeCheck: true)) return true; + if (VariantSpiritDartPvE_33863.CanUse(out act, skipAoeCheck: true)) return true; + if (VariantRampartPvE.CanUse(out act)) return true; + if (VariantRampartPvE_33864.CanUse(out act)) return true; + return base.AttackAbility(nextGCD, out act); + } + + public override bool HealSingleGCD(out IAction? act) + { + if (VariantCurePvE.CanUse(out act, skipStatusProvideCheck: true)) return true; + if (VariantCurePvE_33862.CanUse(out act, skipStatusProvideCheck: true)) return true; + return base.HealSingleGCD(out act); + } + + public override bool RaiseGCD(out IAction? act) + { + if (VariantRaisePvE.CanUse(out act)) return true; + if (VariantRaiseIiPvE.CanUse(out act)) return true; + return base.RaiseGCD(out act); + } +} diff --git a/BasicRotations/Healer/AST_Default.cs b/BasicRotations/Healer/AST_Default.cs new file mode 100644 index 000000000..2518a8fe5 --- /dev/null +++ b/BasicRotations/Healer/AST_Default.cs @@ -0,0 +1,276 @@ +namespace DefaultRotations.Healer; + +[Rotation("Default", CombatType.PvE, GameVersion = "7.05")] +[SourceCode(Path = "main/BasicRotations/Healer/AST_Default.cs")] +[Api(4)] +public sealed class AST_Default : AstrologianRotation +{ + #region Config Options + [RotationConfig(CombatType.PvE, Name = "Enable Swiftcast Restriction Logic to attempt to prevent actions other than Raise when you have swiftcast")] + public bool SwiftLogic { get; set; } = true; + + [RotationConfig(CombatType.PvE, Name = "Use both stacks of Lightspeed while moving")] + public bool LightspeedMove { get; set; } = true; + + [RotationConfig(CombatType.PvE, Name = "Use experimental card logic to pool for divination buff if possible")] + public bool SmartCard { 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; + + [RotationConfig(CombatType.PvE, Name = "Prevent actions while you have the bubble mit up")] + public bool BubbleProtec { get; set; } = false; + + [RotationConfig(CombatType.PvE, Name = "Prioritize Microcosmos over all other healing when available")] + public bool MicroPrio { get; set; } = false; + + [Range(4, 20, ConfigUnitType.Seconds)] + [RotationConfig(CombatType.PvE, Name = "Use Earthly Star during countdown timer.")] + public float UseEarthlyStarTime { get; set; } = 15; + + [Range(0, 1, ConfigUnitType.Percent)] + [RotationConfig(CombatType.PvE, Name = "Minimum HP threshold party member needs to be to use Aspected Benefic")] + public float AspectedBeneficHeal { get; set; } = 0.4f; + + [Range(0, 1, ConfigUnitType.Percent)] + [RotationConfig(CombatType.PvE, Name = "Minimum HP threshold among party member needed to use Horoscope")] + public float HoroscopeHeal { get; set; } = 0.3f; + + [RotationConfig(CombatType.PvE, Name = "Use DOT while moving even if it does not need refresh (disabling is a damage down)")] + public bool DOTUpkeep { get; set; } = true; + #endregion + + private static bool InBurstStatus => !Player.WillStatusEnd(0, true, StatusID.Divination); + + #region Countdown Logic + protected override IAction? CountDownAction(float remainTime) + { + if (remainTime < MaleficPvE.Info.CastTime + CountDownAhead + && MaleficPvE.CanUse(out var act)) return act; + if (remainTime < 3 && UseBurstMedicine(out act)) return act; + if (remainTime is < 4 and > 3 && AspectedBeneficPvE.CanUse(out act)) return act; + if (remainTime < UseEarthlyStarTime + && EarthlyStarPvE.CanUse(out act, skipTTKCheck: true)) return act; + if (remainTime < 30 && AstralDrawPvE.CanUse(out act)) return act; + + return base.CountDownAction(remainTime); + } + #endregion + + #region oGCD Logic + protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (BubbleProtec && Player.HasStatus(true, StatusID.CollectiveUnconscious_848)) return false; + if (MicroPrio && Player.HasStatus(true, StatusID.Macrocosmos)) return false; + + if (!InCombat) return false; + + if (OraclePvE.CanUse(out act)) return true; + if (nextGCD.IsTheSameTo(true, AspectedHeliosPvE, HeliosPvE)) + { + if (HoroscopePvE.CanUse(out act)) return true; + if (NeutralSectPvE.CanUse(out act)) return true; + } + + if (nextGCD.IsTheSameTo(true, BeneficPvE, BeneficIiPvE, AspectedBeneficPvE)) + { + if (SynastryPvE.CanUse(out act)) return true; + } + return base.EmergencyAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.ExaltationPvE, ActionID.TheArrowPvE, ActionID.TheSpirePvE, ActionID.TheBolePvE, ActionID.TheEwerPvE)] + protected override bool DefenseSingleAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (BubbleProtec && Player.HasStatus(true, StatusID.CollectiveUnconscious_848)) return false; + + if (InCombat && TheSpirePvE.CanUse(out act)) return true; + if (InCombat && TheBolePvE.CanUse(out act)) return true; + + if (ExaltationPvE.CanUse(out act)) return true; + return base.DefenseSingleAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.MacrocosmosPvE)] + protected override bool DefenseAreaGCD(out IAction? act) + { + act = null; + if (BubbleProtec && Player.HasStatus(true, StatusID.CollectiveUnconscious_848)) return false; + + if (MacrocosmosPvE.Cooldown.IsCoolingDown && !MacrocosmosPvE.Cooldown.WillHaveOneCharge(150) + || CollectiveUnconsciousPvE.Cooldown.IsCoolingDown && !CollectiveUnconsciousPvE.Cooldown.WillHaveOneCharge(40)) return false; + + if (MacrocosmosPvE.CanUse(out act)) return true; + return base.DefenseAreaGCD(out act); + } + + [RotationDesc(ActionID.CollectiveUnconsciousPvE, ActionID.SunSignPvE)] + protected override bool DefenseAreaAbility(IAction nextGCD, out IAction? act) + { + if (SunSignPvE.CanUse(out act)) return true; + + act = null; + if (BubbleProtec && Player.HasStatus(true, StatusID.CollectiveUnconscious_848)) return false; + + if (MacrocosmosPvE.Cooldown.IsCoolingDown && !MacrocosmosPvE.Cooldown.WillHaveOneCharge(150) + || CollectiveUnconsciousPvE.Cooldown.IsCoolingDown && !CollectiveUnconsciousPvE.Cooldown.WillHaveOneCharge(40)) return false; + + if (CollectiveUnconsciousPvE.CanUse(out act)) return true; + return base.DefenseAreaAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.TheArrowPvE, ActionID.TheEwerPvE, ActionID.EssentialDignityPvE, + ActionID.CelestialIntersectionPvE)] + protected override bool HealSingleAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (BubbleProtec && Player.HasStatus(true, StatusID.CollectiveUnconscious_848)) return false; + if (MicroPrio && Player.HasStatus(true, StatusID.Macrocosmos)) return false; + + if (InCombat && TheArrowPvE.CanUse(out act)) return true; + if (InCombat && TheEwerPvE.CanUse(out act)) return true; + + if (EssentialDignityPvE.CanUse(out act, usedUp: true)) return true; + + if (CelestialIntersectionPvE.CanUse(out act, usedUp: true)) return true; + + return base.HealSingleAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.CelestialOppositionPvE, ActionID.StellarDetonationPvE, ActionID.HoroscopePvE, ActionID.HoroscopePvE_16558, ActionID.LadyOfCrownsPvE, ActionID.HeliosConjunctionPvE)] + protected override bool HealAreaAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (BubbleProtec && Player.HasStatus(true, StatusID.CollectiveUnconscious_848)) return false; + + if (MicrocosmosPvE.CanUse(out act)) return true; + if (MicroPrio && Player.HasStatus(true, StatusID.Macrocosmos)) return false; + + if (CelestialOppositionPvE.CanUse(out act)) return true; + + if (StellarDetonationPvE.CanUse(out act)) return true; + + if (HoroscopePvE.CanUse(out act)) return true; + + if (HoroscopePvE_16558.CanUse(out act)) return true; + + if (LadyOfCrownsPvE.CanUse(out act)) return true; + + if (HeliosConjunctionPvE.CanUse(out act)) return true; + return base.HealAreaAbility(nextGCD, out act); + } + + protected override bool GeneralAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (BubbleProtec && Player.HasStatus(true, StatusID.CollectiveUnconscious_848)) return false; + + if (AstralDrawPvE.CanUse(out act)) return true; + if (UmbralDrawPvE.CanUse(out act)) return true; + if (((Player.HasStatus(true, StatusID.Divination) || !DivinationPvE.Cooldown.WillHaveOneCharge(45) || !DivinationPvE.EnoughLevel || UmbralDrawPvE.Cooldown.WillHaveOneCharge(3)) && SmartCard || (!SmartCard)) && InCombat && TheBalancePvE.CanUse(out act)) return true; + if (((Player.HasStatus(true, StatusID.Divination) || !DivinationPvE.Cooldown.WillHaveOneCharge(45) || !DivinationPvE.EnoughLevel || UmbralDrawPvE.Cooldown.WillHaveOneCharge(3)) && SmartCard || (!SmartCard)) && InCombat && TheSpearPvE.CanUse(out act)) return true; + return base.GeneralAbility(nextGCD, out act); + } + + protected override bool AttackAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (BubbleProtec && Player.HasStatus(true, StatusID.CollectiveUnconscious_848)) return false; + + if (!Player.HasStatus(true, StatusID.Lightspeed) + && InCombat + && DivinationPvE.Cooldown.ElapsedAfter(115) + && LightspeedPvE.CanUse(out act, usedUp: true)) return true; + + if (IsBurst && !IsMoving + && DivinationPvE.CanUse(out act)) return true; + + if (AstralDrawPvE.CanUse(out act, usedUp: IsBurst)) return true; + + if (!Player.HasStatus(true, StatusID.Lightspeed) + && (InBurstStatus || DivinationPvE.Cooldown.ElapsedAfter(115)) + && InCombat + && LightspeedPvE.CanUse(out act, usedUp: true)) return true; + + if (InCombat) + { + if (!Player.HasStatus(true, StatusID.Lightspeed) && IsMoving && LightspeedPvE.CanUse(out act, usedUp: LightspeedMove)) return true; + + if (!IsMoving) + { + if (!Player.HasStatus(true, StatusID.EarthlyDominance, StatusID.GiantDominance)) + { + if (EarthlyStarPvE.CanUse(out act)) return true; + } + } + + { + if (((Player.HasStatus(true, StatusID.Divination) || !DivinationPvE.Cooldown.WillHaveOneCharge(45) || !DivinationPvE.EnoughLevel || UmbralDrawPvE.Cooldown.WillHaveOneCharge(3)) && SmartCard || (!SmartCard)) && LordOfCrownsPvE.CanUse(out act)) return true; + } + } + return base.AttackAbility(nextGCD, out act); + } + #endregion + + #region GCD Logic + [RotationDesc(ActionID.AspectedBeneficPvE, ActionID.BeneficIiPvE, ActionID.BeneficPvE)] + protected override bool HealSingleGCD(out IAction? act) + { + act = null; + if (BubbleProtec && Player.HasStatus(true, StatusID.CollectiveUnconscious_848)) return false; + if (MicroPrio && Player.HasStatus(true, StatusID.Macrocosmos)) return false; + if (HasSwift && SwiftLogic && AscendPvE.CanUse(out _)) return false; + + if (AspectedBeneficPvE.CanUse(out act) + && (IsMoving + || AspectedBeneficPvE.Target.Target?.GetHealthRatio() > AspectedBeneficHeal)) return true; + + if (BeneficIiPvE.CanUse(out act)) return true; + if (BeneficPvE.CanUse(out act)) return true; + + return base.HealSingleGCD(out act); + } + + [RotationDesc(ActionID.AspectedHeliosPvE, ActionID.HeliosPvE)] + protected override bool HealAreaGCD(out IAction? act) + { + act = null; + if (BubbleProtec && Player.HasStatus(true, StatusID.CollectiveUnconscious_848)) return false; + if (MicroPrio && Player.HasStatus(true, StatusID.Macrocosmos)) return false; + if (HasSwift && SwiftLogic && AscendPvE.CanUse(out _)) return false; + + if (AspectedHeliosPvE.CanUse(out act)) return true; + if (HeliosPvE.CanUse(out act)) return true; + return base.HealAreaGCD(out act); + } + + protected override bool GeneralGCD(out IAction? act) + { + act = null; + if (BubbleProtec && Player.HasStatus(true, StatusID.CollectiveUnconscious_848)) return false; + if (HasSwift && SwiftLogic) return false; + + if (GravityPvE.CanUse(out act)) return true; + + if (CombustIiiPvE.CanUse(out act)) return true; + if (CombustIiPvE.CanUse(out act)) return true; + if (CombustPvE.CanUse(out act)) return true; + if (MaleficPvE.CanUse(out act)) return true; + if (CombustIiiPvE.CanUse(out act, skipStatusProvideCheck: DOTUpkeep)) return true; + if (CombustIiPvE.CanUse(out act, skipStatusProvideCheck: DOTUpkeep)) return true; + if (CombustPvE.CanUse(out act, skipStatusProvideCheck: DOTUpkeep)) return true; + + return base.GeneralGCD(out act); + } + #endregion + + + + #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); + + #endregion +} diff --git a/BasicRotations/Healer/SCH_Default.cs b/BasicRotations/Healer/SCH_Default.cs new file mode 100644 index 000000000..5876655a3 --- /dev/null +++ b/BasicRotations/Healer/SCH_Default.cs @@ -0,0 +1,237 @@ +namespace DefaultRotations.Healer; + +[Rotation("Default", CombatType.PvE, GameVersion = "7.05")] +[SourceCode(Path = "main/BasicRotations/Healer/SCH_Default.cs")] +[Api(4)] +public sealed class SCH_Default : ScholarRotation +{ + #region Config Options + [RotationConfig(CombatType.PvE, Name = "Enable Swiftcast Restriction Logic to attempt to prevent actions other than Raise when you have swiftcast")] + public bool SwiftLogic { 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; + + [RotationConfig(CombatType.PvE, Name = "Recitation during Countdown.")] + public bool PrevDUN { get; set; } = true; + + [RotationConfig(CombatType.PvE, Name = "Use Adloquium during Countdown")] + public bool GiveT { get; set; } = true; + + [RotationConfig(CombatType.PvE, Name = "Use Sacred Soil while moving")] + public bool SacredMove { get; set; } = false; + + [Range(0, 1, ConfigUnitType.Percent)] + [RotationConfig(CombatType.PvE, Name = "Remove Aetherpact to conserve resources if party member is above this percentage")] + public float AetherpactRemove { get; set; } = 0.9f; + + [RotationConfig(CombatType.PvE, Name = "Use DOT while moving even if it does not need refresh (disabling is a damage down)")] + public bool DOTUpkeep { get; set; } = true; + #endregion + + #region Countdown Logic + protected override IAction? CountDownAction(float remainTime) + { + var tank = PartyMembers.GetJobCategory(JobRole.Tank); + + if (SummonEosPvE.CanUse(out var act)) return act; + + if (remainTime < RuinPvE.Info.CastTime + CountDownAhead + && RuinPvE.CanUse(out act)) return act; + if (remainTime < 3 && UseBurstMedicine(out act)) return act; + if (remainTime is < 4 and > 3 && DeploymentTacticsPvE.CanUse(out act)) return act; + if (remainTime is < 7 and > 6 && GiveT && AdloquiumPvE.CanUse(out act)) return act; + if (remainTime <= 15 && PrevDUN && RecitationPvE.CanUse(out act)) return act; + + return base.CountDownAction(remainTime); + } + #endregion + + #region oGCD Logic + protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) + { + if (nextGCD.IsTheSameTo(true, SuccorPvE, AdloquiumPvE)) + { + if (RecitationPvE.CanUse(out act)) return true; + } + + //Remove Aetherpact + foreach (var item in PartyMembers) + { + if (item.GetHealthRatio() < AetherpactRemove) continue; + if (item.HasStatus(true, StatusID.FeyUnion_1223)) + { + act = AetherpactPvE; + return true; + } + } + + return base.EmergencyAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.SummonSeraphPvE, ActionID.ConsolationPvE, ActionID.WhisperingDawnPvE, ActionID.SacredSoilPvE, ActionID.IndomitabilityPvE)] + protected override bool HealAreaAbility(IAction nextGCD, out IAction? act) + { + if (AccessionPvE.CanUse(out act)) return true; + if (ConcitationPvE.CanUse(out act)) return true; + if (WhisperingDawnPvE_16537.Cooldown.ElapsedOneChargeAfterGCD(1) || FeyIlluminationPvE_16538.Cooldown.ElapsedOneChargeAfterGCD(1) || FeyBlessingPvE.Cooldown.ElapsedOneChargeAfterGCD(1)) + { + if (SummonSeraphPvE.CanUse(out act)) return true; + } + if (ConsolationPvE.CanUse(out act, usedUp: true)) return true; + if (FeyBlessingPvE.CanUse(out act)) return true; + + if (WhisperingDawnPvE_16537.CanUse(out act)) return true; + if (SacredSoilPvE.CanUse(out act)) return true; + if (IndomitabilityPvE.CanUse(out act)) return true; + + return base.HealAreaAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.AetherpactPvE, ActionID.ProtractionPvE, ActionID.SacredSoilPvE, ActionID.ExcogitationPvE, ActionID.LustratePvE, ActionID.AetherpactPvE)] + protected override bool HealSingleAbility(IAction nextGCD, out IAction? act) + { + var haveLink = PartyMembers.Any(p => p.HasStatus(true, StatusID.FeyUnion_1223)); + if (ManifestationPvE.CanUse(out act)) return true; + if (AetherpactPvE.CanUse(out act) && FairyGauge >= 70 && !haveLink) return true; + if (ProtractionPvE.CanUse(out act)) return true; + if (ExcogitationPvE.CanUse(out act)) return true; + if (LustratePvE.CanUse(out act)) return true; + if (AetherpactPvE.CanUse(out act) && !haveLink) return true; + + return base.HealSingleAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.FeyIlluminationPvE, ActionID.ExpedientPvE, ActionID.SummonSeraphPvE, ActionID.ConsolationPvE, ActionID.SacredSoilPvE)] + protected override bool DefenseAreaAbility(IAction nextGCD, out IAction? act) + { + if (DeploymentTacticsPvE.CanUse(out act)) return true; + + if (SeraphismPvE.CanUse(out act)) return true; + + if (FeyIlluminationPvE_16538.CanUse(out act)) return true; + if (ExpedientPvE.CanUse(out act)) return true; + + if (WhisperingDawnPvE_16537.Cooldown.ElapsedOneChargeAfterGCD(1) || FeyIlluminationPvE_16538.Cooldown.ElapsedOneChargeAfterGCD(1) || FeyBlessingPvE.Cooldown.ElapsedOneChargeAfterGCD(1)) + { + if (SummonSeraphPvE.CanUse(out act)) return true; + } + if (ConsolationPvE.CanUse(out act, usedUp: true)) return true; + if (((!SacredMove && !IsMoving) || SacredMove) && SacredSoilPvE.CanUse(out act)) return true; + + return base.DefenseAreaAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.ExcogitationPvE)] + protected override bool DefenseSingleAbility(IAction nextGCD, out IAction? act) + { + if (ExcogitationPvE.CanUse(out act)) return true; + return base.DefenseSingleAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.ExpedientPvE)] + protected override bool SpeedAbility(IAction nextGCD, out IAction? act) + { + if (InCombat && ExpedientPvE.CanUse(out act, usedUp: true)) return true; + return base.SpeedAbility(nextGCD, out act); + } + + protected override bool AttackAbility(IAction nextGCD, out IAction? act) + { + if (BanefulImpactionPvE.CanUse(out act)) return true; + if (IsBurst) + { + if (ChainStratagemPvE.CanUse(out act)) return true; + } + + if (DissipationPvE.EnoughLevel && DissipationPvE.Cooldown.WillHaveOneChargeGCD(3) && DissipationPvE.IsEnabled || AetherflowPvE.Cooldown.WillHaveOneChargeGCD(3)) + { + if (EnergyDrainPvE.CanUse(out act, usedUp: true)) return true; + } + + if (DissipationPvE.CanUse(out act)) return true; + if (AetherflowPvE.CanUse(out act)) return true; + + return base.AttackAbility(nextGCD, out act); + } + #endregion + + #region GCD Logic + [RotationDesc(ActionID.SuccorPvE)] + protected override bool HealAreaGCD(out IAction? act) + { + act = null; + + if (HasSwift && SwiftLogic && ResurrectionPvE.CanUse(out _)) return false; + + if (SuccorPvE.CanUse(out act)) return true; + + return base.HealAreaGCD(out act); + } + + [RotationDesc(ActionID.AdloquiumPvE, ActionID.PhysickPvE)] + protected override bool HealSingleGCD(out IAction? act) + { + act = null; + + if (HasSwift && SwiftLogic && ResurrectionPvE.CanUse(out _)) return false; + + if (AdloquiumPvE.CanUse(out act)) return true; + if (PhysickPvE.CanUse(out act)) return true; + + return base.HealSingleGCD(out act); + } + + [RotationDesc(ActionID.SuccorPvE)] + protected override bool DefenseAreaGCD(out IAction? act) + { + act = null; + + if (HasSwift && SwiftLogic && ResurrectionPvE.CanUse(out _)) return false; + + if (SuccorPvE.CanUse(out act)) return true; + return base.DefenseAreaGCD(out act); + } + + protected override bool GeneralGCD(out IAction? act) + { + act = null; + + if (HasSwift && SwiftLogic && ResurrectionPvE.CanUse(out _)) return false; + + // Summon Eos + if (SummonEosPvE.CanUse(out act)) return true; + + //Add dot + if (BiolysisPvE.CanUse(out act) && AllHostileTargets.Where(p => p.DistanceToPlayer() < 5).Count() < 4) return true; + if (BioIiPvE.CanUse(out act) && AllHostileTargets.Where(p => p.DistanceToPlayer() < 5).Count() < 4) return true; + if (BioPvE.CanUse(out act) && AllHostileTargets.Where(p => p.DistanceToPlayer() < 5).Count() < 4) return true; + + //AOE + if (ArtOfWarIiPvE.CanUse(out act)) return true; + if (ArtOfWarPvE.CanUse(out act)) return true; + + //Single target cast + if (BroilIvPvP.CanUse(out act)) return true; + if (BroilIiiPvE.CanUse(out act)) return true; + if (BroilIiPvE.CanUse(out act)) return true; + if (BroilPvE.CanUse(out act)) return true; + if (RuinPvE.CanUse(out act)) return true; + + //Single Instant for when moving. + if (RuinIiPvE.CanUse(out act)) return true; + + //Add dot while moving. + if (BiolysisPvE.CanUse(out act, skipStatusProvideCheck: DOTUpkeep)) return true; + if (BioIiPvE.CanUse(out act, skipStatusProvideCheck: DOTUpkeep)) return true; + if (BioPvE.CanUse(out act, skipStatusProvideCheck: DOTUpkeep)) return true; + + return base.GeneralGCD(out act); + } + #endregion + + #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); + #endregion +} \ No newline at end of file diff --git a/BasicRotations/Healer/SGE_Default.cs b/BasicRotations/Healer/SGE_Default.cs new file mode 100644 index 000000000..73cbb7a89 --- /dev/null +++ b/BasicRotations/Healer/SGE_Default.cs @@ -0,0 +1,433 @@ +namespace DefaultRotations.Healer; + +[Rotation("Default", CombatType.PvE, GameVersion = "7.05")] +[SourceCode(Path = "main/BasicRotations/Healer/SGE_Default.cs")] +[Api(4)] +public sealed class SGE_Default : SageRotation +{ + #region Config Options + [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; + + [RotationConfig(CombatType.PvE, Name = "Enable Swiftcast Restriction Logic to attempt to prevent actions other than Raise when you have swiftcast")] + public bool SwiftLogic { get; set; } = true; + + [Range(0, 1, ConfigUnitType.Percent)] + [RotationConfig(CombatType.PvE, Name = "Health threshold party member needs to be to use Taurochole")] + public float TaurocholeHeal { get; set; } = 0.8f; + + [Range(0, 1, ConfigUnitType.Percent)] + [RotationConfig(CombatType.PvE, Name = "Health threshold party member needs to be to use Soteria")] + public float SoteriaHeal { get; set; } = 0.85f; + + [Range(0, 1, ConfigUnitType.Percent)] + [RotationConfig(CombatType.PvE, Name = "Average health threshold party members need to be to use Holos")] + public float HolosHeal { get; set; } = 0.5f; + + [Range(0, 1, ConfigUnitType.Percent)] + [RotationConfig(CombatType.PvE, Name = "Health threshold tank party member needs to use Zoe")] + public float ZoeHeal { get; set; } = 0.6f; + + [Range(0, 1, ConfigUnitType.Percent)] + [RotationConfig(CombatType.PvE, Name = "Health threshold party member needs to be to use an OGCD Heal while not holding addersgal stacks")] + public float OGCDHeal { get; set; } = 0.20f; + + [Range(0, 1, ConfigUnitType.Percent)] + [RotationConfig(CombatType.PvE, Name = "Health threshold tank party member needs to use an OGCD Heal on Tanks while not holding addersgal stacks")] + public float OGCDTankHeal { get; set; } = 0.65f; + + [Range(0, 1, ConfigUnitType.Percent)] + [RotationConfig(CombatType.PvE, Name = "Health threshold party member needs to be to use Krasis")] + public float KrasisHeal { get; set; } = 0.3f; + + [Range(0, 1, ConfigUnitType.Percent)] + [RotationConfig(CombatType.PvE, Name = "Health threshold tank party member needs to use Krasis")] + public float KrasisTankHeal { get; set; } = 0.7f; + + [Range(0, 1, ConfigUnitType.Percent)] + [RotationConfig(CombatType.PvE, Name = "Health threshold party member needs to be to use Pneuma as a ST heal")] + public float PneumaSTPartyHeal { get; set; } = 0.2f; + + [Range(0, 1, ConfigUnitType.Percent)] + [RotationConfig(CombatType.PvE, Name = "Health threshold tank party member needs to use Pneuma as a ST heal")] + public float PneumaSTTankHeal { get; set; } = 0.6f; + + [Range(0, 1, ConfigUnitType.Percent)] + [RotationConfig(CombatType.PvE, Name = "Average health threshold party members need to be to use Pneuma as an AOE heal")] + public float PneumaAOEPartyHeal { get; set; } = 0.65f; + + [Range(0, 1, ConfigUnitType.Percent)] + [RotationConfig(CombatType.PvE, Name = "Health threshold tank party member needs to use Pneuma as an AOE heal")] + public float PneumaAOETankHeal { get; set; } = 0.6f; + + #endregion + + #region Countdown Logic + protected override IAction? CountDownAction(float remainTime) + { + if (remainTime < DosisPvE.Info.CastTime + CountDownAhead + && DosisPvE.CanUse(out var act)) return act; + if (remainTime <= 3 && UseBurstMedicine(out act)) return act; + return base.CountDownAction(remainTime); + } + #endregion + + #region oGCD Logic + [RotationDesc(ActionID.PsychePvE)] + protected override bool AttackAbility(IAction nextGCD, out IAction? act) + { + if (PsychePvE.CanUse(out act)) return true; + + return base.AttackAbility(nextGCD, out act); + } + + protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) + { + if (base.EmergencyAbility(nextGCD, out act)) return true; + + if (nextGCD.IsTheSameTo(false, PneumaPvE, EukrasianDiagnosisPvE, + EukrasianPrognosisPvE, EukrasianPrognosisIiPvE, DiagnosisPvE, PrognosisPvE)) + { + if (ZoePvE.CanUse(out act)) return true; + } + + if (nextGCD.IsTheSameTo(false, PneumaPvE, EukrasianDiagnosisPvE, + EukrasianPrognosisPvE, EukrasianPrognosisIiPvE, DiagnosisPvE, PrognosisPvE)) + { + if (KrasisPvE.CanUse(out act)) return true; + } + + if (nextGCD.IsTheSameTo(false, PneumaPvE, EukrasianDiagnosisPvE, + EukrasianPrognosisPvE, EukrasianPrognosisIiPvE, DiagnosisPvE, PrognosisPvE)) + { + if (PhilosophiaPvE.CanUse(out act)) return true; + } + + return base.EmergencyAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.PanhaimaPvE, ActionID.KeracholePvE, ActionID.HolosPvE)] + protected override bool DefenseAreaAbility(IAction nextGCD, out IAction? act) + { + if (Addersgall <= 1) + { + if (PanhaimaPvE.CanUse(out act)) return true; + } + + if (KeracholePvE.CanUse(out act)) return true; + + if (HolosPvE.CanUse(out act)) return true; + + return base.DefenseAreaAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.HaimaPvE, ActionID.TaurocholePvE, ActionID.PanhaimaPvE, ActionID.KeracholePvE, ActionID.HolosPvE)] + protected override bool DefenseSingleAbility(IAction nextGCD, out IAction? act) + { + if (Addersgall <= 1) + { + if (HaimaPvE.CanUse(out act)) return true; + } + + if (TaurocholePvE.CanUse(out act) && TaurocholePvE.Target.Target?.GetHealthRatio() < TaurocholeHeal) return true; + + if (Addersgall <= 1) + { + if ((!HaimaPvE.EnoughLevel || HaimaPvE.Cooldown.ElapsedAfter(20)) && PanhaimaPvE.CanUse(out act)) return true; + } + + if ((!TaurocholePvE.EnoughLevel || TaurocholePvE.Cooldown.ElapsedAfter(20)) && KeracholePvE.CanUse(out act)) return true; + + if (HolosPvE.CanUse(out act)) return true; + + return base.DefenseSingleAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.KeracholePvE, ActionID.PhysisPvE, ActionID.HolosPvE, ActionID.IxocholePvE)] + protected override bool HealAreaAbility(IAction nextGCD, out IAction? act) + { + if (PhysisIiPvE.CanUse(out act)) return true; + if (!PhysisIiPvE.EnoughLevel && PhysisPvE.CanUse(out act)) return true; + + if (KeracholePvE.CanUse(out act) && EnhancedKeracholeTrait.EnoughLevel) return true; + + if (HolosPvE.CanUse(out act) && PartyMembersAverHP < HolosHeal) return true; + + if (IxocholePvE.CanUse(out act)) return true; + + if (KeracholePvE.CanUse(out act)) return true; + + return base.HealAreaAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.TaurocholePvE, ActionID.KeracholePvE, ActionID.DruocholePvE, ActionID.HolosPvE, ActionID.PhysisPvE, ActionID.PanhaimaPvE)] + protected override bool HealSingleAbility(IAction nextGCD, out IAction? act) + { + if (TaurocholePvE.CanUse(out act)) return true; + + if (KeracholePvE.CanUse(out act) && EnhancedKeracholeTrait.EnoughLevel) return true; + + 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; + + + var tank = PartyMembers.GetJobCategory(JobRole.Tank); + if (Addersgall < 1 && (tank.Any(t => t.GetHealthRatio() < OGCDTankHeal) || PartyMembers.Any(b => b.GetHealthRatio() < OGCDHeal))) + { + 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 (tank.Any(t => t.GetHealthRatio() < ZoeHeal)) + { + if (ZoePvE.CanUse(out act)) return true; + } + + if (tank.Any(t => t.GetHealthRatio() < KrasisTankHeal) || PartyMembers.Any(b => b.GetHealthRatio() < KrasisHeal)) + { + if (KrasisPvE.CanUse(out act)) return true; + } + + if (KeracholePvE.CanUse(out act)) return true; + + return base.HealSingleAbility(nextGCD, out 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 + if (!InCombat && !Player.HasStatus(true, StatusID.Kardia) && KardiaPvE.CanUse(out act)) return true; + + if (KardiaPvE.CanUse(out act)) return true; + + if (Addersgall <= 1 && RhizomataPvE.CanUse(out act)) return true; + + if (SoteriaPvE.CanUse(out act) && PartyMembers.Any(b => b.HasStatus(true, StatusID.Kardion) && b.GetHealthRatio() < HealthSingleAbility)) return true; + + if (PepsisPvE.CanUse(out act)) return true; + + return base.GeneralAbility(nextGCD, out act); + } + #endregion + + #region GCD Logic + protected override bool DefenseAreaGCD(out IAction? act) + { + act = null; + + if (HasSwift && SwiftLogic && EgeiroPvE.CanUse(out _)) return false; + + if (EukrasianPrognosisIiPvE.CanUse(out act)) + { + if (EukrasianPrognosisIiPvE.Target.Target?.HasStatus(false, + StatusID.EukrasianDiagnosis, + StatusID.EukrasianPrognosis, + StatusID.Galvanize + ) ?? false) return false; + + if (EukrasiaPvE.CanUse(out act)) return true; + + act = EukrasianPrognosisIiPvE; + return true; + } + + if (!EukrasianPrognosisIiPvE.EnoughLevel && EukrasianPrognosisPvE.CanUse(out act)) + { + if (EukrasianPrognosisPvE.Target.Target?.HasStatus(false, + StatusID.EukrasianDiagnosis, + StatusID.EukrasianPrognosis, + StatusID.Galvanize + ) ?? false) return false; + + if (EukrasiaPvE.CanUse(out act)) return true; + + act = EukrasianPrognosisPvE; + return true; + } + + return base.DefenseAreaGCD(out act); + } + + [RotationDesc(ActionID.EukrasianDiagnosisPvE)] + protected override bool DefenseSingleGCD(out IAction? act) + { + act = null; + + if (HasSwift && SwiftLogic && EgeiroPvE.CanUse(out _)) return false; + + if (EukrasianDiagnosisPvE.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; + return true; + } + + return base.DefenseSingleGCD(out act); + } + + [RotationDesc(ActionID.PneumaPvE, ActionID.PrognosisPvE, ActionID.EukrasianPrognosisPvE, ActionID.EukrasianPrognosisIiPvE)] + protected override bool HealAreaGCD(out IAction? act) + { + act = null; + + if (HasSwift && SwiftLogic && EgeiroPvE.CanUse(out _)) return false; + + if (PartyMembersAverHP < PneumaAOEPartyHeal || DyskrasiaPvE.CanUse(out _) && PartyMembers.GetJobCategory(JobRole.Tank).Any(t => t.GetHealthRatio() < PneumaAOETankHeal)) + { + if (PneumaPvE.CanUse(out act)) return true; + } + + if (Player.HasStatus(false, StatusID.EukrasianDiagnosis, StatusID.EukrasianPrognosis, StatusID.Galvanize)) + { + if (PrognosisPvE.CanUse(out act)) return true; + } + + if (EukrasianPrognosisIiPvE.CanUse(out _)) + { + if (EukrasiaPvE.CanUse(out act)) return true; + act = EukrasianPrognosisIiPvE; + return true; + } + + if (!EukrasianPrognosisIiPvE.EnoughLevel && EukrasianPrognosisPvE.CanUse(out _)) + { + if (EukrasiaPvE.CanUse(out act)) return true; + act = EukrasianPrognosisPvE; + return true; + } + + return base.HealAreaGCD(out act); + } + + [RotationDesc(ActionID.DiagnosisPvE)] + protected override bool HealSingleGCD(out IAction? act) + { + act = null; + + if (HasSwift && SwiftLogic && EgeiroPvE.CanUse(out _)) return false; + + if (DiagnosisPvE.CanUse(out _) && !EukrasianDiagnosisPvE.CanUse(out _, skipCastingCheck: true) && InCombat) + { + if (DiagnosisPvE.CanUse(out act)) + { + return true; + } + } + return base.HealSingleGCD(out act); + } + + protected override bool GeneralGCD(out IAction? act) + { + act = null; + + if (HasSwift && SwiftLogic && EgeiroPvE.CanUse(out _)) return false; + + 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; + + if (PartyMembers.Any(b => b.GetHealthRatio() < PneumaSTPartyHeal && !b.IsDead) || PartyMembers.GetJobCategory(JobRole.Tank).Any(t => t.GetHealthRatio() < PneumaSTTankHeal && !t.IsDead)) + { + if (PneumaPvE.CanUse(out act)) return true; + } + + 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 (DyskrasiaPvE.CanUse(out _)) + { + if (EukrasianDyskrasiaPvE.EnoughLevel && (EukrasianDyskrasiaPvE.Target.Target?.WillStatusEnd(3, true, EukrasianDyskrasiaPvE.Setting.TargetStatusProvide ?? []) ?? false) && InCombat) + { + StatusHelper.StatusOff(StatusID.Eukrasia); + } + 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) + { + StatusHelper.StatusOff(StatusID.Eukrasia); + } + if (DosisPvE.CanUse(out act)) + { + return true; + } + } + + return base.GeneralGCD(out act); + } + #endregion + + #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); + #endregion +} diff --git a/BasicRotations/Healer/WHM_Default.cs b/BasicRotations/Healer/WHM_Default.cs new file mode 100644 index 000000000..5527ff4e6 --- /dev/null +++ b/BasicRotations/Healer/WHM_Default.cs @@ -0,0 +1,255 @@ +using System.ComponentModel; + +namespace DefaultRotations.Healer; + +[Rotation("Default", CombatType.PvE, GameVersion = "7.05")] +[SourceCode(Path = "main/BasicRotations/Healer/WHM_Default.cs")] +[Api(4)] +public sealed class WHM_Default : WhiteMageRotation +{ + #region Config Options + [RotationConfig(CombatType.PvE, Name = "Enable Swiftcast Restriction Logic to attempt to prevent actions other than Raise when you have swiftcast")] + public bool SwiftLogic { 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; + + [RotationConfig(CombatType.PvE, Name = "Use DOT while moving even if it does not need refresh (disabling is a damage down)")] + public bool DOTUpkeep { get; set; } = true; + + [RotationConfig(CombatType.PvE, Name = "Use Lily at max stacks.")] + public bool UseLilyWhenFull { get; set; } = true; + + [RotationConfig(CombatType.PvE, Name = "Regen on Tank at 5 seconds remaining on Prepull Countdown.")] + public bool UsePreRegen { get; set; } = true; + + [RotationConfig(CombatType.PvE, Name = "Use Divine Carress as soon as its available")] + public bool UseDivine { get; set; } = false; + + [RotationConfig(CombatType.PvE, Name = "Use Asylum as soon a single player heal (i.e. tankbusters) while moving, in addition to normal logic")] + public bool AsylumSingle { get; set; } = false; + + [Range(0, 1, ConfigUnitType.Percent)] + [RotationConfig(CombatType.PvE, Name = "Minimum health threshold party member needs to be to use Benediction")] + public float BenedictionHeal { get; set; } = 0.3f; + + [Range(0, 1, ConfigUnitType.Percent)] + [RotationConfig(CombatType.PvE, Name = "If a party member's health drops below this percentage, the Regen healing ability will not be used on them")] + public float RegenHeal { get; set; } = 0.3f; + + [Range(0, 10000, ConfigUnitType.None, 100)] + [RotationConfig(CombatType.PvE, Name = "Casting cost requirement for Thin Air to be used")] + + public float ThinAirNeed { get; set; } = 1000; + + [RotationConfig(CombatType.PvE, Name = "How to manage the last thin air charge")] + public ThinAirUsageStrategy ThinAirLastChargeUsage { get; set; } = ThinAirUsageStrategy.ReserveLastChargeForRaise; + + public enum ThinAirUsageStrategy : byte + { + [Description("Use all thin air charges on expensive spells")] + UseAllCharges, + + [Description("Reserve the last charge for raise")] + ReserveLastChargeForRaise, + + [Description("Reserve the last charge")] + ReserveLastCharge, + } + #endregion + + #region Countdown Logic + protected override IAction? CountDownAction(float remainTime) + { + if (remainTime < StonePvE.Info.CastTime + CountDownAhead + && StonePvE.CanUse(out var act)) return act; + + if (UsePreRegen && remainTime <= 5 && remainTime > 3) + { + if (RegenPvE.CanUse(out act)) return act; + if (DivineBenisonPvE.CanUse(out act)) return act; + } + return base.CountDownAction(remainTime); + } + #endregion + + #region oGCD Logic + protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) + { + if (Player.WillStatusEndGCD(0, 3, true, StatusID.DivineGrace) && DivineCaressPvE.CanUse(out act)) return true; + + bool useLastThinAirCharge = ThinAirLastChargeUsage == ThinAirUsageStrategy.UseAllCharges || (ThinAirLastChargeUsage == ThinAirUsageStrategy.ReserveLastChargeForRaise && nextGCD == RaisePvE); + if (nextGCD is IBaseAction action && action.Info.MPNeed >= ThinAirNeed && + ThinAirPvE.CanUse(out act, usedUp: useLastThinAirCharge)) return true; + + if (nextGCD.IsTheSameTo(true, AfflatusRapturePvE, MedicaPvE, MedicaIiPvE, CureIiiPvE) + && (MergedStatus.HasFlag(AutoStatus.HealAreaSpell) || MergedStatus.HasFlag(AutoStatus.HealSingleSpell))) + { + if (PlenaryIndulgencePvE.CanUse(out act)) return true; + } + + return base.EmergencyAbility(nextGCD, out act); + } + + protected override bool GeneralAbility(IAction nextGCD, out IAction? act) + { + if (UseDivine && DivineCaressPvE.CanUse(out act)) return true; + return base.GeneralAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.TemperancePvE, ActionID.LiturgyOfTheBellPvE)] + protected override bool DefenseAreaAbility(IAction nextGCD, out IAction? act) + { + act = null; + + if (TemperancePvE.Cooldown.IsCoolingDown && !TemperancePvE.Cooldown.WillHaveOneCharge(100) + || LiturgyOfTheBellPvE.Cooldown.IsCoolingDown && !LiturgyOfTheBellPvE.Cooldown.WillHaveOneCharge(160)) return false; + + if (TemperancePvE.CanUse(out act)) return true; + + if (DivineCaressPvE.CanUse(out act)) return true; + + if (LiturgyOfTheBellPvE.CanUse(out act, skipAoeCheck: true)) return true; + return base.DefenseAreaAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.DivineBenisonPvE, ActionID.AquaveilPvE)] + protected override bool DefenseSingleAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (DivineBenisonPvE.Cooldown.IsCoolingDown && !DivineBenisonPvE.Cooldown.WillHaveOneCharge(15) + || AquaveilPvE.Cooldown.IsCoolingDown && !AquaveilPvE.Cooldown.WillHaveOneCharge(52)) return false; + + if (DivineBenisonPvE.CanUse(out act)) return true; + + if (AquaveilPvE.CanUse(out act)) return true; + return base.DefenseSingleAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.AsylumPvE)] + protected override bool HealAreaAbility(IAction nextGCD, out IAction? act) + { + if (AsylumPvE.CanUse(out act)) return true; + return base.HealAreaAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.BenedictionPvE, ActionID.AsylumPvE, ActionID.DivineBenisonPvE, ActionID.TetragrammatonPvE)] + protected override bool HealSingleAbility(IAction nextGCD, out IAction? act) + { + if (BenedictionPvE.CanUse(out act) && + RegenPvE.Target.Target?.GetHealthRatio() < BenedictionHeal) return true; + + if (AsylumSingle && !IsMoving && AsylumPvE.CanUse(out act)) return true; + + if (DivineBenisonPvE.CanUse(out act)) return true; + + if (TetragrammatonPvE.CanUse(out act, usedUp: true)) return true; + return base.HealSingleAbility(nextGCD, out act); + } + + protected override bool AttackAbility(IAction nextGCD, out IAction? act) + { + if (InCombat) + { + if (PresenceOfMindPvE.CanUse(out act)) return true; + if (AssizePvE.CanUse(out act, skipAoeCheck: true)) return true; + } + + return base.AttackAbility(nextGCD, out act); + } + #endregion + + #region GCD Logic + + [RotationDesc(ActionID.AfflatusRapturePvE, ActionID.MedicaIiPvE, ActionID.CureIiiPvE, ActionID.MedicaPvE)] + protected override bool HealAreaGCD(out IAction? act) + { + act = null; + + if (HasSwift && SwiftLogic && RaisePvE.CanUse(out _)) return false; + + if (AfflatusRapturePvE.CanUse(out act)) return true; + + int hasMedica2 = PartyMembers.Count((n) => n.HasStatus(true, StatusID.MedicaIi)); + + if (MedicaIiPvE.CanUse(out act) && hasMedica2 < PartyMembers.Count() / 2 && !IsLastAction(true, MedicaIiPvE)) return true; + + if (CureIiiPvE.CanUse(out act)) return true; + + if (MedicaPvE.CanUse(out act)) return true; + + return base.HealAreaGCD(out act); + } + + [RotationDesc(ActionID.AfflatusSolacePvE, ActionID.RegenPvE, ActionID.CureIiPvE, ActionID.CurePvE)] + protected override bool HealSingleGCD(out IAction? act) + { + act = null; + + if (HasSwift && SwiftLogic && RaisePvE.CanUse(out _)) return false; + + if (AfflatusSolacePvE.CanUse(out act)) return true; + + if (RegenPvE.CanUse(out act) && (RegenPvE.Target.Target?.GetHealthRatio() > RegenHeal)) return true; + + if (CureIiPvE.CanUse(out act)) return true; + + if (CurePvE.CanUse(out act)) return true; + + return base.HealSingleGCD(out act); + } + + protected override bool GeneralGCD(out IAction? act) + { + + act = null; + + if (HasSwift && SwiftLogic && RaisePvE.CanUse(out _)) return false; + + //if (NotInCombatDelay && RegenDefense.CanUse(out act)) return true; + + if (AfflatusMiseryPvE.CanUse(out act, skipAoeCheck: true)) return true; + + bool liliesNearlyFull = Lily == 2 && LilyTime > 13; + bool liliesFullNoBlood = Lily == 3; + if (UseLilyWhenFull && (liliesNearlyFull || liliesFullNoBlood) && AfflatusMiseryPvE.EnoughLevel && BloodLily < 3) + { + if (UseLily(out act)) return true; + } + + if (GlareIvPvE.CanUse(out act)) return true; + + if (HolyPvE.CanUse(out act)) return true; + + if (AeroPvE.CanUse(out act)) return true; + + if (StonePvE.CanUse(out act)) return true; + + if (Lily >= 2) + { + if (UseLily(out act)) return true; + } + + if (AeroPvE.CanUse(out act, skipStatusProvideCheck: DOTUpkeep)) return true; + + return base.GeneralGCD(out act); + } + #endregion + + #region Extra Methods + public WHM_Default() + { + AfflatusRapturePvE.Setting.RotationCheck = () => BloodLily < 3; + AfflatusSolacePvE.Setting.RotationCheck = () => BloodLily < 3; + } + 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); + + private bool UseLily(out IAction? act) + { + if (AfflatusRapturePvE.CanUse(out act, skipAoeCheck: true)) return true; + if (AfflatusSolacePvE.CanUse(out act)) return true; + return false; + } + #endregion +} diff --git a/BasicRotations/Limited Jobs/BLU_Default.cs b/BasicRotations/Limited Jobs/BLU_Default.cs new file mode 100644 index 000000000..e0b291eb5 --- /dev/null +++ b/BasicRotations/Limited Jobs/BLU_Default.cs @@ -0,0 +1,70 @@ +namespace DefaultRotations.Magical; + +[Rotation("DOES NOT WORK", CombatType.PvE, GameVersion = "7.05")] +[SourceCode(Path = "main/BasicRotations/Limited Jobs/BLU_Default.cs")] +[Api(4)] +public sealed class Blue_Default : BlueMageRotation +{ + #region Countdown logic + // Defines logic for actions to take during the countdown before combat starts. + protected override IAction? CountDownAction(float remainTime) + { + + return base.CountDownAction(remainTime); + } + #endregion + + #region Emergency Logic + // Determines emergency actions to take based on the next planned GCD action. + protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) + { + act = null; + + return base.EmergencyAbility(nextGCD, out act); + } + #endregion + + #region oGCD Logic + protected override bool AttackAbility(IAction nextGCD, out IAction? act) + { + act = null; + + + return base.AttackAbility(nextGCD, out act); + } + + protected override bool MoveForwardAbility(IAction nextGCD, out IAction? act) + { + act = null; + + + return base.MoveForwardAbility(nextGCD, out act); + } + #endregion + + #region GCD Logic + protected override bool MoveForwardGCD(out IAction? act) + { + act = null; + + return base.MoveForwardGCD(out act); + } + + protected override bool GeneralGCD(out IAction? act) + { + act = null; + if (TinglePvE.CanUse(out act)) return true; + if (WaterCannonPvE.CanUse(out act)) return true; + return base.GeneralGCD(out act); + } + #endregion + + #region Extra Methods + // Extra private helper methods for determining the usability of specific abilities under certain conditions. + // These methods simplify the main logic by encapsulating specific checks related to abilities' cooldowns and prerequisites. + //private bool CanUseExamplePvE(out IAction? act) + //{ + + //} + #endregion +} \ No newline at end of file diff --git a/BasicRotations/Limited Jobs/BSM_Default.cs b/BasicRotations/Limited Jobs/BSM_Default.cs new file mode 100644 index 000000000..a35aff04d --- /dev/null +++ b/BasicRotations/Limited Jobs/BSM_Default.cs @@ -0,0 +1,76 @@ +//namespace DefaultRotations.Ranged; + +//[Rotation("Default", CombatType.PvE, GameVersion = "7.0")] +//[SourceCode(Path = "main/BasicRotations/Limited Jobs/BSM_Default.cs")] +//[Api(1)] +//public sealed class BSM_Default : BeastmasterRotation +//{ +// #region Countdown logic +// // Defines logic for actions to take during the countdown before combat starts. +// protected override IAction? CountDownAction(float remainTime) +// { + +// return base.CountDownAction(remainTime); +// } +// #endregion + +// #region Emergency Logic +// // Determines emergency actions to take based on the next planned GCD action. +// protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) +// { +// act = null; + +// return base.EmergencyAbility(nextGCD, out act); +// } +// #endregion + +// #region oGCD Logic +// protected override bool AttackAbility(IAction nextGCD, out IAction? act) +// { +// act = null; + + +// return base.AttackAbility(nextGCD, out act); +// } + +// protected override bool MoveForwardAbility(IAction nextGCD, out IAction? act) +// { +// act = null; + + +// return base.MoveForwardAbility(nextGCD, out act); +// } +// #endregion + +// #region GCD Logic +// protected override bool MoveForwardGCD(out IAction? act) +// { +// act = null; + +// return base.MoveForwardGCD(out act); +// } + +// protected override bool GeneralGCD(out IAction? act) +// { +// act = null; + +// return base.GeneralGCD(out act); +// } + +// private bool AttackGCD(out IAction? act, bool burst) +// { +// act = null; + +// return false; +// } +// #endregion + +// #region Extra Methods +// // Extra private helper methods for determining the usability of specific abilities under certain conditions. +// // These methods simplify the main logic by encapsulating specific checks related to abilities' cooldowns and prerequisites. +// private bool CanUseExamplePvE(out IAction? act) +// { + +// } +// #endregion +//} \ No newline at end of file diff --git a/BasicRotations/Magical/BLM_Default.cs b/BasicRotations/Magical/BLM_Default.cs new file mode 100644 index 000000000..856d78ce2 --- /dev/null +++ b/BasicRotations/Magical/BLM_Default.cs @@ -0,0 +1,414 @@ +namespace DefaultRotations.Magical; + +[Rotation("Default", CombatType.PvE, GameVersion = "7.01")] +[SourceCode(Path = "main/BasicRotations/Magical/BLM_Default.cs")] +[Api(4)] +public class BLM_Default : BlackMageRotation +{ + #region Config Options + [RotationConfig(CombatType.PvE, Name = "Use Transpose to Astral Fire before Paradox")] + public bool UseTransposeForParadox { get; set; } = true; + + [RotationConfig(CombatType.PvE, Name = "Use Retrace when out of Leylines and standing still (Dangerous and Experimental)")] + public bool UseRetrace { get; set; } = false; + + [RotationConfig(CombatType.PvE, Name = "Extend Astral Fire time more conservatively (3 GCDs) (Default is 2 GCDs)")] + public bool ExtendTimeSafely { get; set; } = false; + + [RotationConfig(CombatType.PvE, Name = @"Use ""Double Paradox"" rotation [N15]")] + public bool UseN15 { get; set; } = false; + #endregion + + #region Additional oGCD Logic + + protected override IAction? CountDownAction(float remainTime) + { + IAction act; + if (remainTime < FireIiiPvE.Info.CastTime + CountDownAhead) + { + if (FireIiiPvE.CanUse(out act)) return act; + } + //if (remainTime <= 12 && SharpcastPvE.CanUse(out act, usedUp: true)) return act; + return base.CountDownAction(remainTime); + } + + [RotationDesc] + protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) + { + if (UseRetrace && RetracePvE.CanUse(out act)) return true; + //To Fire + if (CurrentMp >= 7200 && UmbralIceStacks == 2 && ParadoxPvE.EnoughLevel) + { + if ((HasFire || HasSwift) && TransposePvE.CanUse(out act)) return true; + } + if (nextGCD.IsTheSameTo(false, FireIiiPvE) && HasFire) + { + if (TransposePvE.CanUse(out act)) return true; + } + + //Using Manafont + if (InAstralFire) + { + if (CurrentMp == 0 && ManafontPvE.CanUse(out act)) return true; + //To Ice + if (NeedToTransposeGoIce(true) && TransposePvE.CanUse(out act)) return true; + } + + return base.EmergencyAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.AetherialManipulationPvE)] + protected override bool MoveForwardAbility(IAction nextGCD, out IAction? act) + { + if (AetherialManipulationPvE.CanUse(out act)) return true; + + return base.MoveForwardAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.BetweenTheLinesPvE)] + protected override bool MoveBackAbility(IAction nextGCD, out IAction? act) + { + if (BetweenTheLinesPvE.CanUse(out act)) return true; + + return base.MoveBackAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.ManawardPvE)] + protected override bool DefenseSingleAbility(IAction nextGCD, out IAction? act) + { + if (ManawardPvE.CanUse(out act)) return true; + return base.DefenseSingleAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.ManawardPvE, ActionID.AddlePvE)] + protected sealed override bool DefenseAreaAbility(IAction nextGCD, out IAction? act) + { + if (ManawardPvE.CanUse(out act)) return true; + if (AddlePvE.CanUse(out act)) return true; + return base.DefenseAreaAbility(nextGCD, out act); + } + #endregion + + #region oGCD Logic + [RotationDesc(ActionID.ManafontPvE, ActionID.TransposePvE)] + protected override bool GeneralAbility(IAction nextGCD, out IAction? act) + { + if (IsMoving && HasHostilesInRange && TriplecastPvE.CanUse(out act, usedUp: true)) return true; + + return base.GeneralAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.RetracePvE, ActionID.SwiftcastPvE, ActionID.TriplecastPvE, ActionID.AmplifierPvE)] + protected override bool AttackAbility(IAction nextGCD, out IAction? act) + { + if (InUmbralIce) + { + if (UmbralIceStacks == 2 && !HasFire + && !IsLastGCD(ActionID.ParadoxPvE)) + { + if (SwiftcastPvE.CanUse(out act)) return true; + if (TriplecastPvE.CanUse(out act, usedUp: true)) return true; + } + + if (UmbralIceStacks < 3 && LucidDreamingPvE.CanUse(out act)) return true; + } + + if (InAstralFire) + { + if (TriplecastPvE.CanUse(out act, gcdCountForAbility: 5)) return true; + } + + if (AmplifierPvE.CanUse(out act)) return true; + return base.AttackAbility(nextGCD, out act); + } + #endregion + + #region GCD Logic + protected override bool GeneralGCD(out IAction? act) + { + if (FlareStarPvE.CanUse(out act)) return true; + + if (InFireOrIce(out act, out var mustGo)) return true; + if (mustGo) return false; + + if (AddElementBase(out act)) return true; + if (ScathePvE.CanUse(out act)) return true; + if (MaintainStatus(out act)) return true; + return base.GeneralGCD(out act); + } + + private bool InFireOrIce(out IAction? act, out bool mustGo) + { + act = null; + mustGo = false; + if (InUmbralIce) + { + if (GoFire(out act)) return true; + if (MaintainIce(out act)) return true; + if (DoIce(out act)) return true; + } + if (InAstralFire) + { + if (GoIce(out act)) return true; + if (MaintainFire(out act)) return true; + if (DoFire(out act)) return true; + } + return false; + } + + private bool GoIce(out IAction? act) + { + act = null; + + if (!NeedToGoIce) return false; + + //Use Manafont or transpose. + if ((!ManafontPvE.Cooldown.IsCoolingDown || NeedToTransposeGoIce(false)) + && UseInstanceSpell(out act)) return true; + + //Go to Ice. + if (BlizzardIiPvE.CanUse(out act)) return true; + if (BlizzardIiiPvE.CanUse(out act)) return true; + if (TransposePvE.CanUse(out act)) return true; + if (BlizzardPvE.CanUse(out act)) return true; + return false; + } + + private bool MaintainIce(out IAction? act) + { + act = null; + if (UmbralIceStacks == 1) + { + if (BlizzardIiPvE.CanUse(out act)) return true; + + if (Player.Level == 90 && BlizzardPvE.CanUse(out act)) return true; + if (BlizzardIiiPvE.CanUse(out act)) return true; + } + if (UmbralIceStacks == 2 && Player.Level < 90) + { + if (BlizzardIiPvE.CanUse(out act)) return true; + if (BlizzardPvE.CanUse(out act)) return true; + } + return false; + } + + private bool DoIce(out IAction? act) + { + act = null; + + if (IsLastAction(ActionID.UmbralSoulPvE, ActionID.TransposePvE) + && IsParadoxActive && BlizzardPvE.CanUse(out act)) return true; + + if (UmbralIceStacks == 3 && UsePolyglot(out act)) return true; + + //Add Hearts + if (UmbralIceStacks == 3 && + BlizzardIvPvE.EnoughLevel && UmbralHearts < 3 && !IsLastGCD + (ActionID.BlizzardIvPvE, ActionID.FreezePvE)) + { + if (FreezePvE.CanUse(out act)) return true; + if (BlizzardIvPvE.CanUse(out act)) return true; + } + + if (AddThunder(out act, 5)) return true; + if (UmbralIceStacks == 2 && UsePolyglot(out act, 0)) return true; + + if (IsParadoxActive) + { + if (BlizzardPvE.CanUse(out act)) return true; + } + + if (BlizzardIiPvE.CanUse(out act)) return true; + if (BlizzardIvPvE.CanUse(out act)) return true; + if (BlizzardPvE.CanUse(out act)) return true; + return false; + } + + private bool GoFire(out IAction? act) + { + act = null; + + //Transpose line + if (UmbralIceStacks < 3) return false; + + //Need more MP + if (CurrentMp < 9600) return false; + + if (IsParadoxActive) + { + if (BlizzardPvE.CanUse(out act)) return true; + } + + //Go to Fire. + if (FireIiPvE.CanUse(out act)) return true; + if (FireIiiPvE.CanUse(out act)) return true; + if (TransposePvE.CanUse(out act)) return true; + if (FirePvE.CanUse(out act)) return true; + + return false; + } + + private bool MaintainFire(out IAction? act) + { + act = null; + switch (AstralFireStacks) + { + case 1: + if (FireIiPvE.CanUse(out act)) return true; + if (UseN15) + { + if (HasFire && FireIiiPvE.CanUse(out act)) return true; + if (IsParadoxActive && FirePvE.CanUse(out act)) return true; + } + if (FireIiiPvE.CanUse(out act)) return true; + break; + case 2: + if (FireIiPvE.CanUse(out act)) return true; + if (FirePvE.CanUse(out act)) return true; + break; + } + + if (ElementTimeEndAfterGCD(ExtendTimeSafely ? 3u : 2u)) + { + if (CurrentMp >= FirePvE.Info.MPNeed * 2 + 800 && FirePvE.CanUse(out act)) return true; + if (FlarePvE.CanUse(out act)) return true; + if (DespairPvE.CanUse(out act)) return true; + } + + return false; + } + + private bool DoFire(out IAction? act) + { + act = null; + if (UsePolyglot(out act)) return true; + + // Add thunder only at combat start. + if (CombatElapsedLess(5)) + { + if (AddThunder(out act, 0)) return true; + } + + if (TriplecastPvE.CanUse(out act)) return true; + + if (AddThunder(out act, 0) && Player.WillStatusEndGCD(1, 0, true, + StatusID.Thundercloud)) return true; + + if (UmbralHearts < 2 && FlarePvE.CanUse(out act)) return true; + if (FireIiPvE.CanUse(out act)) return true; + + if (CurrentMp >= FirePvE.Info.MPNeed + 800) + { + if (FireIvPvE.EnoughLevel) + { + if (FireIvPvE.CanUse(out act)) return true; + } + else if (HasFire) + { + if (FireIiiPvE.CanUse(out act)) return true; + } + if (FirePvE.CanUse(out act)) return true; + } + + if (DespairPvE.CanUse(out act)) return true; + + return false; + } + + private bool UseInstanceSpell(out IAction? act) + { + act = null; + if (UsePolyglot(out act)) return true; + if (HasThunder && AddThunder(out act, 1)) return true; + if (UsePolyglot(out act, 0)) return true; + return false; + } + + private bool AddThunder(out IAction? act, uint gcdCount = 3) + { + act = null; + //Return if just used. + if (IsLastGCD(ActionID.ThunderPvE, ActionID.ThunderIiPvE, ActionID.ThunderIiiPvE, ActionID.ThunderIvPvE)) return false; + + //So long for thunder. + if (ThunderPvE.CanUse(out _) && (!ThunderPvE.Target.Target?.WillStatusEndGCD(gcdCount, 0, true, + StatusID.Thunder, StatusID.ThunderIi, StatusID.ThunderIii, StatusID.ThunderIv, StatusID.HighThunder_3872) ?? false)) + return false; + + if (ThunderIiPvE.CanUse(out act)) return true; + if (ThunderPvE.CanUse(out act)) return true; + + return false; + } + + private bool AddElementBase(out IAction? act) + { + act = null; + if (CurrentMp >= 7200) + { + if (FireIiPvE.CanUse(out act)) return true; + if (FireIiiPvE.CanUse(out act)) return true; + if (FirePvE.CanUse(out act)) return true; + } + else + { + if (BlizzardIiPvE.CanUse(out act)) return true; + if (BlizzardIiiPvE.CanUse(out act)) return true; + if (BlizzardPvE.CanUse(out act)) return true; + } + return false; + } + + private bool UsePolyglot(out IAction? act, uint gcdCount = 3) + { + act = null; + + if (gcdCount == 0 || IsPolyglotStacksMaxed && EnochianEndAfterGCD(gcdCount)) + { + if (FoulPvE.CanUse(out act)) return true; + if (XenoglossyPvE.CanUse(out act)) return true; + } + return false; + } + + private bool MaintainStatus(out IAction? act) + { + act = null; + if (CombatElapsedLess(6)) return false; + if (UmbralSoulPvE.CanUse(out act)) return true; + if (InAstralFire && TransposePvE.CanUse(out act)) return true; + if (UseTransposeForParadox && + InUmbralIce && !IsParadoxActive && UmbralIceStacks == 3 + && TransposePvE.CanUse(out act)) return true; + + return false; + } + + private bool NeedToGoIce + { + get + { + //Can use Despair. + if (DespairPvE.EnoughLevel && CurrentMp >= DespairPvE.Info.MPNeed) return false; + + //Can use Fire1 + if (FirePvE.EnoughLevel && CurrentMp >= FirePvE.Info.MPNeed) return false; + + return true; + } + } + + private bool NeedToTransposeGoIce(bool usedOne) + { + if (!NeedToGoIce) return false; + if (!ParadoxPvE.EnoughLevel) return false; + var compare = usedOne ? -1 : 0; + var count = PolyglotStacks; + if (count == compare++) return false; + if (count == compare++ && !EnochianEndAfterGCD(2)) return false; + if (count >= compare && (HasFire || SwiftcastPvE.Cooldown.WillHaveOneChargeGCD(2) || TriplecastPvE.Cooldown.WillHaveOneChargeGCD(2))) return true; + if (!HasFire && !SwiftcastPvE.Cooldown.WillHaveOneChargeGCD(2) && !TriplecastPvE.CanUse(out _, gcdCountForAbility: 8)) return false; + return true; + } + #endregion +} \ No newline at end of file diff --git a/BasicRotations/Magical/ICWA_PCT_BETA.cs b/BasicRotations/Magical/ICWA_PCT_BETA.cs new file mode 100644 index 000000000..1ad445453 --- /dev/null +++ b/BasicRotations/Magical/ICWA_PCT_BETA.cs @@ -0,0 +1,261 @@ +using System.ComponentModel; + +namespace DefaultRotations.Magical; + +[Rotation("IcWa PCT BETA", CombatType.PvE, GameVersion = "7.05", Description = "Kindly created and donated by Rabbs and further update made by IcWa")] +[SourceCode(Path = "main/BasicRotations/Magical/ICWA_PCT_BETA.cs")] +[Api(4)] +public sealed class IcWaPctBeta : PictomancerRotation +{ + #region Config Options + public override MedicineType MedicineType => MedicineType.Intelligence; + public static IBaseAction RainbowPrePull { get; } = new BaseAction((ActionID)34688); + [RotationConfig(CombatType.PvE, Name = "Use HolyInWhite or CometInBlack while moving")] + public bool HolyCometMoving { get; set; } = true; + [RotationConfig(CombatType.PvE, Name = "Use swifcast on (would advise weapon only - Creature can delay timings and f opener and reopener and landscape doesn't bring any bonus on dps.)")] + public MotifSwift MotifSwiftCast { get; set; } = MotifSwift.WeaponMotif; + [Range(1, 5, ConfigUnitType.None, 1)] + [RotationConfig(CombatType.PvE, Name = "Paint overcap protection. How many paint do you need to be at before using a paint?")] + public bool UseCapCometHoly { get; set; } = true; + [RotationConfig(CombatType.PvE, Name = "Use the paint overcap protection (will still use comet while moving if the setup is on)")] + public bool UseCapCometOnly { get; set; } = false; + [RotationConfig(CombatType.PvE, Name = "Use the paint overcap protection for comet only (will still use comet while moving if the setup is on)")] + public int HolyCometMax { get; set; } = 5; + public enum MotifSwift : byte + { + [Description("CreatureMotif")] CreatureMotif, + [Description("WeaponMotif")] WeaponMotif, + [Description("LandscapeMotif")] LandscapeMotif, + [Description("AllMotif")] AllMotif, + [Description("NoMotif(ManualSwifcast")] NoMotif + } + + #endregion + + #region Countdown logic + // Defines logic for actions to take during the countdown before combat starts. + protected override IAction? CountDownAction(float remainTime) + { + IAction act; + if (!InCombat) + { + if (!CreatureMotifDrawn) + { + if (PomMotifPvE.CanUse(out act) && CreatureMotifPvE.AdjustedID == PomMotifPvE.ID) return act; + if (WingMotifPvE.CanUse(out act) && CreatureMotifPvE.AdjustedID == WingMotifPvE.ID) return act; + if (ClawMotifPvE.CanUse(out act) && CreatureMotifPvE.AdjustedID == ClawMotifPvE.ID) return act; + if (MawMotifPvE.CanUse(out act) && CreatureMotifPvE.AdjustedID == MawMotifPvE.ID) return act; + } + if (!WeaponMotifDrawn) + { + if (HammerMotifPvE.CanUse(out act)) return act; + } + if (!LandscapeMotifDrawn) + { + if (StarrySkyMotifPvE.CanUse(out act) && !Player.HasStatus(true, StatusID.Hyperphantasia)) return act; + } + } + if (remainTime < RainbowDripPvE.Info.CastTime + CountDownAhead) + { + if (StrikingMusePvE.CanUse(out act, skipCastingCheck: true, skipStatusProvideCheck: true, skipComboCheck: true, skipAoeCheck: true, usedUp: true) && WeaponMotifDrawn) return act; + } + if (remainTime < RainbowDripPvE.Info.CastTime + 0.4f + CountDownAhead) + { + if (RainbowPrePull.CanUse(out act, skipAoeCheck: true, skipCastingCheck: true, skipStatusProvideCheck: true)) return act; + } + if (remainTime < FireInRedPvE.Info.CastTime + CountDownAhead && Player.Level < 92) + { + if (FireInRedPvE.CanUse(out act, skipAoeCheck: true, skipCastingCheck: true, skipStatusProvideCheck: true)) return act; + } + return base.CountDownAction(remainTime); + } + #endregion + + #region Additional oGCD Logic + protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) + { + if (InCombat) + { + switch (MotifSwiftCast) + { + case MotifSwift.CreatureMotif: + if (nextGCD == PomMotifPvE || nextGCD == WingMotifPvE || nextGCD == MawMotifPvE || nextGCD == ClawMotifPvE) + { + if (SwiftcastPvE.CanUse(out act)) return true; + } + break; + case MotifSwift.WeaponMotif: + if (nextGCD == HammerMotifPvE) + { + if (SwiftcastPvE.CanUse(out act)) return true; + } + break; + case MotifSwift.LandscapeMotif: + if (nextGCD == StarrySkyMotifPvE) + { + if (SwiftcastPvE.CanUse(out act)) return true; + } + break; + case MotifSwift.AllMotif: + if (nextGCD == PomMotifPvE || nextGCD == WingMotifPvE || nextGCD == MawMotifPvE || nextGCD == ClawMotifPvE) + { + if (SwiftcastPvE.CanUse(out act)) return true; + } + else if (nextGCD == HammerMotifPvE) + { + if (SwiftcastPvE.CanUse(out act)) return true; + } + else if (nextGCD == StarrySkyMotifPvE) + { + if (SwiftcastPvE.CanUse(out act)) return true; + } + break; + case MotifSwift.NoMotif: + break; + } + } + return base.EmergencyAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.SmudgePvE)] + protected override bool MoveForwardAbility(IAction nextGCD, out IAction? act) + { + if (SmudgePvE.CanUse(out act)) return true; + return base.AttackAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.AddlePvE, ActionID.TemperaCoatPvE, ActionID.TemperaGrassaPvE)] + protected override bool DefenseAreaAbility(IAction nextGCD, out IAction? act) + { + if (AddlePvE.CanUse(out act)) return true; + if (TemperaCoatPvE.CanUse(out act)) return true; + if (TemperaGrassaPvE.CanUse(out act)) return true; + return base.DefenseAreaAbility(nextGCD, out act); + } + #endregion + + #region oGCD Logic + protected override bool AttackAbility(IAction nextGCD, out IAction? act) + { + bool burstTimingCheckerStriking = !ScenicMusePvE.Cooldown.WillHaveOneCharge(60) || Player.HasStatus(true, StatusID.StarryMuse); + int adjustCombatTimeForOpener = Player.Level < 92 ? 2 : 5; + if (StarryMusePvE.CanUse(out act, skipCastingCheck: true, skipStatusProvideCheck: true, skipComboCheck: true, skipAoeCheck: true, usedUp: true) && CombatTime > adjustCombatTimeForOpener && IsBurst) return true; + if (CombatTime > adjustCombatTimeForOpener && StrikingMusePvE.CanUse(out act, skipCastingCheck: true, skipStatusProvideCheck: true, skipComboCheck: true, skipAoeCheck: true, usedUp: true) && burstTimingCheckerStriking) return true; + if (SubtractivePalettePvE.CanUse(out act) && !Player.HasStatus(true, StatusID.SubtractivePalette)) return true; + if (Player.HasStatus(true, StatusID.StarryMuse)) + { + if (FangedMusePvE.CanUse(out act, skipCastingCheck: true, skipStatusProvideCheck: true, skipComboCheck: true, skipAoeCheck: true, usedUp: true)) return true; + if (RetributionOfTheMadeenPvE.CanUse(out act, skipCastingCheck: true, skipStatusProvideCheck: true, skipComboCheck: true, skipAoeCheck: true, usedUp: true)) return true; + } + if (RetributionOfTheMadeenPvE.CanUse(out act, skipCastingCheck: true, skipStatusProvideCheck: true, skipComboCheck: true, skipAoeCheck: true, usedUp: true)) return true; + if (MogOfTheAgesPvE.CanUse(out act, skipCastingCheck: true, skipStatusProvideCheck: true, skipComboCheck: true, skipAoeCheck: true, usedUp: true)) return true; + if (StrikingMusePvE.CanUse(out act, skipCastingCheck: true, skipStatusProvideCheck: true, skipComboCheck: true, skipAoeCheck: true, usedUp: true) && burstTimingCheckerStriking) return true; + if (PomMusePvE.CanUse(out act, skipCastingCheck: true, skipStatusProvideCheck: true, skipComboCheck: true, skipAoeCheck: true, usedUp: true) && LivingMusePvE.AdjustedID == PomMusePvE.ID) return true; + if (WingedMusePvE.CanUse(out act, skipCastingCheck: true, skipStatusProvideCheck: true, skipComboCheck: true, skipAoeCheck: true, usedUp: true) && LivingMusePvE.AdjustedID == WingedMusePvE.ID) return true; + if (ClawedMusePvE.CanUse(out act, skipCastingCheck: true, skipStatusProvideCheck: true, skipComboCheck: true, skipAoeCheck: true, usedUp: true) && LivingMusePvE.AdjustedID == ClawedMusePvE.ID) return true; + return base.AttackAbility(nextGCD, out act); + } + #endregion + + #region GCD Logic + protected override bool GeneralGCD(out IAction? act) + { + //Opener requirements + if (CombatTime < 5) + { + if (HolyInWhitePvE.CanUse(out act, skipCastingCheck: true, skipAoeCheck: true) && Paint > 0) return true; + if (PomMotifPvE.CanUse(out act) && CreatureMotifPvE.AdjustedID == PomMotifPvE.ID) return true; + if (WingMotifPvE.CanUse(out act) && CreatureMotifPvE.AdjustedID == WingMotifPvE.ID) return true; + if (ClawMotifPvE.CanUse(out act) && CreatureMotifPvE.AdjustedID == ClawMotifPvE.ID) return true; + if (MawMotifPvE.CanUse(out act) && CreatureMotifPvE.AdjustedID == MawMotifPvE.ID) return true; + } + // some gcd priority + if (RainbowDripPvE.CanUse(out act, skipAoeCheck: true) && Player.HasStatus(true, StatusID.RainbowBright)) return true; + if (Player.HasStatus(true, StatusID.StarryMuse)) + { + if (CometInBlackPvE.CanUse(out act, skipCastingCheck: true, skipAoeCheck: true) && Paint > 0) return true; + } + if (StarPrismPvE.CanUse(out act, skipAoeCheck: true) && Player.HasStatus(true, StatusID.Starstruck)) return true; + if (PolishingHammerPvE.CanUse(out act, skipComboCheck: true)) return true; + if (HammerBrushPvE.CanUse(out act, skipComboCheck: true)) return true; + if (HammerStampPvE.CanUse(out act, skipComboCheck: true)) return true; + //Cast when not in fight or no target available + if (!InCombat) + { + if (PomMotifPvE.CanUse(out act) && CreatureMotifPvE.AdjustedID == PomMotifPvE.ID) return true; + if (WingMotifPvE.CanUse(out act) && CreatureMotifPvE.AdjustedID == WingMotifPvE.ID) return true; + if (ClawMotifPvE.CanUse(out act) && CreatureMotifPvE.AdjustedID == ClawMotifPvE.ID) return true; + if (MawMotifPvE.CanUse(out act) && CreatureMotifPvE.AdjustedID == MawMotifPvE.ID) return true; + if (HammerMotifPvE.CanUse(out act)) return true; + if (StarrySkyMotifPvE.CanUse(out act) && !Player.HasStatus(true, StatusID.Hyperphantasia)) return true; + if (RainbowDripPvE.CanUse(out act)) return true; + } + // timings for motif casting + if (ScenicMusePvE.Cooldown.RecastTimeRemainOneCharge <= 15 && !Player.HasStatus(true, StatusID.StarryMuse) && !Player.HasStatus(true, StatusID.Hyperphantasia)) + { + if (StarrySkyMotifPvE.CanUse(out act) && !Player.HasStatus(true, StatusID.Hyperphantasia)) return true; + } + if ((LivingMusePvE.Cooldown.HasOneCharge || LivingMusePvE.Cooldown.RecastTimeRemainOneCharge <= CreatureMotifPvE.Info.CastTime * 1.7) && !Player.HasStatus(true, StatusID.StarryMuse) && !Player.HasStatus(true, StatusID.Hyperphantasia)) + { + if (PomMotifPvE.CanUse(out act) && CreatureMotifPvE.AdjustedID == PomMotifPvE.ID) return true; + if (WingMotifPvE.CanUse(out act) && CreatureMotifPvE.AdjustedID == WingMotifPvE.ID) return true; + if (ClawMotifPvE.CanUse(out act) && CreatureMotifPvE.AdjustedID == ClawMotifPvE.ID) return true; + if (MawMotifPvE.CanUse(out act) && CreatureMotifPvE.AdjustedID == MawMotifPvE.ID) return true; + ; + } + if ((SteelMusePvE.Cooldown.HasOneCharge || SteelMusePvE.Cooldown.RecastTimeRemainOneCharge <= WeaponMotifPvE.Info.CastTime) && !Player.HasStatus(true, StatusID.StarryMuse) && !Player.HasStatus(true, StatusID.Hyperphantasia)) + { + if (HammerMotifPvE.CanUse(out act)) return true; + } + bool isMovingAndSwift = IsMoving && !Player.HasStatus(true, StatusID.Swiftcast); + // white/black paint use while moving + if (isMovingAndSwift) + { + if (PolishingHammerPvE.CanUse(out act, skipComboCheck: true)) return true; + if (HammerBrushPvE.CanUse(out act, skipComboCheck: true)) return true; + if (HammerStampPvE.CanUse(out act, skipComboCheck: true)) return true; + if (HolyCometMoving) + { + if (CometInBlackPvE.CanUse(out act, skipCastingCheck: true, skipAoeCheck: true)) return true; + if (HolyInWhitePvE.CanUse(out act, skipCastingCheck: true, skipAoeCheck: true)) return true; + } + } + // When in swift management + if (Player.HasStatus(true, StatusID.Swiftcast) && (!LandscapeMotifDrawn || !CreatureMotifDrawn || !WeaponMotifDrawn)) + { + bool creature = MotifSwiftCast is MotifSwift.CreatureMotif or MotifSwift.AllMotif; + bool weapon = MotifSwiftCast is MotifSwift.WeaponMotif or MotifSwift.AllMotif; + bool landscape = MotifSwiftCast is MotifSwift.LandscapeMotif or MotifSwift.AllMotif; + if (PomMotifPvE.CanUse(out act, skipCastingCheck: creature) && CreatureMotifPvE.AdjustedID == PomMotifPvE.ID && creature) return true; + if (WingMotifPvE.CanUse(out act, skipCastingCheck: creature) && CreatureMotifPvE.AdjustedID == WingMotifPvE.ID && creature) return true; + if (ClawMotifPvE.CanUse(out act, skipCastingCheck: creature) && CreatureMotifPvE.AdjustedID == ClawMotifPvE.ID && creature) return true; + if (MawMotifPvE.CanUse(out act, skipCastingCheck: creature) && CreatureMotifPvE.AdjustedID == MawMotifPvE.ID && creature) return true; + if (HammerMotifPvE.CanUse(out act, skipCastingCheck: weapon) && weapon) return true; + if (StarrySkyMotifPvE.CanUse(out act, skipCastingCheck: landscape) && !Player.HasStatus(true, StatusID.Hyperphantasia) && landscape) return true; + } + //white paint over cap protection + if ((Paint == HolyCometMax && !Player.HasStatus(true, StatusID.StarryMuse)) && (UseCapCometHoly || UseCapCometOnly)) + { + if (CometInBlackPvE.CanUse(out act, skipCastingCheck: true, skipAoeCheck: true)) return true; + if (HolyInWhitePvE.CanUse(out act, skipCastingCheck: true, skipAoeCheck: true) && !UseCapCometOnly) return true; + } + //aoe sub + if (ThunderIiInMagentaPvE.CanUse(out act)) return true; + if (StoneIiInYellowPvE.CanUse(out act)) return true; + if (BlizzardIiInCyanPvE.CanUse(out act)) return true; + //aoe normal + if (WaterIiInBluePvE.CanUse(out act)) return true; + if (AeroIiInGreenPvE.CanUse(out act)) return true; + if (FireIiInRedPvE.CanUse(out act)) return true; + //single target sub + if (ThunderInMagentaPvE.CanUse(out act)) return true; + if (StoneInYellowPvE.CanUse(out act)) return true; + if (BlizzardInCyanPvE.CanUse(out act)) return true; + //single target normal + if (WaterInBluePvE.CanUse(out act)) return true; + if (AeroInGreenPvE.CanUse(out act)) return true; + if (FireInRedPvE.CanUse(out act)) return true; + return base.GeneralGCD(out act); + } + #endregion +} \ No newline at end of file diff --git a/BasicRotations/Magical/PCT_Default.cs b/BasicRotations/Magical/PCT_Default.cs new file mode 100644 index 000000000..93659ba68 --- /dev/null +++ b/BasicRotations/Magical/PCT_Default.cs @@ -0,0 +1,183 @@ +namespace DefaultRotations.Magical; + +[Rotation("Default", CombatType.PvE, GameVersion = "7.05")] +[SourceCode(Path = "main/BasicRotations/Magical/PCT_Default.cs")] +[Api(4)] +public sealed class PCT_Default : PictomancerRotation +{ + private const float CountdownBuffer = 0.4f; + + #region Config Options + [RotationConfig(CombatType.PvE, Name = "Use HolyInWhite or CometInBlack while moving")] + public bool HolyCometMoving { get; set; } = true; + + [Range(1, 5, ConfigUnitType.None, 1)] + [RotationConfig(CombatType.PvE, Name = "Paint overcap protection. How many paint do you need to be at before using a paint? (Setting is ignored when you have Hyperphantasia)")] + public int HolyCometMax { get; set; } = 5; + + [RotationConfig(CombatType.PvE, Name = "Use swiftcast on Rainbow Drip (Priority over below settings)")] + public bool RainbowDripSwift { get; set; } = true; + + [RotationConfig(CombatType.PvE, Name = "Use swiftcast on Motif")] + public bool MotifSwiftCastSwift { get; set; } = true; + + [RotationConfig(CombatType.PvE, Name = "Which Motif to use swiftcast on")] + public CanvasFlags MotifSwiftCast { get; set; } = CanvasFlags.Weapon; + + #endregion + + #region Countdown logic + // Defines logic for actions to take during the countdown before combat starts. + protected override IAction? CountDownAction(float remainTime) + { + IAction act; + if (!InCombat) + { + if (!CreatureMotifDrawn && PomMotifPvE.CanUse(out act, skipCastingCheck: true)) return act; + if (!WeaponMotifDrawn && HammerMotifPvE.CanUse(out act, skipCastingCheck: true)) return act; + if (!LandscapeMotifDrawn && StarrySkyMotifPvE.CanUse(out act, skipCastingCheck: true) && !Player.HasStatus(true, StatusID.Hyperphantasia)) return act; + } + + if (remainTime < RainbowDripPvE.Info.CastTime + CountdownBuffer + CountDownAhead && RainbowDripPvE.CanUse(out act, skipCastingCheck: true)) + { + return act; + } + + return base.CountDownAction(remainTime); + } + #endregion + + #region Additional oGCD Logic + + protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) + { + if (InCombat) + { + if (RainbowDripSwift && nextGCD == RainbowDripPvE && SwiftcastPvE.CanUse(out act)) return true; + + if (MotifSwiftCastSwift) + { + if (MotifSwiftCast switch + { + CanvasFlags.Pom => nextGCD == PomMotifPvE, + CanvasFlags.Wing => nextGCD == WingMotifPvE, + CanvasFlags.Claw => nextGCD == ClawMotifPvE, + CanvasFlags.Maw => nextGCD == MawMotifPvE, + CanvasFlags.Weapon => nextGCD == HammerMotifPvE, + CanvasFlags.Landscape => nextGCD == StarrySkyMotifPvE, + _ => false + } && SwiftcastPvE.CanUse(out act)) + { + return true; + } + } + } + return base.EmergencyAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.SmudgePvE)] + protected override bool MoveForwardAbility(IAction nextGCD, out IAction? act) + { + if (SmudgePvE.CanUse(out act)) return true; + return base.MoveForwardAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.TemperaCoatPvE, ActionID.TemperaGrassaPvE, ActionID.AddlePvE)] + protected sealed override bool DefenseAreaAbility(IAction nextGCD, out IAction? act) + { + // Mitigations + if (TemperaCoatPvE.CanUse(out act)) return true; + if (TemperaGrassaPvE.CanUse(out act)) return true; + if (AddlePvE.CanUse(out act)) return true; + return base.DefenseAreaAbility(nextGCD, out act); + } + + #endregion + + #region oGCD Logic + + protected override bool AttackAbility(IAction nextGCD, out IAction? act) + { + // Bursts + if (SubtractivePalettePvE.CanUse(out act)) return true; + if (RetributionOfTheMadeenPvE.CanUse(out act)) return true; + if (FangedMusePvE.CanUse(out act, usedUp: true)) return true; + if (ClawedMusePvE.CanUse(out act, usedUp: true)) return true; + + //Expert Muses + if (MogOfTheAgesPvE.CanUse(out act)) return true; + if (WingedMusePvE.CanUse(out act, usedUp: true)) return true; + + //Advanced Muses + if (StarryMusePvE.CanUse(out act)) return true; + if (StrikingMusePvE.CanUse(out act)) return true; + if (ClawedMusePvE.CanUse(out act, usedUp: true)) return true; + if (WingedMusePvE.CanUse(out act, usedUp: true)) return true; + if (PomMusePvE.CanUse(out act, usedUp: true)) return true; + + //Basic Muses + //if (ScenicMusePvE.CanUse(out act)) return true; + //if (SteelMusePvE.CanUse(out act, usedUp: true)) return true; + //if (LivingMusePvE.CanUse(out act, usedUp: true)) return true; + return base.AttackAbility(nextGCD, out act); + } + #endregion + + #region GCD Logic + + protected override bool GeneralGCD(out IAction? act) + { + // Weapon Painting Burst + if (PolishingHammerPvE.CanUse(out act, skipComboCheck: true)) return true; + if (HammerBrushPvE.CanUse(out act, skipComboCheck: true)) return true; + if (HammerStampPvE.CanUse(out act, skipComboCheck: true)) return true; + + if (HolyCometMoving && IsMoving && HolyInWhitePvE.CanUse(out act)) return true; + + //Use up paint if in Hyperphantasia + if (Player.HasStatus(true, StatusID.Hyperphantasia) && CometInBlackPvE.CanUse(out act)) return true; + + //Paint overcap protection + if (Paint == HolyCometMax && HolyInWhitePvE.CanUse(out act)) return true; + + // Landscape Paining Burst + if (RainbowDripPvE.CanUse(out act)) return true; + if (StarPrismPvE.CanUse(out act)) return true; + + //Advanced Paintings + if (StarrySkyMotifPvE.CanUse(out act)) return true; + if (HammerMotifPvE.CanUse(out act)) return true; + if (MawMotifPvE.CanUse(out act)) return true; + if (ClawMotifPvE.CanUse(out act)) return true; + if (WingMotifPvE.CanUse(out act)) return true; + if (PomMotifPvE.CanUse(out act)) return true; + + //Basic Paintings + //if (LandscapeMotifPvE.CanUse(out act)) return true; + //if (WeaponMotifPvE.CanUse(out act)) return true; + //if (CreatureMotifPvE.CanUse(out act)) return true; + + //AOE Subtractive Inks + if (ThunderIiInMagentaPvE.CanUse(out act)) return true; + if (StoneIiInYellowPvE.CanUse(out act)) return true; + if (BlizzardIiInCyanPvE.CanUse(out act)) return true; + + //AOE Additive Inks + if (WaterIiInBluePvE.CanUse(out act)) return true; + if (AeroIiInGreenPvE.CanUse(out act)) return true; + if (FireIiInRedPvE.CanUse(out act)) return true; + + //ST Subtractive Inks + if (ThunderInMagentaPvE.CanUse(out act)) return true; + if (StoneInYellowPvE.CanUse(out act)) return true; + if (BlizzardInCyanPvE.CanUse(out act)) return true; + + //ST Additive Inks + if (WaterInBluePvE.CanUse(out act)) return true; + if (AeroInGreenPvE.CanUse(out act)) return true; + if (FireInRedPvE.CanUse(out act)) return true; + return base.GeneralGCD(out act); + } + + #endregion +} \ No newline at end of file diff --git a/BasicRotations/Magical/RDM_Default.cs b/BasicRotations/Magical/RDM_Default.cs new file mode 100644 index 000000000..eb410446c --- /dev/null +++ b/BasicRotations/Magical/RDM_Default.cs @@ -0,0 +1,273 @@ +namespace DefaultRotations.Magical; + +[Rotation("Default", CombatType.PvE, GameVersion = "7.05")] +[SourceCode(Path = "main/BasicRotations/Magical/RDM_Default.cs")] +[Api(4)] +public sealed class RDM_Default : RedMageRotation +{ + #region Config Options + private static BaseAction VerthunderStartUp { get; } = new BaseAction(ActionID.VerthunderPvE, false); + + [RotationConfig(CombatType.PvE, Name = "Use Vercure for Dualcast when out of combat.")] + public bool UseVercure { get; set; } = false; + + [RotationConfig(CombatType.PvE, Name = "Cast Reprise when moving with no instacast.")] + public bool RangedSwordplay { get; set; } = false; + + [RotationConfig(CombatType.PvE, Name = "DO NOT CAST EMBOLDEN/MANAFICATION OUTSIDE OF MELEE RANGE, I'M SERIOUS YOU HAVE TO MOVE UP FOR IT TO WORK IF THIS IS ON.")] + public bool AnyonesMeleeRule { get; set; } = false; + + //Fine, ill do it myself + [RotationConfig(CombatType.PvE, Name = "Cast manafication outside of embolden window (use at own risk).")] + public bool AnyoneManafication { get; set; } = false; + #endregion + + #region Countdown Logic + protected override IAction? CountDownAction(float remainTime) + { + if (remainTime < VerthunderStartUp.Info.CastTime + CountDownAhead + && VerthunderStartUp.CanUse(out var act)) return act; + + //Remove Swift + StatusHelper.StatusOff(StatusID.Dualcast); + StatusHelper.StatusOff(StatusID.Acceleration); + StatusHelper.StatusOff(StatusID.Swiftcast); + + return base.CountDownAction(remainTime); + } + #endregion + + #region oGCD Logic + protected override bool AttackAbility(IAction nextGCD, out IAction? act) + + //When we removed emergencyGCD vercure/verraise start overwriting all logic below. Need to do something about it. + + //No bugs in this section (mostly™). Extra Methods is fucked up tho, need to good look of experienced rotation dev. + + { + bool AnyoneInRange = AllHostileTargets.Any(hostile => hostile.DistanceToPlayer() <= 4); + + act = null; + + if (CombatElapsedLess(4)) return false; + + //COMMENT FOR MYSELF FROM FUTURE - WHY THE HELL EMBOLDEN DONT WORK WITHOUT skipAoeCheck:true??? + if (!AnyonesMeleeRule) + { + if (IsBurst && HasHostilesInRange && EmboldenPvE.CanUse(out act, skipAoeCheck: true)) return true; + } + else + { + if (IsBurst && AnyoneInRange && EmboldenPvE.CanUse(out act, skipAoeCheck: true)) return true; + } + + //If manafication usage OUTSIDE of embolden enabled. + if (AnyoneManafication) + { + if (AnyoneInRange && ManaficationPvE.CanUse(out act)) return true; + } + + //Use Manafication after embolden. + if (!AnyoneManafication && (Player.HasStatus(true, StatusID.Embolden) || IsLastAbility(ActionID.EmboldenPvE)) && + ManaficationPvE.CanUse(out act)) return true; + + //Swiftcast/Acceleration usage OLD VERSION + // if (ManaStacks == 0 && (BlackMana < 50 || WhiteMana < 50) + // && (CombatElapsedLess(4) || !ManaficationPvE.EnoughLevel || !ManaficationPvE.Cooldown.WillHaveOneChargeGCD(0, 1))) + // { + // if (InCombat && !Player.HasStatus(true, StatusID.VerfireReady, StatusID.VerstoneReady)) + // { + // if (SwiftcastPvE.CanUse(out act)) return true; + // if (AccelerationPvE.CanUse(out act, usedUp: true)) return true; + // } + // } + + //Melee combo interrupt protection (i hate this too) + bool checkmelee = IsLastGCD(new[] + { + ActionID.ResolutionPvE, + ActionID.ScorchPvE, + ActionID.VerflarePvE, + ActionID.VerholyPvE, + ActionID.RedoublementPvE, + ActionID.EnchantedRedoublementPvE, + ActionID.ZwerchhauPvE, + ActionID.EnchantedZwerchhauPvE, + ActionID.RipostePvE, + ActionID.EnchantedRipostePvE, + ActionID.EnchantedMoulinetTroisPvE, + ActionID.EnchantedMoulinetDeuxPvE, + 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 + }); + + //i really hate this. + bool ambatumelee = Player.HasStatus(true, StatusID.Manafication, StatusID.MagickedSwordplay); + + //Acceleration usage on rotation with saving 1 charge for movement + if (GrandImpactPvE.EnoughLevel && !checkmelee && !ambatumelee && //Check for enough level to use Grand Impact, or its pointless. + !Player.HasStatus(true, StatusID.Manafication, StatusID.MagickedSwordplay) && + !Player.HasStatus(true, StatusID.Dualcast) && AccelerationPvE.CanUse(out act)) return true; + + //Acceleration/Swiftcast usage on move + if (IsMoving && !Player.HasStatus(true, StatusID.Dualcast) && !checkmelee && !ambatumelee && + //Checks for not override previous acceleration and lose grand impact + !Player.HasStatus(true, StatusID.Acceleration) && + !Player.HasStatus(true, StatusID.GrandImpactReady) && HasHostilesInRange && + //Use acceleration. If acceleration not available, use switfcast instead + (AccelerationPvE.CanUse(out act, usedUp: IsMoving) || (!AccelerationPvE.CanUse(out _) && SwiftcastPvE.CanUse(out act)))) + { + return true; + } + + + //Reprise logic + if (IsMoving && RangedSwordplay && !checkmelee && !ambatumelee && + //Check to not use Reprise when player can do melee combo, to not break it + (ManaStacks == 0 && (BlackMana < 50 || WhiteMana < 50) && + //Check if dualcast active + !Player.HasStatus(true, StatusID.Dualcast) && + //Bunch of checks if anything else can be used instead of Reprise + !AccelerationPvE.CanUse(out _) && + !Player.HasStatus(true, StatusID.Acceleration) && + !SwiftcastPvE.CanUse(out _) && + !Player.HasStatus(true, StatusID.Swiftcast) && + !GrandImpactPvE.CanUse(out _) && + !Player.HasStatus(true, StatusID.GrandImpactReady) && + //If nothing else to use and player moving - fire reprise. + EnchantedReprisePvE.CanUse(out act))) return true; + + //Attack abilities. + if (PrefulgencePvE.CanUse(out act, skipAoeCheck: true)) return true; + if (ViceOfThornsPvE.CanUse(out act, skipAoeCheck: true)) return true; + if (ContreSixtePvE.CanUse(out act, skipAoeCheck: true)) return true; + if (FlechePvE.CanUse(out act)) return true; + if (EngagementPvE.CanUse(out act, usedUp: true)) return true; + if (CorpsacorpsPvE.CanUse(out act) && !IsMoving) return true; + + return base.AttackAbility(nextGCD, out act); + } + #endregion + + #region GCD Logic + + protected override bool GeneralGCD(out IAction? act) + { + act = null; + + if (ManaStacks == 3) + { + if (BlackMana > WhiteMana) + { + if (VerholyPvE.CanUse(out act, skipAoeCheck: true)) return true; + } + + if (VerflarePvE.CanUse(out act, skipAoeCheck: true)) return true; + } + + // Hardcode Resolution & Scorch to avoid double melee without finishers + if (IsLastGCD(ActionID.ScorchPvE)) + { + if (ResolutionPvE.CanUse(out act, skipStatusProvideCheck: true, skipAoeCheck: true)) return true; + } + + if (IsLastGCD(ActionID.VerholyPvE, ActionID.VerflarePvE)) + { + if (ScorchPvE.CanUse(out act, skipStatusProvideCheck: true, skipAoeCheck: true)) return true; + } + + //Melee AOE combo + if (IsLastGCD(false, EnchantedMoulinetDeuxPvE) && EnchantedMoulinetTroisPvE.CanUse(out act)) return true; + if (IsLastGCD(false, EnchantedMoulinetPvE) && EnchantedMoulinetDeuxPvE.CanUse(out act)) return true; + if (EnchantedRedoublementPvE.CanUse(out act)) return true; + if (EnchantedZwerchhauPvE.CanUse(out act)) return true; + + + //Check if you can start melee combo + if (CanStartMeleeCombo) + { + if (EnchantedMoulinetPvE.CanUse(out act)) + { + if (BlackMana >= 50 && WhiteMana >= 50 || Player.HasStatus(true, StatusID.MagickedSwordplay)) return true; + } + else + { + if ((BlackMana >= 50 && WhiteMana >= 50 || Player.HasStatus(true, StatusID.MagickedSwordplay)) && + EnchantedRipostePvE.CanUse(out act)) return true; + } + } + //Grand impact usage if not interrupting melee combo + if (GrandImpactPvE.CanUse(out act, skipStatusProvideCheck: Player.HasStatus(true, StatusID.GrandImpactReady), skipCastingCheck: true, skipAoeCheck: true)) return true; + + if (ManaStacks == 3) return false; + + if (!VerthunderIiPvE.CanUse(out _)) + { + if (VerfirePvE.CanUse(out act)) return true; + if (VerstonePvE.CanUse(out act)) return true; + } + + if (ScatterPvE.CanUse(out act)) return true; + + if (WhiteMana < BlackMana) + { + if (VeraeroIiPvE.CanUse(out act) && BlackMana - WhiteMana != 5) return true; + if (VeraeroPvE.CanUse(out act) && BlackMana - WhiteMana != 6) return true; + } + if (VerthunderIiPvE.CanUse(out act)) return true; + if (VerthunderPvE.CanUse(out act)) return true; + + if (JoltPvE.CanUse(out act)) return true; + + if (UseVercure && NotInCombatDelay && VercurePvE.CanUse(out act)) return true; + + return base.GeneralGCD(out act); + } + #endregion + + #region Extra Methods + + + //why is this not working if called. Its always return false. + // private bool _didWeJustCombo = IsLastGCD([ + // ActionID.ScorchPvE, ActionID.VerflarePvE, ActionID.VerholyPvE, ActionID.EnchantedZwerchhauPvE, + // ActionID.EnchantedRedoublementPvP, ActionID.EnchantedRipostePvE, ActionID.EnchantedMoulinetPvE, ActionID.EnchantedMoulinetDeuxPvE, ActionID.EnchantedMoulinetTroisPvE + // ]); + + private bool CanStartMeleeCombo + { + get + { + if (Player.HasStatus(true, StatusID.Dualcast)) return false; + + if (Player.HasStatus(true, StatusID.Manafication, StatusID.Embolden, StatusID.MagickedSwordplay) || + BlackMana >= 50 || WhiteMana >= 50) return true; + + if (BlackMana == WhiteMana) return false; + + else if (WhiteMana < BlackMana) + { + if (Player.HasStatus(true, StatusID.VerstoneReady)) return false; + } + else + { + if (Player.HasStatus(true, StatusID.VerfireReady)) return false; + } + + if (Player.HasStatus(true, VercurePvE.Setting.StatusProvide ?? [])) return false; + + //Waiting for embolden. + if (EmboldenPvE.EnoughLevel && EmboldenPvE.Cooldown.WillHaveOneChargeGCD(5)) return false; + + return true; + } + } + #endregion +} diff --git a/BasicRotations/Magical/SMN_Archive b/BasicRotations/Magical/SMN_Archive new file mode 100644 index 000000000..b6ec5967d --- /dev/null +++ b/BasicRotations/Magical/SMN_Archive @@ -0,0 +1,159 @@ +using System.ComponentModel; + +namespace DefaultRotations.Magical; + +[Rotation("Default", CombatType.PvE, GameVersion = "7.00")] +[SourceCode(Path = "main/BasicRotations/Magical/SMN_Default.cs")] +[Api(3)] +public sealed class SMN_Default : SummonerRotation +{ + #region Config Options + public enum SwiftType : byte + { + No, + Emerald, + Ruby, + All, + } + + public enum SummonOrderType : byte + { + [Description("Topaz-Emerald-Ruby")] + TopazEmeraldRuby, + + [Description("Topaz-Ruby-Emerald")] + TopazRubyEmerald, + + [Description("Emerald-Topaz-Ruby")] + EmeraldTopazRuby, + } + + [RotationConfig(CombatType.PvE, Name = "Use Crimson Cyclone. Will use at any range, regardless of saftey use with caution.")] + public bool AddCrimsonCyclone { get; set; } = true; + + [RotationConfig(CombatType.PvE, Name = "Use Bahamut no matter what whenever it's up lol don't wait")] + public bool AlwaysBaha { get; set; } = false; + + [RotationConfig(CombatType.PvE, Name = "Use Swiftcast")] + public SwiftType AddSwiftcast { get; set; } = SwiftType.No; + + [RotationConfig(CombatType.PvE, Name = "Order")] + public SummonOrderType SummonOrder { get; set; } = SummonOrderType.EmeraldTopazRuby; + #endregion + + #region Countdown Logic + protected override IAction? CountDownAction(float remainTime) + { + if (SummonCarbunclePvE.CanUse(out var act)) return act; + + if (remainTime <= RuinPvE.Info.CastTime + CountDownAhead + && RuinPvE.CanUse(out act)) return act; + return base.CountDownAction(remainTime); + } + #endregion + + #region Move Logic + [RotationDesc(ActionID.CrimsonCyclonePvE)] + protected override bool MoveForwardGCD(out IAction? act) + { + if (CrimsonCyclonePvE.CanUse(out act, skipAoeCheck: true)) return true; + return base.MoveForwardGCD(out act); + } + #endregion + + #region oGCD Logic + protected override bool AttackAbility(IAction nextGCD, out IAction? act) + { + if (IsBurst && !Player.HasStatus(false, StatusID.SearingLight)) + { + if (SearingLightPvE.CanUse(out act, skipAoeCheck: true)) return true; + } + + var IsTargetBoss = HostileTarget?.IsBossFromTTK() ?? false; + var IsTargetDying = HostileTarget?.IsDying() ?? false; + + if ((InBahamut && SummonBahamutPvE.Cooldown.ElapsedOneChargeAfterGCD(3) || InPhoenix || + IsTargetBoss && IsTargetDying) && EnkindleBahamutPvE.CanUse(out act, skipAoeCheck: true)) return true; + + if ((SummonBahamutPvE.Cooldown.ElapsedOneChargeAfterGCD(3) || IsTargetBoss && IsTargetDying) && DeathflarePvE.CanUse(out act, skipAoeCheck: true)) return true; + if (RekindlePvE.CanUse(out act, skipAoeCheck: true)) return true; + if (MountainBusterPvE.CanUse(out act, skipAoeCheck: true)) return true; + + if ((Player.HasStatus(false, StatusID.SearingLight) && InBahamut && (SummonBahamutPvE.Cooldown.ElapsedOneChargeAfterGCD(3) || !EnergyDrainPvE.Cooldown.IsCoolingDown) || + !SearingLightPvE.EnoughLevel || IsTargetBoss && IsTargetDying) && PainflarePvE.CanUse(out act)) return true; + + if ((Player.HasStatus(false, StatusID.SearingLight) && InBahamut && (SummonBahamutPvE.Cooldown.ElapsedOneChargeAfterGCD(3) || !EnergyDrainPvE.Cooldown.IsCoolingDown) || + !SearingLightPvE.EnoughLevel || IsTargetBoss && IsTargetDying) && FesterPvE.CanUse(out act)) return true; + + if (EnergySiphonPvE.CanUse(out act)) return true; + if (EnergyDrainPvE.CanUse(out act)) return true; + + return base.AttackAbility(nextGCD, out act); + } + #endregion + + #region GCD Logic + protected override bool GeneralGCD(out IAction? act) + { + if (SummonCarbunclePvE.CanUse(out act)) return true; + + if ((Player.HasStatus(false, StatusID.SearingLight) || SearingLightPvE.Cooldown.IsCoolingDown) && AlwaysBaha && SummonBahamutPvE.CanUse(out act)) return true; + + if (SlipstreamPvE.CanUse(out act, skipAoeCheck: true)) return true; + + if (CrimsonStrikePvE.CanUse(out act, skipAoeCheck: true)) return true; + + //AOE + if (PreciousBrilliancePvE.CanUse(out act)) return true; + //Single + if (GemshinePvE.CanUse(out act)) return true; + + if (!IsMoving && AddCrimsonCyclone && CrimsonCyclonePvE.CanUse(out act, skipAoeCheck: true)) return true; + + if ((Player.HasStatus(false, StatusID.SearingLight) || SearingLightPvE.Cooldown.IsCoolingDown) && SummonBahamutPvE.CanUse(out act)) return true; + + if (!SummonBahamutPvE.EnoughLevel && HasHostilesInRange && AetherchargePvE.CanUse(out act)) return true; + + if (IsMoving && (Player.HasStatus(true, StatusID.GarudasFavor) || InIfrit) + && !Player.HasStatus(true, StatusID.Swiftcast) && !InBahamut && !InPhoenix + && RuinIvPvE.CanUse(out act, skipAoeCheck: true)) return true; + + switch (SummonOrder) + { + case SummonOrderType.TopazEmeraldRuby: + default: + if (SummonTopazPvE.CanUse(out act)) return true; + if (SummonEmeraldPvE.CanUse(out act)) return true; + if (SummonRubyPvE.CanUse(out act)) return true; + break; + + case SummonOrderType.TopazRubyEmerald: + if (SummonTopazPvE.CanUse(out act)) return true; + if (SummonRubyPvE.CanUse(out act)) return true; + if (SummonEmeraldPvE.CanUse(out act)) return true; + break; + + case SummonOrderType.EmeraldTopazRuby: + if (SummonEmeraldPvE.CanUse(out act)) return true; + if (SummonTopazPvE.CanUse(out act)) return true; + if (SummonRubyPvE.CanUse(out act)) return true; + break; + } + + if (SummonTimeEndAfterGCD() && AttunmentTimeEndAfterGCD() && + !Player.HasStatus(true, StatusID.Swiftcast) && !InBahamut && !InPhoenix && + RuinIvPvE.CanUse(out act, skipAoeCheck: true)) return true; + + if (OutburstPvE.CanUse(out act)) return true; + + if (RuinPvE.CanUse(out act)) return true; + + return base.GeneralGCD(out act); + } + #endregion + + #region Extra Methods + public override bool CanHealSingleSpell => false; + + #endregion +} diff --git a/BasicRotations/Magical/SMN_Default.cs b/BasicRotations/Magical/SMN_Default.cs new file mode 100644 index 000000000..f7e47aa9e --- /dev/null +++ b/BasicRotations/Magical/SMN_Default.cs @@ -0,0 +1,229 @@ +using System.ComponentModel; + +namespace DefaultRotations.Magical; + +[Rotation("Default", CombatType.PvE, GameVersion = "7.05")] +[SourceCode(Path = "main/BasicRotations/Magical/SMN_Default.cs")] +[Api(4)] +public sealed class SMN_Default : SummonerRotation +{ + + #region Config Options + + public enum SummonOrderType : byte + { + [Description("Topaz-Emerald-Ruby")] TopazEmeraldRuby, + + [Description("Topaz-Ruby-Emerald")] TopazRubyEmerald, + + [Description("Emerald-Topaz-Ruby")] EmeraldTopazRuby, + + [Description("Ruby-Emerald-Topaz")] RubyEmeraldTopaz, + } + + [RotationConfig(CombatType.PvE, Name = "Use Crimson Cyclone. Will use at any range, regardless of saftey use with caution.")] + public bool AddCrimsonCyclone { get; set; } = true; + + [RotationConfig(CombatType.PvE, Name = "Use Crimson Cyclone. Even When MOVING")] + public bool AddCrimsonCycloneMoving { get; set; } = false; + + [RotationConfig(CombatType.PvE, Name = "Use Swiftcast on Garuda")] + public bool AddSwiftcastOnGaruda { get; set; } = false; + + [RotationConfig(CombatType.PvE, Name = "Order")] + public SummonOrderType SummonOrder { get; set; } = SummonOrderType.TopazEmeraldRuby; + + [RotationConfig(CombatType.PvE, Name = "Use radiant on cooldown. But still keeping one charge")] + public bool RadiantOnCooldown { get; set; } = true; + + [RotationConfig(CombatType.PvE, Name = "Use this if there's no other raid buff in your party")] + public bool SecondTypeOpenerLogic { get; set; } = false; + + #endregion + + + #region Countdown Logic + protected override IAction? CountDownAction(float remainTime) + { + if (SummonCarbunclePvE.CanUse(out var act)) return act; + + if (remainTime <= RuinPvE.Info.CastTime + CountDownAhead + && RuinPvE.CanUse(out act)) return act; + return base.CountDownAction(remainTime); + } + #endregion + + #region Move Logic + [RotationDesc(ActionID.CrimsonCyclonePvE)] + protected override bool MoveForwardGCD(out IAction? act) + { + if (CrimsonCyclonePvE.CanUse(out act, skipAoeCheck: true)) return true; + return base.MoveForwardGCD(out act); + } + #endregion + + + #region oGCD Logic + protected override bool AttackAbility(IAction nextGCD, out IAction? act) + { + bool isTargetBoss = HostileTarget?.IsBossFromTTK() ?? false; + bool isTargetDying = HostileTarget?.IsDying() ?? false; + bool targetIsBossAndDying = isTargetBoss && isTargetDying; + bool inBigInvocation = !SummonBahamutPvE.EnoughLevel || (InBahamut || InPhoenix || InSolarBahamut); + bool inSolarUnique = Player.Level == 100 ? !InBahamut && !InPhoenix && InSolarBahamut : InBahamut && !InPhoenix; + if (SecondTypeOpenerLogic) + { + bool elapsed0ChargeAfterInvocation = SummonSolarBahamutPvE.Cooldown.ElapsedOneChargeAfterGCD() || SummonBahamutPvE.Cooldown.ElapsedOneChargeAfterGCD() || SummonPhoenixPvE.Cooldown.ElapsedOneChargeAfterGCD(); + bool elapsed1ChargeAfterInvocation = SummonSolarBahamutPvE.Cooldown.ElapsedOneChargeAfterGCD(1) || SummonBahamutPvE.Cooldown.ElapsedOneChargeAfterGCD(1) || SummonPhoenixPvE.Cooldown.ElapsedOneChargeAfterGCD(1); + bool elapsed2ChargeAfterInvocation = SummonSolarBahamutPvE.Cooldown.ElapsedOneChargeAfterGCD(2) || SummonBahamutPvE.Cooldown.ElapsedOneChargeAfterGCD(2) || SummonPhoenixPvE.Cooldown.ElapsedOneChargeAfterGCD(2); + bool burstInSolar = Player.Level == 100 ? InSolarBahamut : InBahamut; + + if (!Player.HasStatus(false, StatusID.SearingLight) && burstInSolar && elapsed0ChargeAfterInvocation) + { + if (SearingLightPvE.CanUse(out act, skipAoeCheck: true)) return true; + } + + if (inBigInvocation && (elapsed0ChargeAfterInvocation || targetIsBossAndDying) && EnergySiphonPvE.CanUse(out act)) return true; + if (inBigInvocation && (elapsed0ChargeAfterInvocation || targetIsBossAndDying) && EnergyDrainPvE.CanUse(out act)) return true; + if (inBigInvocation && (elapsed1ChargeAfterInvocation || targetIsBossAndDying) && EnkindleBahamutPvE.CanUse(out act)) return true; + if (inBigInvocation && (elapsed1ChargeAfterInvocation || targetIsBossAndDying) && EnkindleSolarBahamutPvE.CanUse(out act)) return true; + if (inBigInvocation && (elapsed1ChargeAfterInvocation || targetIsBossAndDying) && EnkindlePhoenixPvE.CanUse(out act)) return true; + if (inBigInvocation && (elapsed1ChargeAfterInvocation || targetIsBossAndDying) && DeathflarePvE.CanUse(out act, skipAoeCheck: true)) return true; + if (inBigInvocation && (elapsed1ChargeAfterInvocation || targetIsBossAndDying) && SunflarePvE.CanUse(out act, skipAoeCheck: true)) return true; + + if (RekindlePvE.CanUse(out act, skipAoeCheck: true)) return true; + if (MountainBusterPvE.CanUse(out act, skipAoeCheck: true)) return true; + + if ((inSolarUnique && Player.HasStatus(false, StatusID.SearingLight) || !SearingLightPvE.EnoughLevel || isTargetBoss && isTargetDying) && EnergyDrainPvE.IsInCooldown && PainflarePvE.CanUse(out act)) return true; + if ((inSolarUnique && Player.HasStatus(false, StatusID.SearingLight) || !SearingLightPvE.EnoughLevel || isTargetBoss && isTargetDying) && EnergyDrainPvE.IsInCooldown && FesterPvE.CanUse(out act)) return true; + + if ((elapsed2ChargeAfterInvocation || targetIsBossAndDying) && SearingFlashPvE.CanUse(out act, skipAoeCheck: true)) return true; + if (DoesAnyPlayerNeedHeal() && !inBigInvocation && LuxSolarisPvE.CanUse(out act)) return true; + } + else + { + bool elapsed0ChargeAfterInvocation = SummonSolarBahamutPvE.Cooldown.ElapsedOneChargeAfterGCD() || SummonBahamutPvE.Cooldown.ElapsedOneChargeAfterGCD() || SummonPhoenixPvE.Cooldown.ElapsedOneChargeAfterGCD(); + bool elapsed1ChargeAfterInvocation = SummonSolarBahamutPvE.Cooldown.ElapsedOneChargeAfterGCD(1) || SummonBahamutPvE.Cooldown.ElapsedOneChargeAfterGCD(1) || SummonPhoenixPvE.Cooldown.ElapsedOneChargeAfterGCD(1); + bool elapsed2ChargeAfterInvocation = SummonSolarBahamutPvE.Cooldown.ElapsedOneChargeAfterGCD(2) || SummonBahamutPvE.Cooldown.ElapsedOneChargeAfterGCD(2) || SummonPhoenixPvE.Cooldown.ElapsedOneChargeAfterGCD(2); + bool elapsed3ChargeAfterInvocation = SummonSolarBahamutPvE.Cooldown.ElapsedOneChargeAfterGCD(3) || SummonBahamutPvE.Cooldown.ElapsedOneChargeAfterGCD(3) || SummonPhoenixPvE.Cooldown.ElapsedOneChargeAfterGCD(3); + bool elapsed4ChargeAfterInvocation = SummonSolarBahamutPvE.Cooldown.ElapsedOneChargeAfterGCD(4) || SummonBahamutPvE.Cooldown.ElapsedOneChargeAfterGCD(4) || SummonPhoenixPvE.Cooldown.ElapsedOneChargeAfterGCD(4); + bool elapsed6ChargeAfterInvocation = SummonSolarBahamutPvE.Cooldown.ElapsedOneChargeAfterGCD(6) || SummonBahamutPvE.Cooldown.ElapsedOneChargeAfterGCD(6) || SummonPhoenixPvE.Cooldown.ElapsedOneChargeAfterGCD(6); + bool burstInSolar = Player.Level == 100 ? InSolarBahamut : InBahamut; + + if (!Player.HasStatus(false, StatusID.SearingLight) && burstInSolar && elapsed1ChargeAfterInvocation) + { + if (SearingLightPvE.CanUse(out act, skipAoeCheck: true)) return true; + } + + if (inBigInvocation && (elapsed3ChargeAfterInvocation || targetIsBossAndDying) && EnergySiphonPvE.CanUse(out act)) return true; + if (inBigInvocation && (elapsed3ChargeAfterInvocation || targetIsBossAndDying) && EnergyDrainPvE.CanUse(out act)) return true; + if (inBigInvocation && (elapsed4ChargeAfterInvocation || targetIsBossAndDying) && EnkindleBahamutPvE.CanUse(out act)) return true; + if (inBigInvocation && (elapsed4ChargeAfterInvocation || targetIsBossAndDying) && EnkindleSolarBahamutPvE.CanUse(out act)) return true; + if (inBigInvocation && (elapsed4ChargeAfterInvocation || targetIsBossAndDying) && EnkindlePhoenixPvE.CanUse(out act)) return true; + if (inBigInvocation && (elapsed4ChargeAfterInvocation || targetIsBossAndDying) && DeathflarePvE.CanUse(out act, skipAoeCheck: true)) return true; + if (inBigInvocation && (elapsed4ChargeAfterInvocation || targetIsBossAndDying) && SunflarePvE.CanUse(out act, skipAoeCheck: true)) return true; + + if (RekindlePvE.CanUse(out act, skipAoeCheck: true)) return true; + if (MountainBusterPvE.CanUse(out act, skipAoeCheck: true)) return true; + + if ((inSolarUnique && Player.HasStatus(false, StatusID.SearingLight) && elapsed2ChargeAfterInvocation && EnergyDrainPvE.Cooldown.WillHaveOneCharge(2) || !SearingLightPvE.EnoughLevel || isTargetBoss && isTargetDying) && EnergyDrainPvE.IsInCooldown && PainflarePvE.CanUse(out act)) return true; + if ((inSolarUnique && Player.HasStatus(false, StatusID.SearingLight) && elapsed2ChargeAfterInvocation && EnergyDrainPvE.Cooldown.WillHaveOneCharge(2) || !SearingLightPvE.EnoughLevel || isTargetBoss && isTargetDying) && EnergyDrainPvE.IsInCooldown && FesterPvE.CanUse(out act)) return true; + + if ((inSolarUnique && Player.HasStatus(false, StatusID.SearingLight) && elapsed4ChargeAfterInvocation || !SearingLightPvE.EnoughLevel || isTargetBoss && isTargetDying) && EnergyDrainPvE.IsInCooldown && PainflarePvE.CanUse(out act)) return true; + if ((inSolarUnique && Player.HasStatus(false, StatusID.SearingLight) && elapsed4ChargeAfterInvocation || !SearingLightPvE.EnoughLevel || isTargetBoss && isTargetDying) && EnergyDrainPvE.IsInCooldown && FesterPvE.CanUse(out act)) return true; + + if ((elapsed6ChargeAfterInvocation || targetIsBossAndDying) && SearingFlashPvE.CanUse(out act, skipAoeCheck: true)) return true; + if (DoesAnyPlayerNeedHeal() && !inBigInvocation && LuxSolarisPvE.CanUse(out act)) return true; + } + + + return base.AttackAbility(nextGCD, out act); + } + + protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) + { + bool anyBigInvocationIsCoolingDown = SummonBahamutPvE.Cooldown.IsCoolingDown || SummonSolarBahamutPvE.Cooldown.IsCoolingDown || SummonPhoenixPvE.Cooldown.IsCoolingDown; + if (AddSwiftcastOnGaruda && nextGCD == SlipstreamPvE && Player.Level > 86 && !InBahamut && !InPhoenix && !InSolarBahamut) + { + if (SwiftcastPvE.CanUse(out act)) return true; + } + + if ((RadiantOnCooldown && RadiantAegisPvE.Cooldown.CurrentCharges == 2 || RadiantAegisPvE.Cooldown.CurrentCharges == 1 && RadiantAegisPvE.Cooldown.WillHaveOneCharge(5)) && (anyBigInvocationIsCoolingDown && Player.Level <= 100) && RadiantAegisPvE.CanUse(out act)) return true; + if (RadiantOnCooldown && Player.Level < 88 && anyBigInvocationIsCoolingDown && RadiantAegisPvE.CanUse(out act)) return true; + + return base.EmergencyAbility(nextGCD, out act); + } + + #endregion + + #region GCD Logic + protected override bool GeneralGCD(out IAction? act) + { + if (SummonCarbunclePvE.CanUse(out act)) return true; + + if (SummonBahamutPvE.CanUse(out act)) return true; + if ((Player.HasStatus(false, StatusID.SearingLight) || SearingLightPvE.Cooldown.IsCoolingDown) && SummonBahamutPvE.CanUse(out act)) return true; + if (IsBurst && (!SearingLightPvE.Cooldown.IsCoolingDown && SummonSolarBahamutPvE.CanUse(out act))) return true; + + if (AddSwiftcastOnGaruda && SlipstreamPvE.CanUse(out act, skipAoeCheck: true, skipCastingCheck: !SwiftcastPvE.Cooldown.IsCoolingDown || HasSwift)) return true; + if (SlipstreamPvE.CanUse(out act, skipAoeCheck: true)) return true; + + if (CrimsonStrikePvE.CanUse(out act, skipAoeCheck: true)) return true; + + if (PreciousBrilliancePvE.CanUse(out act)) return true; + + if (GemshinePvE.CanUse(out act)) return true; + + if ((!IsMoving || AddCrimsonCycloneMoving) && AddCrimsonCyclone && CrimsonCyclonePvE.CanUse(out act, skipAoeCheck: true)) return true; + + if (!SummonBahamutPvE.EnoughLevel && HasHostilesInRange && AetherchargePvE.CanUse(out act)) return true; + + if (!InBahamut && !InPhoenix && !InSolarBahamut) + { + switch (SummonOrder) + { + case SummonOrderType.TopazEmeraldRuby: + default: + if (SummonTopazPvE.CanUse(out act)) return true; + if (SummonEmeraldPvE.CanUse(out act)) return true; + if (SummonRubyPvE.CanUse(out act)) return true; + break; + + case SummonOrderType.TopazRubyEmerald: + if (SummonTopazPvE.CanUse(out act)) return true; + if (SummonRubyPvE.CanUse(out act)) return true; + if (SummonEmeraldPvE.CanUse(out act)) return true; + break; + + case SummonOrderType.EmeraldTopazRuby: + if (SummonEmeraldPvE.CanUse(out act)) return true; + if (SummonTopazPvE.CanUse(out act)) return true; + if (SummonRubyPvE.CanUse(out act)) return true; + break; + + case SummonOrderType.RubyEmeraldTopaz: + if (SummonRubyPvE.CanUse(out act)) return true; + if (SummonEmeraldPvE.CanUse(out act)) return true; + if (SummonTopazPvE.CanUse(out act)) return true; + break; + } + } + + if (SummonTimeEndAfterGCD() && AttunmentTimeEndAfterGCD() && !InBahamut && !InPhoenix && !InSolarBahamut && SummonEmeraldPvE.IsInCooldown && SummonTopazPvE.IsInCooldown && SummonRubyPvE.IsInCooldown && + RuinIvPvE.CanUse(out act, skipAoeCheck: true)) return true; + + if (OutburstPvE.CanUse(out act)) return true; + if (RuinPvE.CanUse(out act)) return true; + return base.GeneralGCD(out act); + } + #endregion + + #region Extra Methods + public override bool CanHealSingleSpell => false; + + public bool DoesAnyPlayerNeedHeal() + { + return PartyMembersAverHP < 0.8f; + } + #endregion +} diff --git a/BasicRotations/Melee/DRG_Default.cs b/BasicRotations/Melee/DRG_Default.cs new file mode 100644 index 000000000..40b1e0956 --- /dev/null +++ b/BasicRotations/Melee/DRG_Default.cs @@ -0,0 +1,127 @@ +namespace DefaultRotations.Melee; + +[Rotation("Default", CombatType.PvE, GameVersion = "7.05")] +[SourceCode(Path = "main/BasicRotations/Melee/DRG_Default.cs")] +[Api(4)] + +public sealed class DRG_Default : DragoonRotation +{ + #region Config Options + [RotationConfig(CombatType.PvE, Name = "Use Doom Spike for damage uptime if out of melee range even if it breaks combo")] + public bool DoomSpikeWhenever { get; set; } = true; + + [RotationConfig(CombatType.PvE, Name = "Attempt to assign Stardiver to the first ogcd slot (Experimental)")] + public bool OGCDTimers { get; set; } = false; + #endregion + + private static bool InBurstStatus => !Player.WillStatusEnd(0, true, StatusID.BattleLitany); + + #region Additional oGCD Logic + + [RotationDesc(ActionID.WingedGlidePvE)] + protected override bool MoveForwardAbility(IAction nextGCD, out IAction? act) + { + if (WingedGlidePvE.CanUse(out act)) return true; + + return false; + } + + [RotationDesc(ActionID.ElusiveJumpPvE)] + protected override bool MoveBackAbility(IAction nextGCD, out IAction? act) + { + if (ElusiveJumpPvE.CanUse(out act)) return true; + + return false; + } + + [RotationDesc(ActionID.FeintPvE)] + protected sealed override bool DefenseAreaAbility(IAction nextGCD, out IAction? act) + { + if (FeintPvE.CanUse(out act)) return true; + return false; + } + #endregion + + #region oGCD Logic + protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) + { + if (IsBurst && InCombat) + { + if ((!BattleLitanyPvE.Cooldown.ElapsedAfter(60) || !BattleLitanyPvE.EnoughLevel) && LanceChargePvE.CanUse(out act)) return true; + + if (Player.HasStatus(true, StatusID.LanceCharge) && BattleLitanyPvE.CanUse(out act)) return true; + + if ((Player.HasStatus(true, StatusID.BattleLitany) || Player.HasStatus(true, StatusID.LanceCharge) || LOTDEndAfter(1000)) && nextGCD.IsTheSameTo(true, HeavensThrustPvE, DrakesbanePvE) + || (Player.HasStatus(true, StatusID.BattleLitany) && Player.HasStatus(true, StatusID.LanceCharge) && LOTDEndAfter(1000) && nextGCD.IsTheSameTo(true, ChaoticSpringPvE, LanceBarragePvE, WheelingThrustPvE, FangAndClawPvE)) + || (nextGCD.IsTheSameTo(true, HeavensThrustPvE, DrakesbanePvE) && (LanceChargePvE.IsInCooldown || BattleLitanyPvE.IsInCooldown))) + { + if (LifeSurgePvE.CanUse(out act, usedUp: true)) return true; + } + } + + return base.EmergencyAbility(nextGCD, out act); + } + + protected override bool AttackAbility(IAction nextGCD, out IAction? act) + { + if (Player.HasStatus(true, StatusID.LanceCharge)) + { + if (GeirskogulPvE.CanUse(out act)) return true; + } + + if (BattleLitanyPvE.EnoughLevel && Player.HasStatus(true, StatusID.BattleLitany) && Player.HasStatus(true, StatusID.LanceCharge) + || !BattleLitanyPvE.EnoughLevel && Player.HasStatus(true, StatusID.LanceCharge)) + { + if (DragonfireDivePvE.CanUse(out act)) return true; + } + + if ((Player.HasStatus(true, StatusID.BattleLitany) || Player.HasStatus(true, StatusID.LanceCharge) || LOTDEndAfter(1000)) + || nextGCD.IsTheSameTo(true, RaidenThrustPvE, DraconianFuryPvE)) + { + if (WyrmwindThrustPvE.CanUse(out act, usedUp: true)) return true; + } + + if (JumpPvE.CanUse(out act)) return true; + if (HighJumpPvE.CanUse(out act)) return true; + + if (StardiverPvE.CanUse(out act, isFirstAbility: OGCDTimers)) return true; + if (MirageDivePvE.CanUse(out act)) return true; + if (NastrondPvE.CanUse(out act)) return true; + if (StarcrossPvE.CanUse(out act)) return true; + if (RiseOfTheDragonPvE.CanUse(out act)) return true; + + return base.AttackAbility(nextGCD, out act); + } + #endregion + + #region GCD Logic + protected override bool GeneralGCD(out IAction? act) + { + bool doomSpikeRightNow = DoomSpikeWhenever; + + if (CoerthanTormentPvE.CanUse(out act)) return true; + if (SonicThrustPvE.CanUse(out act, skipStatusProvideCheck: true)) return true; + if (DoomSpikePvE.CanUse(out act, skipComboCheck: doomSpikeRightNow)) return true; + + if (DrakesbanePvE.CanUse(out act)) return true; + + if (FangAndClawPvE.CanUse(out act)) return true; + if (WheelingThrustPvE.CanUse(out act)) return true; + + if (FullThrustPvE.CanUse(out act)) return true; + if (ChaosThrustPvE.CanUse(out act)) return true; + + if (SpiralBlowPvE.CanUse(out act)) return true; + if (DisembowelPvE.CanUse(out act)) return true; + if (LanceBarragePvE.CanUse(out act)) return true; + if (VorpalThrustPvE.CanUse(out act)) return true; + + if (RaidenThrustPvE.CanUse(out act)) return true; + if (TrueThrustPvE.CanUse(out act)) return true; + + if (PiercingTalonPvE.CanUse(out act)) return true; + + return base.GeneralGCD(out act); + } + #endregion +} diff --git a/BasicRotations/Melee/MNK_Default.cs b/BasicRotations/Melee/MNK_Default.cs new file mode 100644 index 000000000..6e0ef6394 --- /dev/null +++ b/BasicRotations/Melee/MNK_Default.cs @@ -0,0 +1,252 @@ +namespace DefaultRotations.Melee; + +[Rotation("Default", CombatType.PvE, GameVersion = "7.00", Description = "Uses Lunar Solar Opener from The Balance")] +[SourceCode(Path = "main/BasicRotations/Melee/MNK_Default.cs")] +[Api(4)] + +public sealed class MNK_Default : MonkRotation +{ + #region Config Options + [RotationConfig(CombatType.PvE, Name = "Use Form Shift")] + public bool AutoFormShift { get; set; } = true; + + [RotationConfig(CombatType.PvE, Name = "Auto Use Perfect Balance (single target full auto mode, turn me off if you want total control of PB)")] + public bool AutoPB_Boss { get; set; } = true; + + [RotationConfig(CombatType.PvE, Name = "Auto Use Perfect Balance (aoe aggressive PB dump, turn me off if you don't want to waste PB in boss fight)")] + public bool AutoPB_AOE { get; set; } = true; + + [RotationConfig(CombatType.PvE, Name = "Enable TEA Checker.")] + public bool EnableTEAChecker { get; set; } = false; + #endregion + + #region Countdown Logic + protected override IAction? CountDownAction(float remainTime) + { + // gap closer at the end of countdown + if (remainTime <= 0.5 && ThunderclapPvE.CanUse(out var act)) return act; // need to face target to trigger + // true north before pull + if (remainTime <= 2 && TrueNorthPvE.CanUse(out act)) return act; + // turn on 5 chakra at -5 prepull + if (remainTime <= 5 && Chakra < 5 && ForbiddenMeditationPvE.CanUse(out act)) return act; + // formShift to prep opening + if (remainTime < 15 && FormShiftPvE.CanUse(out act)) return act; + + return base.CountDownAction(remainTime); + } + #endregion + + #region oGCD Logic + protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (EnableTEAChecker && Target.Name.ToString() == "Jagd Doll" && Target.GetHealthRatio() < 0.25) + { + return false; + } + + // PerfectBalancePvE after first gcd + TheForbiddenChakraPvE after second gcd + // fail to weave both after first gcd - rsr doesn't have enough time to react to both spells + // you pot -2s (real world -3s) prepull or after 2nd gcd!!! + // there is a small chance PB is not pressed in time if put in AttackAbility + // start the fight 8 yarms away from boss for double weaving + // 'The form shift and meditation prepull are implied. Prepull pot should win out, but choosing to press it in the first few weave slots shouldn¡¯t result in more than a single digit loss' + // 'there may be a delay before it can be used. Pushing it to the 2nd weave slot should avoid this.' + if (AutoPB_Boss && InCombat && CombatElapsedLess(3) && PerfectBalancePvE.CanUse(out act, usedUp: true)) return true; + //if (CombatElapsedLessGCD(1) && TheForbiddenChakraPvE.CanUse(out act)) return true; // if it weaves one day in the future... + + // need this to connect the first three buffs + if (IsLastAbility(true, BrotherhoodPvE) && RiddleOfFirePvE.CanUse(out act)) return true; // Riddle Of Fire + + return base.EmergencyAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.ThunderclapPvE)] + protected override bool MoveForwardAbility(IAction nextGCD, out IAction? act) + { + if (ThunderclapPvE.CanUse(out act)) return true; + return base.MoveForwardAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.FeintPvE)] + protected override bool DefenseAreaAbility(IAction nextGCD, out IAction? act) + { + if (FeintPvE.CanUse(out act)) return true; + return base.DefenseAreaAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.MantraPvE)] + protected override bool HealAreaAbility(IAction nextGCD, out IAction? act) + { + if (MantraPvE.CanUse(out act)) return true; + return base.HealAreaAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.RiddleOfEarthPvE)] + protected override bool DefenseSingleAbility(IAction nextGCD, out IAction? act) + { + if (RiddleOfEarthPvE.CanUse(out act, usedUp: true)) return true; + return base.DefenseSingleAbility(nextGCD, out act); + } + + protected override bool AttackAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (EnableTEAChecker && Target.Name.ToString() == "Jagd Doll" && Target.GetHealthRatio() < 0.25) + { + return false; + } + + // you need to position yourself in the centre of the mobs if they are large, that range is only 3 yarms + if (AutoPB_AOE && NumberOfHostilesInRange >= 2) + { + if (PerfectBalancePvE.CanUse(out act, usedUp: true)) return true; + } + + // opener 2nd burst + if (AutoPB_Boss + && Player.HasStatus(true, StatusID.RiddleOfFire) && Player.HasStatus(true, StatusID.Brotherhood) + && IsLastGCD(true, DragonKickPvE, LeapingOpoPvE, BootshinePvE) // PB must follow an Opo + && !Player.HasStatus(true, StatusID.FormlessFist) && !Player.HasStatus(true, StatusID.FiresRumination) && !Player.HasStatus(true, StatusID.WindsRumination)) + { + if (PerfectBalancePvE.CanUse(out act, usedUp: true)) return true; + } + + // odd min burst + if (AutoPB_Boss + && Player.HasStatus(true, StatusID.RiddleOfFire) + && !PerfectBalancePvE.Cooldown.JustUsedAfter(20) + && IsLastGCD(true, DragonKickPvE, LeapingOpoPvE, BootshinePvE)) // PB must follow an Opo + { + if (PerfectBalancePvE.CanUse(out act, usedUp: true)) return true; + } + + // even min burst + if (AutoPB_Boss + && !Player.HasStatus(true, StatusID.RiddleOfFire) + && RiddleOfFirePvE.Cooldown.WillHaveOneChargeGCD(3) && BrotherhoodPvE.Cooldown.WillHaveOneCharge(3) + && IsLastGCD(true, DragonKickPvE, LeapingOpoPvE, BootshinePvE)) // PB must follow an Opo + { + if (PerfectBalancePvE.CanUse(out act, usedUp: true)) return true; + } + + // 'TFC is used in the first weave slot to avoid any chakra overcap from the following gcds.' + // dump 5 stacks of chakara + if (NumberOfHostilesInRange >= 2) + { + if (EnlightenmentPvE.CanUse(out act, skipAoeCheck: true)) return true; // Enlightment + if (HowlingFistPvE.CanUse(out act, skipAoeCheck: true)) return true; // Howling Fist + } + else + if (TheForbiddenChakraPvE.CanUse(out act)) return true; + + // use bh when bh and rof are ready (opener) or ask bh to wait for rof's cd to be close and then use bh + if (!CombatElapsedLessGCD(2) + && ((BrotherhoodPvE.IsInCooldown && RiddleOfFirePvE.IsInCooldown) || Math.Abs(BrotherhoodPvE.Cooldown.CoolDownGroup - RiddleOfFirePvE.Cooldown.CoolDownGroup) < 3) + && BrotherhoodPvE.CanUse(out act, skipAoeCheck: true)) return true; + + // rof needs to be used on cd or after x gcd in opener + if (!CombatElapsedLessGCD(3) && RiddleOfFirePvE.CanUse(out act)) return true; // Riddle Of Fire + // 'Use on cooldown, unless you know your killtime. You should aim to get as many casts of RoW as you can, and then shift those usages to align with burst as much as possible without losing a use.' + if (!CombatElapsedLessGCD(3) && RiddleOfWindPvE.CanUse(out act)) return true; // Riddle Of Wind + + // what's this? check later + if (MergedStatus.HasFlag(AutoStatus.MoveForward) && MoveForwardAbility(nextGCD, out act)) return true; + + return base.AttackAbility(nextGCD, out act); + } + #endregion + + #region GCD Logic + // 'More opos in the fight is better than... in lunar PBs' + private bool OpoOpoForm(out IAction? act) + { + if (ArmOfTheDestroyerPvE.CanUse(out act)) return true; // Arm Of The Destoryer - aoe + if (LeapingOpoPvE.CanUse(out act)) return true; // Leaping Opo + if (DragonKickPvE.CanUse(out act)) return true; // Dragon Kick + if (BootshinePvE.CanUse(out act)) return true; //Bootshine - low level + return false; + } + + private bool RaptorForm(out IAction? act) + { + if (FourpointFuryPvE.CanUse(out act)) return true; //Fourpoint Fury - aoe + if (RisingRaptorPvE.CanUse(out act)) return true; //Rising Raptor + if (TwinSnakesPvE.CanUse(out act)) return true; //Twin Snakes + if (TrueStrikePvE.CanUse(out act)) return true; //True Strike - low level + return false; + } + + private bool CoerlForm(out IAction? act) + { + if (RockbreakerPvE.CanUse(out act)) return true; // Rockbreaker - aoe + if (PouncingCoeurlPvE.CanUse(out act)) return true; // Pouncing Coeurl + if (DemolishPvE.CanUse(out act)) return true; // Demolish + if (SnapPunchPvE.CanUse(out act)) return true; // Snap Punch - low level + return false; + } + + protected override bool GeneralGCD(out IAction? act) + { + act = null; + if (EnableTEAChecker && Target.Name.ToString() == "Jagd Doll" && Target.GetHealthRatio() < 0.25) + { + return false; + } + + // bullet proofed finisher - use when during burst + // or if burst was missed, and next burst is not arriving in time, use it better than waste it, otherwise, hold it for next rof + if (!BeastChakras.Contains(BeastChakra.NONE) && (Player.HasStatus(true, StatusID.RiddleOfFire) || RiddleOfFirePvE.Cooldown.JustUsedAfter(42))) + { + // for some reason phantom doesn't count as a variation of masterful like the others + if (PhantomRushPvE.CanUse(out act, skipAoeCheck: true)) return true; + if (TornadoKickPvE.CanUse(out act, skipAoeCheck: true)) return true; + if (CelestialRevolutionPvE.CanUse(out act, skipAoeCheck: true)) return true; // shouldn't need this but who know what button the user may press + if (MasterfulBlitzPvE.CanUse(out act, skipAoeCheck: true)) return true; + } + + // 'Because Fire¡¯s Reply grants formless, we have an imposed restriction that we prefer not to use it while under PB, or if we have a formless already.' + 'Cast Fire's Reply after an opo gcd' + // need to test and see if IsLastGCD(false, ...) is better + if ((!Player.HasStatus(true, StatusID.PerfectBalance) && !Player.HasStatus(true, StatusID.FormlessFist) && IsLastGCD(true, DragonKickPvE, LeapingOpoPvE, BootshinePvE) || Player.WillStatusEnd(5, true, StatusID.FiresRumination)) && FiresReplyPvE.CanUse(out act, skipAoeCheck: true)) return true; // Fires Reply + // 'Cast Wind's Reply literally anywhere in the window' + if (!Player.HasStatus(true, StatusID.PerfectBalance) && WindsReplyPvE.CanUse(out act, skipAoeCheck: true)) return true; // Winds Reply + + // Opo needs to follow each PB + // 'This means ¡°bookending¡± any PB usage with opos and spending formless on opos.' + if (Player.HasStatus(true, StatusID.FormlessFist) && OpoOpoForm(out act)) return true; + //if (Player.StatusStack(true, StatusID.PerfectBalance) == 3 && OpoOpoForm(out act)) return true; + + if (Player.HasStatus(true, StatusID.PerfectBalance) && !HasSolar) + { + // SolarNadi - fill the missing one - this order is needed for opener + if (!BeastChakras.Contains(BeastChakra.RAPTOR) && RaptorForm(out act)) return true; + if (!BeastChakras.Contains(BeastChakra.COEURL) && CoerlForm(out act)) return true; + if (!BeastChakras.Contains(BeastChakra.OPOOPO) && OpoOpoForm(out act)) return true; + } + + if (Player.HasStatus(true, StatusID.PerfectBalance) && HasSolar) + { + // 'we still want to prioritize pressing as many opo gcds as possible' + // LunarNadi + if (OpoOpoForm(out act)) return true; + } + + // whatever you have, press it from left to right + if (CoerlForm(out act)) return true; + if (RaptorForm(out act)) return true; + if (OpoOpoForm(out act)) return true; + + // out of range or nothing to do, recharge chakra first + if (Chakra < 5 && (ForbiddenMeditationPvE.CanUse(out act) || SteeledMeditationPvE.CanUse(out act))) return true; + + // out of range or nothing to do, refresh buff second, but dont keep refreshing or it draws too much attention + if (AutoFormShift && !Player.HasStatus(true, StatusID.PerfectBalance) && !Player.HasStatus(true, StatusID.FormlessFist) && FormShiftPvE.CanUse(out act)) return true; // Form Shift GCD use + + // i'm clever and i can do kame hame ha, so i won't stand still and keep refreshing form shift + if (EnlightenmentPvE.CanUse(out act, skipAoeCheck: true)) return true; // Enlightment + if (HowlingFistPvE.CanUse(out act, skipAoeCheck: true)) return true; // Howling Fist + + return base.GeneralGCD(out act); + } + #endregion +} diff --git a/BasicRotations/Melee/NIN_Default.cs b/BasicRotations/Melee/NIN_Default.cs new file mode 100644 index 000000000..ee1a43ecb --- /dev/null +++ b/BasicRotations/Melee/NIN_Default.cs @@ -0,0 +1,432 @@ +namespace DefaultRotations.Melee; + +[Rotation("Default", CombatType.PvE, GameVersion = "7.05")] +[SourceCode(Path = "main/BasicRotations/Melee/NIN_Default.cs")] +[Api(4)] + +public sealed class NIN_Default : NinjaRotation +{ + #region Config Options + // Configuration properties for rotation behavior. + [RotationConfig(CombatType.PvE, Name = "Use Hide")] + public bool UseHide { get; set; } = true; + [RotationConfig(CombatType.PvE, Name = "Use Unhide")] + public bool AutoUnhide { get; set; } = true; + + public bool IsShadowWalking = Player.HasStatus(true, StatusID.ShadowWalker); + #endregion + + #region CountDown Logic + // Logic to determine the action to take during the countdown phase before combat starts. + protected override IAction? CountDownAction(float remainTime) + { + var realInHuton = IsLastAction(false, HutonPvE); + // Clears ninjutsu setup if countdown is more than 6 seconds or if Suiton is the aim but shouldn't be. + if (remainTime > 6) ClearNinjutsu(); + + // Decision-making for ninjutsu actions based on remaining time until combat starts. + if (DoNinjutsu(out var act)) + { + if (act == SuitonPvE && remainTime > CountDownAhead) return null; + return act; + } + + else if (remainTime < 5) + { + SetNinjutsu(SuitonPvE); + } + else if (remainTime < 6) + { + // If within 10 seconds to start, consider using Hide or setting up Huton. + if (_ninActionAim == null && TenPvE.Cooldown.IsCoolingDown && HidePvE.CanUse(out act)) return act; + + } + return base.CountDownAction(remainTime); + } + #endregion + + #region Ninjutsu Logic + // Sets the target ninjutsu action to be performed next. + // If the action is null, or currently set to Rabbit Medium (indicating a failed Ninjutsu attempt), 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 ninjutsu action aim if it's different from the incoming action. + private void SetNinjutsu(IBaseAction act) + { + + if (act == null || AdjustId(ActionID.NinjutsuPvE) == ActionID.RabbitMediumPvE) return; + + if (_ninActionAim != null && IsLastAction(false, TenPvE, JinPvE, ChiPvE, FumaShurikenPvE_18873, FumaShurikenPvE_18874, FumaShurikenPvE_18875)) return; + + if (_ninActionAim != act) + { + _ninActionAim = act; + } + } + + // Clears the ninjutsu action aim, effectively resetting any planned ninjutsu action. + private void ClearNinjutsu() + { + if (_ninActionAim != null) + { + _ninActionAim = null; + } + } + + // Logic for choosing which ninjutsu action to set up next, based on various game state conditions. + private bool ChoiceNinjutsu(out IAction? act) + { + act = null; + + if (!JinPvE.CanUse(out _) || !ChiPvE.CanUse(out _) || !TenPvE.CanUse(out _)) return false; + // Ensures that the action ID currently considered for Ninjutsu is actually valid for Ninjutsu execution. + if (AdjustId(ActionID.NinjutsuPvE) != ActionID.NinjutsuPvE) return false; + // If more than 4.5 seconds have passed since the last action, it clears any pending Ninjutsu to avoid stale actions. + if (TimeSinceLastAction.TotalSeconds > 4.5) ClearNinjutsu(); + + // Checks for Kassatsu status to prioritize high-impact Ninjutsu due to its buff. + if (Player.HasStatus(true, StatusID.Kassatsu)) + { + // Attempts to set high-damage AoE Ninjutsu if available under Kassatsu's effect. + // These are prioritized due to Kassatsu's enhancement of Ninjutsu abilities. + if (GokaMekkyakuPvE.CanUse(out _) && ChiPvE.CanUse(out _) && TenPvE.CanUse(out _)) + { + SetNinjutsu(GokaMekkyakuPvE); + return false; + } + if (HyoshoRanryuPvE.CanUse(out _) && TenPvE.CanUse(out _) && JinPvE.CanUse(out _)) + { + SetNinjutsu(HyoshoRanryuPvE); + return false; + } + + if (HutonPvE.CanUse(out _) && TenPvE.CanUse(out _) && ChiPvE.CanUse(out _) && JinPvE.CanUse(out _)) + { + SetNinjutsu(HutonPvE); + return false; + } + + if (KatonPvE.CanUse(out _) && ChiPvE.CanUse(out _) && TenPvE.CanUse(out _)) + { + SetNinjutsu(KatonPvE); + return false; + } + + if (RaitonPvE.CanUse(out _) && TenPvE.CanUse(out _) && ChiPvE.CanUse(out _)) + { + SetNinjutsu(RaitonPvE); + return false; + } + } + else + { + // If Suiton is active but no specific Ninjutsu is currently aimed, it clears the Ninjutsu aim. + // This check is relevant for managing Suiton's effect, particularly for enabling Trick Attack. + if (Player.HasStatus(true, StatusID.Suiton) + && _ninActionAim == SuitonPvE && NoNinjutsu) + { + ClearNinjutsu(); + } + + // Chooses buffs or AoE actions based on combat conditions and cooldowns. + // For instance, setting Huton for speed buff or choosing AoE Ninjutsu like Katon or Doton based on enemy positioning. + // Also considers using Suiton for vulnerability debuff on the enemy if conditions are optimal. + + //Aoe + if (KatonPvE.CanUse(out _) && ChiPvE.CanUse(out _) && TenPvE.CanUse(out _)) + { + if (!Player.HasStatus(true, StatusID.Doton) && !IsMoving && !IsLastGCD(false, DotonPvE) && (!TenChiJinPvE.Cooldown.WillHaveOneCharge(6)) || !TenChiJinPvE.Cooldown.IsCoolingDown && TenPvE.CanUse(out _) && ChiPvE.CanUse(out _) && JinPvE.CanUse(out _)) + SetNinjutsu(DotonPvE); + else SetNinjutsu(KatonPvE); + return false; + } + + //Vulnerable + if (IsBurst && TrickAttackPvE.Cooldown.WillHaveOneCharge(18) && SuitonPvE.CanUse(out _) && !Player.HasStatus(true, StatusID.Suiton) && TenPvE.CanUse(out _) && ChiPvE.CanUse(out _) && JinPvE.CanUse(out _)) + { + SetNinjutsu(SuitonPvE); + return false; + } + + //Single + if (TenPvE.CanUse(out _, usedUp: InTrickAttack && !Player.HasStatus(false, StatusID.RaijuReady))) + { + if (RaitonPvE.CanUse(out _) && TenPvE.CanUse(out _) && ChiPvE.CanUse(out _)) + { + SetNinjutsu(RaitonPvE); + return false; + } + + if (!ChiPvE.EnoughLevel && FumaShurikenPvE.CanUse(out _)) + { + SetNinjutsu(FumaShurikenPvE); + return false; + } + } + } + + // If the last action performed matches any of a list of specific actions, it clears the Ninjutsu aim. + // This serves as a reset/cleanup mechanism to ensure the decision logic starts fresh for the next cycle. + if (IsLastAction(false, DotonPvE, SuitonPvE, + RabbitMediumPvE, FumaShurikenPvE, KatonPvE, RaitonPvE, + HyotonPvE, HutonPvE, DotonPvE, SuitonPvE, GokaMekkyakuPvE, HyoshoRanryuPvE)) + { + ClearNinjutsu(); + } + return false; // Indicates that no specific Ninjutsu action was chosen in this cycle. + } + #endregion + + #region Ninjutsu Execution + // Attempts to perform a ninjutsu action, based on the current game state and conditions. + private bool DoNinjutsu(out IAction? act) + { + act = null; + + //TenChiJin + if (Player.HasStatus(true, StatusID.TenChiJin)) + { + uint tenId = AdjustId(TenPvE.ID); + uint chiId = AdjustId(ChiPvE.ID); + uint jinId = AdjustId(JinPvE.ID); + + //First + if (tenId == FumaShurikenPvE_18873.ID + && !IsLastAction(false, FumaShurikenPvE_18875, FumaShurikenPvE_18873)) + { + //AOE + if (KatonPvE.CanUse(out _)) + { + if (FumaShurikenPvE_18875.CanUse(out act)) return true; + } + //Single + if (FumaShurikenPvE_18873.CanUse(out act)) return true; + } + + //Second + else if (tenId == KatonPvE_18876.ID && !IsLastAction(false, KatonPvE_18876)) + { + if (KatonPvE_18876.CanUse(out act, skipAoeCheck: true)) return true; + } + //Others + else if (chiId == RaitonPvE_18877.ID && !IsLastAction(false, RaitonPvE_18877)) + { + if (RaitonPvE_18877.CanUse(out act, skipAoeCheck: true)) return true; + } + else if (chiId == DotonPvE_18880.ID && !IsLastAction(false, DotonPvE_18880) && !Player.HasStatus(true, StatusID.Doton)) + { + if (DotonPvE_18880.CanUse(out act, skipAoeCheck: true)) return true; + } + else if (jinId == SuitonPvE_18881.ID && !IsLastAction(false, SuitonPvE_18881)) + { + if (SuitonPvE_18881.CanUse(out act, skipAoeCheck: true)) return true; + } + } + + //Keep Kassatsu in Burst. + if (!Player.WillStatusEnd(3, false, StatusID.Kassatsu) + && Player.HasStatus(false, StatusID.Kassatsu) && !InTrickAttack) return false; + if (_ninActionAim == null) return false; + + var id = AdjustId(ActionID.NinjutsuPvE); + + //Failed + if ((uint)id == RabbitMediumPvE.ID) + { + ClearNinjutsu(); + act = null; + return false; + } + //First + else if (id == ActionID.NinjutsuPvE) + { + //Can't use. + if (!Player.HasStatus(true, StatusID.Kassatsu, StatusID.TenChiJin) + && !TenPvE.CanUse(out _, usedUp: true) + && !IsLastAction(false, _ninActionAim.Setting.Ninjutsu![0])) + { + return false; + } + act = _ninActionAim.Setting.Ninjutsu![0]; + return true; + } + //Second + else if ((uint)id == _ninActionAim.ID) + { + if (_ninActionAim.CanUse(out act, skipAoeCheck: true)) return true; + if (_ninActionAim.ID == DotonPvE.ID && !InCombat) + { + act = _ninActionAim; + return true; + } + } + //Third + else if ((uint)id == FumaShurikenPvE.ID) + { + if (_ninActionAim.Setting.Ninjutsu!.Length > 1 + && !IsLastAction(false, _ninActionAim.Setting.Ninjutsu![1])) + { + act = _ninActionAim.Setting.Ninjutsu![1]; + return true; + } + } + //Finished + else if ((uint)id == KatonPvE.ID || (uint)id == RaitonPvE.ID || (uint)id == HyotonPvE.ID) + { + if (_ninActionAim.Setting.Ninjutsu!.Length > 2 + && !IsLastAction(false, _ninActionAim.Setting.Ninjutsu![2])) + { + act = _ninActionAim.Setting.Ninjutsu![2]; + return true; + } + } + return false; + } + #endregion + + #region Move Logic + // Defines logic for actions to take when moving forward during combat. + // This attribute associates the method with the Forked Raiju PvE action, + // indicating it's a relevant ability when considering movement-based actions. + [RotationDesc(ActionID.ForkedRaijuPvE)] + protected override bool MoveForwardGCD(out IAction? act) + { + // Checks if Forked Raiju, a movement-friendly ability, can be used. + // If so, sets it as the action to perform, returning true to indicate an action has been selected. + if (ForkedRaijuPvE.CanUse(out act)) return true; + + // If Forked Raiju is not available or not the best option, + // falls back to the base class's logic for choosing a move-forward action. + return base.MoveForwardGCD(out act); + } + #endregion + + #region oGCD Logic + // Determines the emergency abilities to use, overriding the base class implementation. + protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) + { + // Initializes the action to null, indicating no action has been chosen yet. + act = null; + + // 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); + + // First priority is given to Kassatsu if it's available, allowing for an immediate powerful Ninjutsu. + if (KassatsuPvE.CanUse(out act)) return true; + + if (TenriJindoPvE.CanUse(out act)) return true; + + // If in a burst phase and not just starting combat, checks if Mug is available to generate additional Ninki. + if (IsBurst && !CombatElapsedLess(5) && MugPvE.CanUse(out act)) return true; + + // Prioritizes using Suiton and Trick Attack for maximizing damage, especially outside the initial combat phase. + if (!CombatElapsedLess(6)) + { + // Attempts to use Trick Attack if it's available. + if (KunaisBanePvE.CanUse(out act, skipAoeCheck: true, skipStatusProvideCheck: IsShadowWalking)) return true; + if (!KunaisBanePvE.EnoughLevel && TrickAttackPvE.CanUse(out act, skipStatusProvideCheck: IsShadowWalking)) return true; + + // If Trick Attack is on cooldown but will not be ready soon, considers using Meisui to recover Ninki. + if (TrickAttackPvE.Cooldown.IsCoolingDown && !TrickAttackPvE.Cooldown.WillHaveOneCharge(19) && MeisuiPvE.CanUse(out act)) return true; + } + + // If none of the specific conditions are met, falls back to the base class's emergency ability logic. + return base.EmergencyAbility(nextGCD, out act); + } + + // Defines attack abilities to use during combat, overriding the base class implementation. + protected override bool AttackAbility(IAction nextGCD, out IAction? act) + { + act = null; + // If Ninjutsu is available or not in combat, it exits early, indicating no attack action to perform. + if (!NoNinjutsu || !InCombat) return false; + + // If the player is not moving, is within Trick Attack's effective window, and Ten Chi Jin hasn't recently been used, + // then Ten Chi Jin is set as the next action to perform. + if (!IsMoving && InTrickAttack && !TenPvE.Cooldown.ElapsedAfter(30) && TenChiJinPvE.CanUse(out act)) return true; + + // If more than 5 seconds have passed in combat, checks if Bunshin is available to use. + if (!CombatElapsedLess(5) && BunshinPvE.CanUse(out act)) return true; + + // Special handling if within Trick Attack's effective window: + if (InTrickAttack) + { + // If Dream Within A Dream is not yet available, checks if Assassinate can be used. + if (!DreamWithinADreamPvE.EnoughLevel) + { + if (AssassinatePvE.CanUse(out act)) return true; + } + else + { + // If Dream Within A Dream is available, it's set as the next action. + if (DreamWithinADreamPvE.CanUse(out act)) return true; + } + } + + // Checks for the use of Hellfrog Medium or Bhavacakra under certain conditions: + // - Not in the Mug's effective window or within Trick Attack's window + // - Certain cooldown conditions are met, or specific statuses are active. + if ((!InMug || InTrickAttack) + && (!BunshinPvE.Cooldown.WillHaveOneCharge(10) || Player.HasStatus(false, StatusID.PhantomKamaitachiReady) || MugPvE.Cooldown.WillHaveOneCharge(2))) + { + if (HellfrogMediumPvE.CanUse(out act)) return true; + if (BhavacakraPvE.CanUse(out act)) return true; + if (TenriJindoPvE.CanUse(out act)) return true; + } + if (MergedStatus.HasFlag(AutoStatus.MoveForward) && MoveForwardAbility(nextGCD, out act)) return true; + // If none of the conditions are met, it falls back to the base class's implementation for attack ability. + return base.AttackAbility(nextGCD, out act); + } + #endregion + + #region GCD Logic + // Main method for determining the general action to take during the combat's global cooldown phase. + protected override bool GeneralGCD(out IAction? act) + { + var hasRaijuReady = Player.HasStatus(true, StatusID.RaijuReady); + + if ((InTrickAttack || InMug) && NoNinjutsu && !hasRaijuReady + && !Player.HasStatus(true, StatusID.TenChiJin) + && PhantomKamaitachiPvE.CanUse(out act)) return true; + + if (ChoiceNinjutsu(out act)) return true; + if ((!InCombat || !CombatElapsedLess(7)) && DoNinjutsu(out act)) return true; + + //No Ninjutsu + if (NoNinjutsu) + { + if (!CombatElapsedLess(10) && FleetingRaijuPvE.CanUse(out act)) return true; + if (hasRaijuReady) return false; + } + + //AOE + if (HakkeMujinsatsuPvE.CanUse(out act)) return true; + if (DeathBlossomPvE.CanUse(out act)) return true; + + //Single + if (!InTrickAttack && Kazematoi < 4 && ArmorCrushPvE.CanUse(out act)) return true; + if (AeolianEdgePvE.CanUse(out act)) return true; + if (GustSlashPvE.CanUse(out act)) return true; + if (SpinningEdgePvE.CanUse(out act)) return true; + + //Range + if (!Player.HasStatus(true, StatusID.Mudra)) + { + if (ThrowingDaggerPvE.CanUse(out act)) return true; + } + + if (AutoUnhide) + { + StatusHelper.StatusOff(StatusID.Hidden); + } + if (!InCombat && _ninActionAim == null && UseHide + && TenPvE.Cooldown.IsCoolingDown && HidePvE.CanUse(out act)) return true; + + return base.GeneralGCD(out act); + } + #endregion + + #region Extra Methods + // Holds the next ninjutsu action to perform. + private IBaseAction? _ninActionAim = null; + #endregion +} diff --git a/BasicRotations/Melee/RPR_Default.cs b/BasicRotations/Melee/RPR_Default.cs new file mode 100644 index 000000000..783cbff6d --- /dev/null +++ b/BasicRotations/Melee/RPR_Default.cs @@ -0,0 +1,218 @@ +namespace DefaultRotations.Melee; + +[Rotation("Default", CombatType.PvE, GameVersion = "7.01", Description = "")] +[SourceCode(Path = "main/BasicRotations/Melee/RPR_Default.cs")] +[Api(4)] +public sealed class RPR_Default : ReaperRotation +{ + #region Config Options + [RotationConfig(CombatType.PvE, Name = "[Beta Option] Pool Shroud for Arcane Circle.")] + public bool EnshroudPooling { get; set; } = false; + + [RotationConfig(CombatType.PvE, Name = "Use custom timing to refresh Death's Design")] + public bool UseCustomDDTiming { get; set; } = false; + + [RotationConfig(CombatType.PvE, Name = "Refresh Death's Design with this many seconds remaining")] + public int RefreshDDSecondsRemaining { get; set; } = 10; + + public static bool ExecutionerReady => Player.HasStatus(true, StatusID.Executioner); + #endregion + + #region Countdown Logic + protected override IAction? CountDownAction(float remainTime) + { + if (remainTime < HarpePvE.Info.CastTime + CountDownAhead + && HarpePvE.CanUse(out var act)) return act; + + if (SoulsowPvE.CanUse(out act)) return act; + + return base.CountDownAction(remainTime); + } + #endregion + + #region oGCD Logic + protected override bool AttackAbility(IAction nextGCD, out IAction? act) + { + bool IsTargetBoss = HostileTarget?.IsBossFromTTK() ?? false; + bool IsTargetDying = HostileTarget?.IsDying() ?? false; + bool NoEnshroudPooling = !EnshroudPooling && Shroud >= 50; + bool YesEnshroudPooling = EnshroudPooling && Shroud >= 50 && (!PlentifulHarvestPvE.EnoughLevel || Player.HasStatus(true, StatusID.ArcaneCircle) || ArcaneCirclePvE.Cooldown.WillHaveOneCharge(8) || !Player.HasStatus(true, StatusID.ArcaneCircle) && ArcaneCirclePvE.Cooldown.WillHaveOneCharge(65) && !ArcaneCirclePvE.Cooldown.WillHaveOneCharge(50) || !Player.HasStatus(true, StatusID.ArcaneCircle) && Shroud >= 90); + bool IsIdealHost = Player.HasStatus(true, StatusID.IdealHost); + + if (IsBurst) + { + if ((HostileTarget?.HasStatus(true, StatusID.DeathsDesign) ?? false) + && !CombatElapsedLess(3.5f) && ArcaneCirclePvE.CanUse(out act, skipAoeCheck: true)) return true; + } + + if ((!Player.HasStatus(true, StatusID.Executioner)) && (IsTargetBoss && IsTargetDying || NoEnshroudPooling || YesEnshroudPooling || IsIdealHost)) + { + if (EnshroudPvE.CanUse(out act)) return true; + } + + if (SacrificiumPvE.CanUse(out act, skipAoeCheck: true, usedUp: true)) return true; + + if (HasEnshrouded && (Player.HasStatus(true, StatusID.ArcaneCircle) || LemureShroud < 3)) + { + if (LemuresScythePvE.CanUse(out act, usedUp: true)) return true; + if (LemuresSlicePvE.CanUse(out act, usedUp: true)) return true; + } + + if (PlentifulHarvestPvE.EnoughLevel && !HasPerfectioParata && !Player.HasStatus(true, StatusID.ImmortalSacrifice) /*&& !Player.HasStatus(true, StatusID.BloodsownCircle_2972) */|| !PlentifulHarvestPvE.EnoughLevel) + { + if (GluttonyPvE.CanUse(out act, skipAoeCheck: true)) return true; + } + + if (!Player.HasStatus(true, StatusID.BloodsownCircle_2972) && !HasPerfectioParata && !Player.HasStatus(true, StatusID.Executioner) && !Player.HasStatus(true, StatusID.ImmortalSacrifice) && (GluttonyPvE.EnoughLevel && !GluttonyPvE.Cooldown.WillHaveOneChargeGCD(4) || !GluttonyPvE.EnoughLevel || Soul == 100)) + { + if (GrimSwathePvE.CanUse(out act)) return true; + if (BloodStalkPvE.CanUse(out act)) return true; + } + + return base.AttackAbility(nextGCD, out act); + } + #endregion + + #region GCD Logic + protected override bool GeneralGCD(out IAction? act) + { + if (ExecutionersGuillotinePvE.EnoughLevel && (IsLastAction(true, GluttonyPvE) || Player.HasStatus(true, StatusID.Executioner))) + { + return ItsGluttonyTime(out act); + } + + if (SoulsowPvE.CanUse(out act)) return true; + + if (!ExecutionerReady && !HasSoulReaver) + { + if (PerfectioPvE.CanUse(out act, skipAoeCheck: true)) return true; + } + + if (WhorlOfDeathPvE.CanUse(out act)) return true; + if (UseCustomDDTiming && ((!HostileTarget?.HasStatus(true, StatusID.DeathsDesign) ?? false) || (HostileTarget?.WillStatusEnd(RefreshDDSecondsRemaining, true, StatusID.DeathsDesign) ?? false))) + { + if (ShadowOfDeathPvE.CanUse(out act, skipStatusProvideCheck: true)) return true; + } + else + { + if (ShadowOfDeathPvE.CanUse(out act)) return true; + } + + if (HasEnshrouded) + { + if (ShadowOfDeathPvE.CanUse(out act)) return true; + + if (LemureShroud > 1) + { + if (PlentifulHarvestPvE.EnoughLevel && ArcaneCirclePvE.Cooldown.WillHaveOneCharge(9) && + (LemureShroud == 4 && (HostileTarget?.WillStatusEnd(30, true, StatusID.DeathsDesign) ?? false) || LemureShroud == 3 && (HostileTarget?.WillStatusEnd(50, true, StatusID.DeathsDesign) ?? false))) + { + if (ShadowOfDeathPvE.CanUse(out act, skipStatusProvideCheck: true)) return true; + } + + if (Reaping(out act)) return true; + } + if (LemureShroud == 1) + { + if (CommunioPvE.EnoughLevel) + { + if (!IsMoving && CommunioPvE.CanUse(out act, skipAoeCheck: true)) + { + return true; + } + else + { + if (ShadowOfDeathPvE.CanUse(out act, skipAoeCheck: IsMoving)) return true; + } + } + else + { + if (Reaping(out act)) return true; + } + } + } + + + + if (HasSoulReaver) + { + if (GuillotinePvE.CanUse(out act)) return true; + + if (Player.HasStatus(true, StatusID.EnhancedGallows)) + { + if (GallowsPvE.CanUse(out act, skipComboCheck: true)) return true; + } + else if (Player.HasStatus(true, StatusID.EnhancedGibbet)) + { + if (GibbetPvE.CanUse(out act, skipComboCheck: true)) return true; + } + + // Try using Gallows/Gibbet that player is in position for when without Enchanced status + if (GallowsPvE.CanUse(out act, skipComboCheck: true) && GallowsPvE.Target.Target != null && CanHitPositional(EnemyPositional.Rear, GallowsPvE.Target.Target)) return true; + if (GibbetPvE.CanUse(out act, skipComboCheck: true) && GibbetPvE.Target.Target != null && CanHitPositional(EnemyPositional.Flank, GibbetPvE.Target.Target)) return true; + + if (GallowsPvE.CanUse(out act, skipComboCheck: true)) return true; + if (GibbetPvE.CanUse(out act, skipComboCheck: true)) return true; + } + + if (!CombatElapsedLessGCD(2) && PlentifulHarvestPvE.CanUse(out act, skipAoeCheck: true)) return true; + + if (SoulScythePvE.CanUse(out act, usedUp: true)) return true; + if (SoulSlicePvE.CanUse(out act, usedUp: true)) return true; + + if (NightmareScythePvE.CanUse(out act)) return true; + if (SpinningScythePvE.CanUse(out act)) return true; + + if (!Player.HasStatus(true, StatusID.Executioner) && InfernalSlicePvE.CanUse(out act)) return true; + if (!Player.HasStatus(true, StatusID.Executioner) && WaxingSlicePvE.CanUse(out act)) return true; + if (!Player.HasStatus(true, StatusID.Executioner) && SlicePvE.CanUse(out act)) return true; + + if (InCombat && !HasSoulReaver && HarvestMoonPvE.CanUse(out act, skipAoeCheck: true)) return true; + if (HarpePvE.CanUse(out act)) return true; + + return base.GeneralGCD(out act); + } + #endregion + + #region Extra Methods + private bool Reaping(out IAction? act) + { + if (GrimReapingPvE.CanUse(out act)) return true; + if (Player.HasStatus(true, StatusID.EnhancedCrossReaping) || !Player.HasStatus(true, StatusID.EnhancedVoidReaping)) + { + if (CrossReapingPvE.CanUse(out act)) return true; + } + else + { + if (VoidReapingPvE.CanUse(out act)) return true; + } + return false; + } + + private bool ItsGluttonyTime(out IAction? act) + { + if (ExecutionerReady) + { + if (ExecutionersGuillotinePvE.CanUse(out act)) return true; + + if (Player.HasStatus(true, StatusID.EnhancedGallows)) + { + if (ExecutionersGallowsPvE.CanUse(out act, skipComboCheck: true)) return true; + } + else if (Player.HasStatus(true, StatusID.EnhancedGibbet)) + { + if (ExecutionersGibbetPvE.CanUse(out act, skipComboCheck: true)) return true; + } + + // Try using Executioners Gallows/Gibbet that player is in position for when without Enchanced status + if (ExecutionersGallowsPvE.CanUse(out act, skipComboCheck: true) && ExecutionersGallowsPvE.Target.Target != null && CanHitPositional(EnemyPositional.Rear, ExecutionersGallowsPvE.Target.Target)) return true; + if (ExecutionersGibbetPvE.CanUse(out act, skipComboCheck: true) && ExecutionersGibbetPvE.Target.Target != null && CanHitPositional(EnemyPositional.Flank, ExecutionersGibbetPvE.Target.Target)) return true; + + if (ExecutionersGallowsPvE.CanUse(out act, skipComboCheck: true)) return true; + if (ExecutionersGibbetPvE.CanUse(out act, skipComboCheck: true)) return true; + } + act = null; + return false; + } + #endregion +} diff --git a/BasicRotations/Melee/SAM_Default.cs b/BasicRotations/Melee/SAM_Default.cs new file mode 100644 index 000000000..5a4d7b1cd --- /dev/null +++ b/BasicRotations/Melee/SAM_Default.cs @@ -0,0 +1,198 @@ +namespace DefaultRotations.Melee; + +[Rotation("Default", CombatType.PvE, GameVersion = "7.05")] +[SourceCode(Path = "main/BasicRotations/Melee/SAM_Default.cs")] +[Api(4)] +public sealed class SAM_Default : SamuraiRotation +{ + #region Config Options + + [Range(0, 85, ConfigUnitType.None, 5)] + [RotationConfig(CombatType.PvE, Name = "Use Kenki above.")] + public int AddKenki { get; set; } = 50; + + [RotationConfig(CombatType.PvE, Name = "Prevent Higanbana use if theres more than one target")] + public bool HiganbanaTargets { get; set; } = false; + + [RotationConfig(CombatType.PvE, Name = "Enable TEA Checker.")] + public bool EnableTEAChecker { get; set; } = false; + #endregion + + #region Countdown Logic + + protected override IAction? CountDownAction(float remainTime) + { + // pre-pull: can be changed to -9 and -5 instead of 5 and 2, but it's hard to be universal !!! check later !!! + if (remainTime <= 5 && MeikyoShisuiPvE.CanUse(out var act)) return act; + if (remainTime <= 2 && TrueNorthPvE.CanUse(out act)) return act; + return base.CountDownAction(remainTime); + } + + #endregion + + #region Additional oGCD Logic + + [RotationDesc(ActionID.HissatsuGyotenPvE)] + protected override bool MoveForwardAbility(IAction nextGCD, out IAction? act) + { + if (HissatsuGyotenPvE.CanUse(out act)) return true; + return base.MoveForwardAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.FeintPvE)] + protected override bool DefenseAreaAbility(IAction nextGCD, out IAction? act) + { + if (FeintPvE.CanUse(out act)) return true; + return base.DefenseAreaAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.ThirdEyePvE)] + protected override bool DefenseSingleAbility(IAction nextGCD, out IAction? act) + { + if (TengentsuPvE.CanUse(out act)) return true; + if (ThirdEyePvE.CanUse(out act)) return true; + return base.DefenseSingleAbility(nextGCD, out act); + } + + #endregion + + #region oGCD Logic + + protected override bool AttackAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (EnableTEAChecker && Target.Name.ToString() == "Jagd Doll" && Target.GetHealthRatio() < 0.25) + { + return false; + } + + var IsTargetBoss = HostileTarget?.IsBossFromTTK() ?? false; + var IsTargetDying = HostileTarget?.IsDying() ?? false; + + // IkishotenPvE logic combined with the delayed opener: + // you should weave the tincture in manually after rsr lands the first gcd (usually Gekko) + // and that's the only chance for tincture weaving during opener + if (!CombatElapsedLessGCD(2) && IkishotenPvE.CanUse(out act)) return true; + if (ShohaPvE.CanUse(out act)) return true; + // from old version - didn't touch this, didn't test this, never saw Hagakure button pressed personally !!! check later !!! + if ((HostileTarget?.HasStatus(true, StatusID.Higanbana) ?? false) && + (HostileTarget?.WillStatusEnd(32, true, StatusID.Higanbana) ?? false) && + !(HostileTarget?.WillStatusEnd(28, true, StatusID.Higanbana) ?? false) && + SenCount == 1 && IsLastAction(true, YukikazePvE) && !HaveMeikyoShisui) + { + if (HagakurePvE.CanUse(out act)) return true; + } + + if (ZanshinPvE.CanUse(out act)) return true; // need to check rsr code for upgrade and remove aoecheck here !!! check later !!! + if (HissatsuGurenPvE.CanUse(out act, skipAoeCheck: !HissatsuSeneiPvE.EnoughLevel)) return true; + if (HissatsuSeneiPvE.CanUse(out act)) return true; + + if (HissatsuKyutenPvE.CanUse(out act)) return true; + if (HissatsuShintenPvE.CanUse(out act)) return true; + + return base.AttackAbility(nextGCD, out act); + } + + protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (EnableTEAChecker && Target.Name.ToString() == "Jagd Doll" && Target.GetHealthRatio() < 0.25) + { + return false; + } + + var IsTargetBoss = HostileTarget?.IsBossFromTTK() ?? false; + var IsTargetDying = HostileTarget?.IsDying() ?? false; + + // from old version - didn't touch this, didn't test this, personally i doubt it's working !!! check later !!! + if (HasHostilesInRange && IsLastGCD(true, YukikazePvE, MangetsuPvE, OkaPvE) && + (!IsTargetBoss || (HostileTarget?.HasStatus(true, StatusID.Higanbana) ?? false) && !(HostileTarget?.WillStatusEnd(40, true, StatusID.Higanbana) ?? false) || !HasMoon && !HasFlower || IsTargetBoss && IsTargetDying)) + { + if (MeikyoShisuiPvE.CanUse(out act, usedUp: true)) return true; + } + return base.EmergencyAbility(nextGCD, out act); + } + + #endregion + + #region GCD Logic + + StatusID[] SamBuffs = [StatusID.Fugetsu, StatusID.Fuka]; + + protected override bool GeneralGCD(out IAction? act) + { + act = null; + if (EnableTEAChecker && Target.Name.ToString() == "Jagd Doll" && Target.GetHealthRatio() < 0.25) + { + return false; + } + + if ((!HiganbanaTargets || (HiganbanaTargets && NumberOfAllHostilesInRange < 2)) && (HostileTarget?.WillStatusEnd(18, true, StatusID.Higanbana) ?? false) && HiganbanaPvE.CanUse(out act, skipStatusProvideCheck: true)) return true; + + + if (MidareSetsugekkaPvE.CanUse(out act)) return true; + + if (TenkaGokenPvE.CanUse(out act)) return true; + if (TendoGokenPvE.CanUse(out act)) return true; + if (TendoSetsugekkaPvE.CanUse(out act)) return true; + if (TendoKaeshiGokenPvE.CanUse(out act)) return true; + if (TendoKaeshiSetsugekkaPvE.CanUse(out act)) return true; + // use 2nd finisher combo spell first + if (KaeshiNamikiriPvE.CanUse(out act, usedUp: true)) return true; + + var IsTargetBoss = HostileTarget?.IsBossFromTTK() ?? false; + var IsTargetDying = HostileTarget?.IsDying() ?? false; + + // use 2nd finisher combo spell first + if (KaeshiGokenPvE.CanUse(out act, usedUp: true)) return true; + if (KaeshiSetsugekkaPvE.CanUse(out act, usedUp: true)) return true; + if (TendoKaeshiGokenPvE.CanUse(out act, usedUp: true)) return true; + if (TendoKaeshiSetsugekkaPvE.CanUse(out act, usedUp: true)) return true; + + // burst finisher + if ((!IsTargetBoss || (HostileTarget?.HasStatus(true, StatusID.Higanbana) ?? false)) && HasMoon && HasFlower + && OgiNamikiriPvE.CanUse(out act)) return true; + + if (TendoSetsugekkaPvE.CanUse(out act)) return true; + if (MidareSetsugekkaPvE.CanUse(out act)) return true; + + // aoe 12 combo's 2 + if ((!HasMoon || IsMoonTimeLessThanFlower || !OkaPvE.EnoughLevel) && MangetsuPvE.CanUse(out act, skipComboCheck: HaveMeikyoShisui && !HasGetsu)) return true; + if ((!HasFlower || !IsMoonTimeLessThanFlower) && OkaPvE.CanUse(out act, skipComboCheck: HaveMeikyoShisui && !HasKa)) return true; + + if (!HasSetsu && SamBuffs.All(SamBuffs => Player.HasStatus(true, SamBuffs)) && + YukikazePvE.CanUse(out act, skipComboCheck: HaveMeikyoShisui && HasGetsu && HasKa)) return true; + + // single target 123 combo's 3 or used 3 directly during burst when MeikyoShisui is active, while also trying to start with the one that player is in position for extra DMG + if (GekkoPvE.CanUse(out act, skipComboCheck: HaveMeikyoShisui && !HasGetsu) && GekkoPvE.Target.Target != null && CanHitPositional(EnemyPositional.Rear, GekkoPvE.Target.Target)) return true; + if (KashaPvE.CanUse(out act, skipComboCheck: HaveMeikyoShisui && !HasKa) && KashaPvE.Target.Target != null && CanHitPositional(EnemyPositional.Flank, KashaPvE.Target.Target)) return true; + + if (GekkoPvE.CanUse(out act, skipComboCheck: HaveMeikyoShisui && !HasGetsu)) return true; + if (KashaPvE.CanUse(out act, skipComboCheck: HaveMeikyoShisui && !HasKa)) return true; + + // single target 123 combo's 2, while also trying to start with the one that player is in position for extra DMG + if (!HasGetsu && JinpuPvE.CanUse(out act) && JinpuPvE.Target.Target != null && (CanHitPositional(EnemyPositional.Rear, JinpuPvE.Target.Target) || (!HasMoon && HasFlower))) return true; + if (!HasKa && ShifuPvE.CanUse(out act) && ShifuPvE.Target.Target != null && (CanHitPositional(EnemyPositional.Flank, ShifuPvE.Target.Target) || (!HasFlower && HasMoon))) return true; + + if ((!HasMoon || IsMoonTimeLessThanFlower || !ShifuPvE.EnoughLevel) && JinpuPvE.CanUse(out act)) return true; + if ((!HasFlower || !IsMoonTimeLessThanFlower) && ShifuPvE.CanUse(out act)) return true; + + // initiate aoe + if (FukoPvE.CanUse(out act, skipComboCheck: true)) return true; // fuga doesn't becomes fuko automatically + if (!FukoPvE.EnoughLevel && FugaPvE.CanUse(out act, skipComboCheck: true)) return true; + + // MeikyoShisui buff is not active - not bursting - single target 123 combo's 1 + if (!HaveMeikyoShisui) + { + // target in range + if (HakazePvE.CanUse(out act)) return true; + + // target out of range + if (EnpiPvE.CanUse(out act)) return true; + } + + return base.GeneralGCD(out act); + } + + #endregion +} diff --git a/BasicRotations/Melee/VPR_Default.cs b/BasicRotations/Melee/VPR_Default.cs new file mode 100644 index 000000000..dcb068782 --- /dev/null +++ b/BasicRotations/Melee/VPR_Default.cs @@ -0,0 +1,211 @@ +namespace DefaultRotations.Melee; + +[Rotation("Default", CombatType.PvE, GameVersion = "7.05")] +[SourceCode(Path = "main/BasicRotations/Melee/VPR_Default.cs")] +[Api(4)] +public sealed class VPR_Default : ViperRotation +{ + #region Config Options + + [RotationConfig(CombatType.PvE, Name = "Use up all charges of Uncoiled Fury if you have used Tincture/Gemdraught (Overrides next option)")] + public bool BurstUncoiledFury { get; set; } = true; + + [RotationConfig(CombatType.PvE, Name = "Allow Uncoiled Fury and Writhing Snap to overwrite oGCDs when at range")] + public bool UFGhosting { get; set; } = true; + + [Range(1, 3, ConfigUnitType.None, 1)] + [RotationConfig(CombatType.PvE, Name = "How many charges of Uncoiled Fury needs to be at before be used inside of melee (Ignores burst, leave at 3 to hold charges for out of melee uptime or burst only)")] + public int MaxUncoiledStacksUser { get; set; } = 3; + + [Range(1, 30, ConfigUnitType.None, 1)] + [RotationConfig(CombatType.PvE, Name = "How long on the status time for Swift needs to be to allow reawaken use (setting this too low can lead to dropping buff)")] + public int SwiftTimer { get; set; } = 10; + + [Range(1, 30, ConfigUnitType.None, 1)] + [RotationConfig(CombatType.PvE, Name = "How long on the status time for Hunt needs to be to allow reawaken use (setting this too low can lead to dropping buff)")] + public int HuntersTimer { get; set; } = 10; + + [Range(0, 120, ConfigUnitType.None, 5)] + [RotationConfig(CombatType.PvE, Name = "How long has to pass on Serpents Ire's cooldown before the rotation starts pooling gauge for burst. Leave this alone if you dont know what youre doing. (Will still use Reawaken if you reach cap regardless of timer)")] + public int ReawakenDelayTimer { get; set; } = 75; + + [RotationConfig(CombatType.PvE, Name = "Experimental Pot Usage(used up to 5 seconds before SerpentsIre comes off cooldown)")] + public bool BurstMed { get; set; } = false; + + #endregion + + private static bool IsInBurst => Player.Level > 50 && !Player.WillStatusEnd(0, true, StatusID.RagingStrikes); + + #region Additional oGCD Logic + [RotationDesc] + protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) + { + // Uncoiled Fury Combo + if (UncoiledTwinfangPvE.CanUse(out act)) return true; + if (UncoiledTwinbloodPvE.CanUse(out act)) return true; + + //AOE Dread Combo + if (TwinfangThreshPvE.CanUse(out act, skipAoeCheck: true)) return true; + if (TwinbloodThreshPvE.CanUse(out act, skipAoeCheck: true)) return true; + + //Single Target Dread Combo + if (TwinfangBitePvE.CanUse(out act)) return true; + if (TwinbloodBitePvE.CanUse(out act)) return true; + + // Use burst medicine if cooldown for Technical Step has elapsed sufficiently + if (SerpentCombo == SerpentCombo.NONE && BurstMed && SerpentsIrePvE.EnoughLevel && SerpentsIrePvE.Cooldown.ElapsedAfter(115) + && UseBurstMedicine(out act)) return true; + + return base.EmergencyAbility(nextGCD, out act); + } + + [RotationDesc] + protected override bool MoveForwardAbility(IAction nextGCD, out IAction? act) + { + if (SlitherPvE.CanUse(out act)) return true; + return base.AttackAbility(nextGCD, out act); + } + + [RotationDesc] + protected override bool HealSingleAbility(IAction nextGCD, out IAction? act) + { + if (SerpentCombo == SerpentCombo.NONE && SecondWindPvE.CanUse(out act)) return true; + if (SerpentCombo == SerpentCombo.NONE && BloodbathPvE.CanUse(out act)) return true; + return base.HealSingleAbility(nextGCD, out act); + } + + [RotationDesc] + protected sealed override bool DefenseAreaAbility(IAction nextGCD, out IAction? act) + { + if (SerpentCombo == SerpentCombo.NONE && FeintPvE.CanUse(out act)) return true; + return base.DefenseAreaAbility(nextGCD, out act); + } + + [RotationDesc] + protected sealed override bool AntiKnockbackAbility(IAction nextGCD, out IAction? act) + { + if (SerpentCombo == SerpentCombo.NONE && ArmsLengthPvE.CanUse(out act)) return true; + return base.AntiKnockbackAbility(nextGCD, out act); + } + + [RotationDesc] + protected sealed override bool InterruptAbility(IAction nextGCD, out IAction? act) + { + if (SerpentCombo == SerpentCombo.NONE && LegSweepPvE.CanUse(out act)) return true; + return base.InterruptAbility(nextGCD, out act); + } + #endregion + + #region oGCD Logic + + protected override bool AttackAbility(IAction nextGCD, out IAction? act) + { + ////Reawaken Combo + if (FirstLegacyPvE.CanUse(out act)) return true; + if (SecondLegacyPvE.CanUse(out act)) return true; + if (ThirdLegacyPvE.CanUse(out act)) return true; + if (FourthLegacyPvE.CanUse(out act)) return true; + if (SerpentsIrePvE.CanUse(out act)) return true; + + ////Serpent Combo oGCDs + if (LastLashPvE.CanUse(out act)) return true; + if (DeathRattlePvE.CanUse(out act)) return true; + + return base.AttackAbility(nextGCD, out act); + } + #endregion + + #region GCD Logic + protected override bool GeneralGCD(out IAction? act) + { + + ////Reawaken Combo + if (OuroborosPvE.CanUse(out act)) return true; + if (FourthGenerationPvE.CanUse(out act)) return true; + if (ThirdGenerationPvE.CanUse(out act)) return true; + if (SecondGenerationPvE.CanUse(out act)) return true; + if (FirstGenerationPvE.CanUse(out act)) return true; + + if (SwiftTime > SwiftTimer && + HuntersTime > HuntersTimer && + !HasHunterVenom && !HasSwiftVenom && + !HasPoisedBlood && !HasPoisedFang && SerpentsIrePvE.EnoughLevel && (!SerpentsIrePvE.Cooldown.ElapsedAfter(ReawakenDelayTimer) || SerpentOffering == 100) || + SwiftTime > SwiftTimer && + HuntersTime > HuntersTimer && + !HasHunterVenom && !HasSwiftVenom && + !HasPoisedBlood && !HasPoisedFang && !SerpentsIrePvE.EnoughLevel) + { + if (ReawakenPvE.CanUse(out act, skipComboCheck: true)) return true; + } + + // Uncoiled Fury Overcap protection + if ((MaxRattling == RattlingCoilStacks || RattlingCoilStacks >= MaxUncoiledStacksUser) && !Player.HasStatus(true, StatusID.ReadyToReawaken) && SerpentCombo == SerpentCombo.NONE) + { + if (UncoiledFuryPvE.CanUse(out act, usedUp: true)) return true; + } + + if (BurstUncoiledFury && Player.HasStatus(true, StatusID.Medicated) && !Player.HasStatus(true, StatusID.ReadyToReawaken) && SerpentCombo == SerpentCombo.NONE) + { + if (UncoiledFuryPvE.CanUse(out act, usedUp: true)) return true; + } + + //Uncoiled fury use + if (SerpentsIrePvE.Cooldown.JustUsedAfter(30) && !Player.HasStatus(true, StatusID.ReadyToReawaken) && SerpentCombo == SerpentCombo.NONE) + { + if (UncoiledFuryPvE.CanUse(out act, usedUp: true)) return true; + } + + ////AOE Dread Combo + if (SwiftskinsDenPvE.CanUse(out act, skipComboCheck: true, skipAoeCheck: true)) return true; + if (HuntersDenPvE.CanUse(out act, skipComboCheck: true, skipAoeCheck: true)) return true; + + if (VicepitPvE.Cooldown.CurrentCharges == 1 && VicepitPvE.Cooldown.RecastTimeRemainOneCharge < 10) + { + if (VicepitPvE.CanUse(out act, usedUp: true)) return true; + } + if (VicepitPvE.CanUse(out act, usedUp: true)) return true; + + ////Single Target Dread Combo + // Try using Coil that player is in position for extra damage first + if (HuntersCoilPvE.CanUse(out act, skipComboCheck: true) && HuntersCoilPvE.Target.Target != null && CanHitPositional(EnemyPositional.Flank, HuntersCoilPvE.Target.Target)) return true; + if (SwiftskinsCoilPvE.CanUse(out act, skipComboCheck: true) && SwiftskinsCoilPvE.Target.Target != null && CanHitPositional(EnemyPositional.Rear, SwiftskinsCoilPvE.Target.Target)) return true; + + if (HuntersCoilPvE.CanUse(out act, skipComboCheck: true)) return true; + if (SwiftskinsCoilPvE.CanUse(out act, skipComboCheck: true)) return true; + + + if (VicewinderPvE.Cooldown.CurrentCharges == 1 && VicewinderPvE.Cooldown.RecastTimeRemainOneCharge < 10) + { + if (VicewinderPvE.CanUse(out act, usedUp: true)) return true; + } + if (VicewinderPvE.CanUse(out act, usedUp: true)) return true; + //AOE Serpent Combo + if (JaggedMawPvE.CanUse(out act, skipAoeCheck: true)) return true; + if (BloodiedMawPvE.CanUse(out act, skipAoeCheck: true)) return true; + + if (HuntersBitePvE.CanUse(out act, skipAoeCheck: true)) return true; + if (SwiftskinsBitePvE.CanUse(out act, skipAoeCheck: true)) return true; + + if (ReavingMawPvE.CanUse(out act)) return true; + if (SteelMawPvE.CanUse(out act)) return true; + + //Single Target Serpent Combo + if (FlankstingStrikePvE.CanUse(out act)) return true; + if (FlanksbaneFangPvE.CanUse(out act)) return true; + if (HindstingStrikePvE.CanUse(out act)) return true; + if (HindsbaneFangPvE.CanUse(out act)) return true; + + if (HuntersStingPvE.CanUse(out act)) return true; + if (SwiftskinsStingPvE.CanUse(out act)) return true; + + if (ReavingFangsPvE.CanUse(out act)) return true; + if (SteelFangsPvE.CanUse(out act)) return true; + + //Ranged + if ((UFGhosting || (!UFGhosting && SerpentCombo == SerpentCombo.NONE)) && UncoiledFuryPvE.CanUse(out act, usedUp: true)) return true; + if ((UFGhosting || (!UFGhosting && SerpentCombo == SerpentCombo.NONE)) && WrithingSnapPvE.CanUse(out act)) return true; + + return base.GeneralGCD(out act); + } + #endregion +} diff --git a/BasicRotations/PVPRotations/Healer/AST_Default.PVP.cs b/BasicRotations/PVPRotations/Healer/AST_Default.PVP.cs new file mode 100644 index 000000000..1c6ac7bda --- /dev/null +++ b/BasicRotations/PVPRotations/Healer/AST_Default.PVP.cs @@ -0,0 +1,143 @@ +namespace DefaultRotations.Healer; + +[Rotation("Default PVP", CombatType.PvP, GameVersion = "7.00", Description = "Beta Rotation")] +[SourceCode(Path = "main/BasicRotations/PVPRotations/Healer/AST_Default.PVP.cs")] +[Api(4)] +public class AST_DefaultPVP : AstrologianRotation +{ + [RotationConfig(CombatType.PvP, Name = "Sprint")] + public bool UseSprintPvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Recuperate")] + public bool UseRecuperatePvP { get; set; } = false; + + [Range(1, 100, ConfigUnitType.Percent, 1)] + [RotationConfig(CombatType.PvP, Name = "RecuperateHP%%?")] + public int RCValue { get; set; } = 75; + + [RotationConfig(CombatType.PvP, Name = "Use Purify")] + public bool UsePurifyPvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Stun")] + public bool Use1343PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on DeepFreeze")] + public bool Use3219PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on HalfAsleep")] + public bool Use3022PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Sleep")] + public bool Use1348PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Bind")] + public bool Use1345PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Heavy")] + public bool Use1344PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Silence")] + public bool Use1347PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Stop attacking while in Guard.")] + public bool GuardCancel { get; set; } = false; + + private bool TryPurify(out IAction? action) + { + action = null; + if (!UsePurifyPvP) return false; + + var purifyStatuses = new Dictionary + { + { 1343, Use1343PvP }, + { 3219, Use3219PvP }, + { 3022, Use3022PvP }, + { 1348, Use1348PvP }, + { 1345, Use1345PvP }, + { 1344, Use1344PvP }, + { 1347, Use1347PvP } + }; + + foreach (var status in purifyStatuses) + { + if (status.Value && Player.HasStatus(true, (StatusID)status.Key)) + { + return PurifyPvP.CanUse(out action); + } + } + + return false; + } + + protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + if (TryPurify(out act)) return true; + if (UseRecuperatePvP && Player.CurrentHp / Player.MaxHp * 100 < RCValue && RecuperatePvP.CanUse(out act)) return true; + + + return base.EmergencyAbility(nextGCD, out act); + } + + protected override bool MoveForwardAbility(IAction nextGCD, out IAction? act) + { + act = null; + + if (EpicyclePvP.CanUse(out act)) return true; + + return base.MoveForwardAbility(nextGCD, out act); + + } + + protected override bool AttackAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + + if (OraclePvP.CanUse(out act, skipAoeCheck: true)) return true; + + if (LordOfCrownsPvP.CanUse(out act, skipAoeCheck: true)) return true; + + if (GravityIiPvP_29248.CanUse(out act, skipAoeCheck: true)) return true; + + return base.AttackAbility(nextGCD, out act); + + } + + protected override bool GeneralAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + + if (MinorArcanaPvP.CanUse(out act)) return true; + + if (LadyOfCrownsPvE.CanUse(out act, skipAoeCheck: true)) return true; + + if (AspectedBeneficPvP_29247.CanUse(out act)) return true; + + if (MacrocosmosPvP.CanUse(out act)) return true; + if (MicrocosmosPvP.CanUse(out act)) return true; + + return base.GeneralAbility(nextGCD, out act); + + } + + protected override bool GeneralGCD(out IAction? act) + { + act = null; + + // Early exits for Guard status or Sprint usage + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + if (!Player.HasStatus(true, StatusID.Guard) && UseSprintPvP && !Player.HasStatus(true, StatusID.Sprint) && !InCombat && SprintPvP.CanUse(out act)) return true; + + if (GravityIiPvP.CanUse(out act)) return true; + + if (FallMaleficPvP.CanUse(out act)) return true; + + if (AspectedBeneficPvP.CanUse(out act)) return true; + + return base.GeneralGCD(out act); + + } +} diff --git a/BasicRotations/PVPRotations/Healer/SCH_Default.PVP.cs b/BasicRotations/PVPRotations/Healer/SCH_Default.PVP.cs new file mode 100644 index 000000000..29d8db91b --- /dev/null +++ b/BasicRotations/PVPRotations/Healer/SCH_Default.PVP.cs @@ -0,0 +1,117 @@ +namespace DefaultRotations.Healer; + +[Rotation("Default PVP", CombatType.PvP, GameVersion = "7.00", Description = "Beta Rotation")] +[SourceCode(Path = "main/BasicRotations/PVPRotations/Healer/SCH_Default.PVP.cs")] +[Api(4)] +public class SCH_DefaultPVP : ScholarRotation +{ + [RotationConfig(CombatType.PvP, Name = "Sprint")] + public bool UseSprintPvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Recuperate")] + public bool UseRecuperatePvP { get; set; } = false; + + [Range(1, 100, ConfigUnitType.Percent, 1)] + [RotationConfig(CombatType.PvP, Name = "RecuperateHP%%?")] + public int RCValue { get; set; } = 75; + + [RotationConfig(CombatType.PvP, Name = "Use Purify")] + public bool UsePurifyPvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Stun")] + public bool Use1343PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on DeepFreeze")] + public bool Use3219PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on HalfAsleep")] + public bool Use3022PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Sleep")] + public bool Use1348PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Bind")] + public bool Use1345PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Heavy")] + public bool Use1344PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Silence")] + public bool Use1347PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Stop attacking while in Guard.")] + public bool GuardCancel { get; set; } = false; + + private bool TryPurify(out IAction? action) + { + action = null; + if (!UsePurifyPvP) return false; + + var purifyStatuses = new Dictionary + { + { 1343, Use1343PvP }, + { 3219, Use3219PvP }, + { 3022, Use3022PvP }, + { 1348, Use1348PvP }, + { 1345, Use1345PvP }, + { 1344, Use1344PvP }, + { 1347, Use1347PvP } + }; + + foreach (var status in purifyStatuses) + { + if (status.Value && Player.HasStatus(true, (StatusID)status.Key)) + { + return PurifyPvP.CanUse(out action); + } + } + + return false; + } + + protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + if (TryPurify(out act)) return true; + if (UseRecuperatePvP && Player.CurrentHp / Player.MaxHp * 100 < RCValue && RecuperatePvP.CanUse(out act)) return true; + if (AdloquiumPvP.CanUse(out act)) return true; + if (DeploymentTacticsPvP.CanUse(out act)) return true; + + return base.EmergencyAbility(nextGCD, out act); + } + + protected override bool AttackAbility(IAction nextGCD, out IAction? act) + { + act = null; + + // Early exits for Guard status or Sprint usage + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + + return base.AttackAbility(nextGCD, out act); + } + protected override bool GeneralAbility(IAction nextGCD, out IAction? act) + { + act = null; + + // Early exits for Guard status or Sprint usage + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + if (ExpedientPvP.CanUse(out act)) return true; + + return base.GeneralAbility(nextGCD, out act); + } + protected override bool GeneralGCD(out IAction? act) + { + act = null; + + // Early exits for Guard status or Sprint usage + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + if (!Player.HasStatus(true, StatusID.Guard) && UseSprintPvP && !Player.HasStatus(true, StatusID.Sprint) && !InCombat && SprintPvP.CanUse(out act)) return true; + + if (BiolysisPvP.CanUse(out act)) return true; + + if (BroilIvPvP.CanUse(out act)) return true; + + return base.GeneralGCD(out act); + } +} diff --git a/BasicRotations/PVPRotations/Healer/SGE_Default.PVP.cs b/BasicRotations/PVPRotations/Healer/SGE_Default.PVP.cs new file mode 100644 index 000000000..4ecbde999 --- /dev/null +++ b/BasicRotations/PVPRotations/Healer/SGE_Default.PVP.cs @@ -0,0 +1,116 @@ +namespace DefaultRotations.Healer; + +[Rotation("Default PVP", CombatType.PvP, GameVersion = "7.00", Description = "Beta Rotation")] +[SourceCode(Path = "main/BasicRotations/PVPRotations/Healer/SGE_Default.PVP.cs")] +[Api(4)] +public class SGE_DefaultPVP : SageRotation +{ + [RotationConfig(CombatType.PvP, Name = "Sprint")] + public bool UseSprintPvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Recuperate")] + public bool UseRecuperatePvP { get; set; } = false; + + [Range(1, 100, ConfigUnitType.Percent, 1)] + [RotationConfig(CombatType.PvP, Name = "RecuperateHP%%?")] + public int RCValue { get; set; } = 75; + + [RotationConfig(CombatType.PvP, Name = "Use Purify")] + public bool UsePurifyPvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Stun")] + public bool Use1343PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on DeepFreeze")] + public bool Use3219PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on HalfAsleep")] + public bool Use3022PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Sleep")] + public bool Use1348PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Bind")] + public bool Use1345PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Heavy")] + public bool Use1344PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Silence")] + public bool Use1347PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Stop attacking while in Guard.")] + public bool GuardCancel { get; set; } = false; + + private bool TryPurify(out IAction? action) + { + action = null; + if (!UsePurifyPvP) return false; + + var purifyStatuses = new Dictionary + { + { 1343, Use1343PvP }, + { 3219, Use3219PvP }, + { 3022, Use3022PvP }, + { 1348, Use1348PvP }, + { 1345, Use1345PvP }, + { 1344, Use1344PvP }, + { 1347, Use1347PvP } + }; + + foreach (var status in purifyStatuses) + { + if (status.Value && Player.HasStatus(true, (StatusID)status.Key)) + { + return PurifyPvP.CanUse(out action); + } + } + + return false; + } + + protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + if (TryPurify(out act)) return true; + if (UseRecuperatePvP && Player.CurrentHp / Player.MaxHp * 100 < RCValue && RecuperatePvP.CanUse(out act)) return true; + + return base.EmergencyAbility(nextGCD, out act); + } + + protected override bool AttackAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + + if (ToxikonPvP.CanUse(out act)) return true; + + return base.AttackAbility(nextGCD, out act); + + } + protected override bool GeneralAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + return base.GeneralAbility(nextGCD, out act); + + } + protected override bool GeneralGCD(out IAction? act) + { + act = null; + + // Early exits for Guard status or Sprint usage + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + if (!Player.HasStatus(true, StatusID.Guard) && UseSprintPvP && !Player.HasStatus(true, StatusID.Sprint) && !InCombat && SprintPvP.CanUse(out act)) return true; + + if (PneumaPvP.CanUse(out act)) return true; + + if (PhlegmaIiiPvP.CanUse(out act)) return true; + + if (DosisIiiPvP.CanUse(out act)) return true; + + return base.GeneralGCD(out act); + + } +} diff --git a/BasicRotations/PVPRotations/Healer/WHM_Default.PVP.cs b/BasicRotations/PVPRotations/Healer/WHM_Default.PVP.cs new file mode 100644 index 000000000..0a3cff78c --- /dev/null +++ b/BasicRotations/PVPRotations/Healer/WHM_Default.PVP.cs @@ -0,0 +1,121 @@ +namespace DefaultRotations.Healer; + +[Rotation("Default PVP", CombatType.PvP, GameVersion = "7.00", Description = "Beta Rotation")] +[SourceCode(Path = "main/BasicRotations/PVPRotations/Healer/WHM_Default.PVP.cs")] +[Api(4)] +public class WHM_DefaultPVP : WhiteMageRotation +{ + [RotationConfig(CombatType.PvP, Name = "Sprint")] + public bool UseSprintPvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Recuperate")] + public bool UseRecuperatePvP { get; set; } = false; + + [Range(1, 100, ConfigUnitType.Percent, 1)] + [RotationConfig(CombatType.PvP, Name = "RecuperateHP%%?")] + public int RCValue { get; set; } = 75; + + [RotationConfig(CombatType.PvP, Name = "Use Purify")] + public bool UsePurifyPvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Stun")] + public bool Use1343PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on DeepFreeze")] + public bool Use3219PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on HalfAsleep")] + public bool Use3022PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Sleep")] + public bool Use1348PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Bind")] + public bool Use1345PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Heavy")] + public bool Use1344PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Silence")] + public bool Use1347PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Stop attacking while in Guard.")] + public bool GuardCancel { get; set; } = false; + + private bool TryPurify(out IAction? action) + { + action = null; + if (!UsePurifyPvP) return false; + + var purifyStatuses = new Dictionary + { + { 1343, Use1343PvP }, + { 3219, Use3219PvP }, + { 3022, Use3022PvP }, + { 1348, Use1348PvP }, + { 1345, Use1345PvP }, + { 1344, Use1344PvP }, + { 1347, Use1347PvP } + }; + + foreach (var status in purifyStatuses) + { + if (status.Value && Player.HasStatus(true, (StatusID)status.Key)) + { + return PurifyPvP.CanUse(out action); + } + } + + return false; + } + + protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + if (TryPurify(out act)) return true; + if (UseRecuperatePvP && Player.CurrentHp / Player.MaxHp * 100 < RCValue && RecuperatePvP.CanUse(out act)) return true; + if (MiracleOfNaturePvP.CanUse(out act)) return true; + + return base.EmergencyAbility(nextGCD, out act); + } + + protected override bool AttackAbility(IAction nextGCD, out IAction? act) + { + act = null; + + // Early exits for Guard status or Sprint usage + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + if (SeraphStrikePvP.CanUse(out act)) return true; + + return base.AttackAbility(nextGCD, out act); + } + protected override bool GeneralAbility(IAction nextGCD, out IAction? act) + { + act = null; + + // Early exits for Guard status or Sprint usage + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + if (AquaveilPvP.CanUse(out act)) return true; + + return base.GeneralAbility(nextGCD, out act); + } + protected override bool GeneralGCD(out IAction? act) + { + act = null; + + // Early exits for Guard status or Sprint usage + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + if (!Player.HasStatus(true, StatusID.Guard) && UseSprintPvP && !Player.HasStatus(true, StatusID.Sprint) && !InCombat && SprintPvP.CanUse(out act)) return true; + + if (AfflatusMiseryPvP.CanUse(out act, skipAoeCheck: true)) return true; + + if (GlareIiiPvP.CanUse(out act)) return true; + + // if (CureIiiPvP.CanUse(out act)) return true; + + if (CureIiPvP.CanUse(out act)) return true; + + return base.GeneralGCD(out act); + } +} \ No newline at end of file diff --git a/BasicRotations/PVPRotations/Magical/BLM_Default.PVP.cs b/BasicRotations/PVPRotations/Magical/BLM_Default.PVP.cs new file mode 100644 index 000000000..344306058 --- /dev/null +++ b/BasicRotations/PVPRotations/Magical/BLM_Default.PVP.cs @@ -0,0 +1,116 @@ +namespace DefaultRotations.Magical; + +[Rotation("Default PVP", CombatType.PvP, GameVersion = "7.00", Description = "Beta Rotation")] +[SourceCode(Path = "main/BasicRotations/PVPRotations/Magical/BLM_Default.PVP.cs")] +[Api(4)] +public class BLM_DefaultPVP : BlackMageRotation +{ + [RotationConfig(CombatType.PvP, Name = "Sprint")] + public bool UseSprintPvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Recuperate")] + public bool UseRecuperatePvP { get; set; } = false; + + [Range(1, 100, ConfigUnitType.Percent, 1)] + [RotationConfig(CombatType.PvP, Name = "RecuperateHP%%?")] + public int RCValue { get; set; } = 75; + + [RotationConfig(CombatType.PvP, Name = "Use Purify")] + public bool UsePurifyPvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Stun")] + public bool Use1343PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on DeepFreeze")] + public bool Use3219PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on HalfAsleep")] + public bool Use3022PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Sleep")] + public bool Use1348PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Bind")] + public bool Use1345PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Heavy")] + public bool Use1344PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Silence")] + public bool Use1347PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Stop attacking while in Guard.")] + public bool GuardCancel { get; set; } = false; + + private bool TryPurify(out IAction? action) + { + action = null; + if (!UsePurifyPvP) return false; + + var purifyStatuses = new Dictionary + { + { 1343, Use1343PvP }, + { 3219, Use3219PvP }, + { 3022, Use3022PvP }, + { 1348, Use1348PvP }, + { 1345, Use1345PvP }, + { 1344, Use1344PvP }, + { 1347, Use1347PvP } + }; + + foreach (var status in purifyStatuses) + { + if (status.Value && Player.HasStatus(true, (StatusID)status.Key)) + { + return PurifyPvP.CanUse(out action); + } + } + + return false; + } + + protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + if (TryPurify(out act)) return true; + if (UseRecuperatePvP && Player.CurrentHp / Player.MaxHp * 100 < RCValue && RecuperatePvP.CanUse(out act)) return true; + + return base.EmergencyAbility(nextGCD, out act); + } + + protected override bool AttackAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + + return base.AttackAbility(nextGCD, out act); + } + + protected override bool GeneralAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + + if (AetherialManipulationPvP.CanUse(out act)) return true; + + return base.GeneralAbility(nextGCD, out act); + } + protected override bool GeneralGCD(out IAction? act) + { + act = null; + + // Early exits for Guard status or Sprint usage + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + if (!Player.HasStatus(true, StatusID.Guard) && UseSprintPvP && !Player.HasStatus(true, StatusID.Sprint) && !InCombat && SprintPvP.CanUse(out act)) return true; + + if (BurstPvP.CanUse(out act)) return true; + + if (ParadoxPvP.CanUse(out act)) return true; + + if (FirePvP.CanUse(out act)) return true; + if (BlizzardPvP.CanUse(out act)) return true; + + return base.GeneralGCD(out act); + } +} diff --git a/BasicRotations/PVPRotations/Magical/RDM_Default.PvP.cs b/BasicRotations/PVPRotations/Magical/RDM_Default.PvP.cs new file mode 100644 index 000000000..494dfe367 --- /dev/null +++ b/BasicRotations/PVPRotations/Magical/RDM_Default.PvP.cs @@ -0,0 +1,130 @@ +namespace DefaultRotations.Magical; + +[Rotation("Default PVP", CombatType.PvP, GameVersion = "7.00", Description = "Beta Rotation")] +[SourceCode(Path = "main/BasicRotations/PVPRotations/Magical/RDM_Default.PVP.cs")] +[Api(4)] +public class RDM_DefaultPvP : RedMageRotation +{ + [RotationConfig(CombatType.PvP, Name = "Sprint")] + public bool UseSprintPvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Recuperate")] + public bool UseRecuperatePvP { get; set; } = false; + + [Range(1, 100, ConfigUnitType.Percent, 1)] + [RotationConfig(CombatType.PvP, Name = "RecuperateHP%%?")] + public int RCValue { get; set; } = 75; + + [RotationConfig(CombatType.PvP, Name = "Use Purify")] + public bool UsePurifyPvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Stun")] + public bool Use1343PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on DeepFreeze")] + public bool Use3219PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on HalfAsleep")] + public bool Use3022PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Sleep")] + public bool Use1348PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Bind")] + public bool Use1345PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Heavy")] + public bool Use1344PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Silence")] + public bool Use1347PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Stop attacking while in Guard.")] + public bool GuardCancel { get; set; } = false; + + private bool TryPurify(out IAction? action) + { + action = null; + if (!UsePurifyPvP) return false; + + var purifyStatuses = new Dictionary + { + { 1343, Use1343PvP }, + { 3219, Use3219PvP }, + { 3022, Use3022PvP }, + { 1348, Use1348PvP }, + { 1345, Use1345PvP }, + { 1344, Use1344PvP }, + { 1347, Use1347PvP } + }; + + foreach (var status in purifyStatuses) + { + if (status.Value && Player.HasStatus(true, (StatusID)status.Key)) + { + return PurifyPvP.CanUse(out action); + } + } + + return false; + } + + protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + if (TryPurify(out act)) return true; + if (UseRecuperatePvP && Player.CurrentHp / Player.MaxHp * 100 < RCValue && RecuperatePvP.CanUse(out act)) return true; + + return base.EmergencyAbility(nextGCD, out act); + } + + protected bool DefenseAreaAbility(out IAction? act) + { + act = null; + + // Early exits for Guard status or Sprint usage + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + if (FortePvP.CanUse(out act)) return true; + + return base.DefenseAreaGCD(out act); + } + + protected override bool AttackAbility(IAction nextGCD, out IAction? act) + { + act = null; + + // Early exits for Guard status or Sprint usage + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + + if (ResolutionPvP.CanUse(out act)) return true; + + if (DisplacementPvP.CanUse(out act, skipAoeCheck: true)) return true; + if (CorpsacorpsPvP.CanUse(out act, skipAoeCheck: true)) return true; + + if (PrefulgencePvP.CanUse(out act)) return true; + if (EmboldenPvP.CanUse(out act)) return true; + + + return base.AttackAbility(nextGCD, out act); + } + + protected override bool GeneralGCD(out IAction? act) + { + act = null; + + // Early exits for Guard status or Sprint usage + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + if (!Player.HasStatus(true, StatusID.Guard) && UseSprintPvP && !Player.HasStatus(true, StatusID.Sprint) && !InCombat && SprintPvP.CanUse(out act)) return true; + + if (JoltIiiPvP.CanUse(out act)) return true; + if (GrandImpactPvP.CanUse(out act)) return true; + if (EnchantedRipostePvP.CanUse(out act)) return true; + if (EnchantedZwerchhauPvP.CanUse(out act)) return true; + if (EnchantedRedoublementPvP.CanUse(out act)) return true; + if (ScorchPvP.CanUse(out act)) return true; + + return base.GeneralGCD(out act); + } + +} diff --git a/BasicRotations/PVPRotations/Magical/SMN_Default.PVP.cs b/BasicRotations/PVPRotations/Magical/SMN_Default.PVP.cs new file mode 100644 index 000000000..47fb8d121 --- /dev/null +++ b/BasicRotations/PVPRotations/Magical/SMN_Default.PVP.cs @@ -0,0 +1,116 @@ +namespace DefaultRotations.Magical; + +[Rotation("Default PVP", CombatType.PvP, GameVersion = "7.00", Description = "Beta Rotation")] +[SourceCode(Path = "main/BasicRotations/PVPRotations/Magical/SMN_Default.PVP.cs")] +[Api(4)] +public class SMN_DefaultPvP : SummonerRotation +{ + [RotationConfig(CombatType.PvP, Name = "Sprint")] + public bool UseSprintPvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Recuperate")] + public bool UseRecuperatePvP { get; set; } = false; + + [Range(1, 100, ConfigUnitType.Percent, 1)] + [RotationConfig(CombatType.PvP, Name = "RecuperateHP%%?")] + public int RCValue { get; set; } = 75; + + [RotationConfig(CombatType.PvP, Name = "Use Purify")] + public bool UsePurifyPvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Stun")] + public bool Use1343PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on DeepFreeze")] + public bool Use3219PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on HalfAsleep")] + public bool Use3022PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Sleep")] + public bool Use1348PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Bind")] + public bool Use1345PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Heavy")] + public bool Use1344PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Silence")] + public bool Use1347PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Stop attacking while in Guard.")] + public bool GuardCancel { get; set; } = false; + + private bool TryPurify(out IAction? action) + { + action = null; + if (!UsePurifyPvP) return false; + + var purifyStatuses = new Dictionary + { + { 1343, Use1343PvP }, + { 3219, Use3219PvP }, + { 3022, Use3022PvP }, + { 1348, Use1348PvP }, + { 1345, Use1345PvP }, + { 1344, Use1344PvP }, + { 1347, Use1347PvP } + }; + + foreach (var status in purifyStatuses) + { + if (status.Value && Player.HasStatus(true, (StatusID)status.Key)) + { + return PurifyPvP.CanUse(out action); + } + } + + return false; + } + + protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + if (TryPurify(out act)) return true; + if (UseRecuperatePvP && Player.CurrentHp / Player.MaxHp * 100 < RCValue && RecuperatePvP.CanUse(out act)) return true; + + if (RadiantAegisPvP.CanUse(out act)) return true; + + return base.EmergencyAbility(nextGCD, out act); + } + + protected override bool AttackAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + + if (MountainBusterPvP.CanUse(out act)) return true; + + return base.AttackAbility(nextGCD, out act); + } + protected override bool GeneralAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + + return base.GeneralAbility(nextGCD, out act); + } + protected override bool GeneralGCD(out IAction? act) + { + act = null; + // Early exits for Guard status or Sprint usage + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + if (!Player.HasStatus(true, StatusID.Guard) && UseSprintPvP && !Player.HasStatus(true, StatusID.Sprint) && !InCombat && SprintPvP.CanUse(out act)) return true; + + if (SlipstreamPvP.CanUse(out act)) return true; + + if (RuinIiiPvP.CanUse(out act)) return true; + + if (CrimsonStrikePvP.CanUse(out act)) return true; + if (CrimsonCyclonePvP.CanUse(out act)) return true; + + return base.GeneralGCD(out act); + } +} \ No newline at end of file diff --git a/BasicRotations/PVPRotations/Melee/DRG_Default.PVP.cs b/BasicRotations/PVPRotations/Melee/DRG_Default.PVP.cs new file mode 100644 index 000000000..b5a8de31d --- /dev/null +++ b/BasicRotations/PVPRotations/Melee/DRG_Default.PVP.cs @@ -0,0 +1,120 @@ +namespace DefaultRotations.Melee; + +[Rotation("Default PVP", CombatType.PvP, GameVersion = "7.00", Description = "Beta Rotation")] +[SourceCode(Path = "main/BasicRotations/PVPRotations/Tank/DRG_Default.PvP.cs")] +[Api(4)] +public sealed class DRG_DefaultPvP : DragoonRotation +{ + [RotationConfig(CombatType.PvP, Name = "Sprint")] + public bool UseSprintPvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Recuperate")] + public bool UseRecuperatePvP { get; set; } = false; + + [Range(1, 100, ConfigUnitType.Percent, 1)] + [RotationConfig(CombatType.PvP, Name = "RecuperateHP%%?")] + public int RCValue { get; set; } = 75; + + [RotationConfig(CombatType.PvP, Name = "Use Purify")] + public bool UsePurifyPvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Stun")] + public bool Use1343PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on DeepFreeze")] + public bool Use3219PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on HalfAsleep")] + public bool Use3022PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Sleep")] + public bool Use1348PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Bind")] + public bool Use1345PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Heavy")] + public bool Use1344PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Silence")] + public bool Use1347PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Stop attacking while in Guard.")] + public bool GuardCancel { get; set; } = false; + + private bool TryPurify(out IAction? action) + { + action = null; + if (!UsePurifyPvP) return false; + + var purifyStatuses = new Dictionary + { + { 1343, Use1343PvP }, + { 3219, Use3219PvP }, + { 3022, Use3022PvP }, + { 1348, Use1348PvP }, + { 1345, Use1345PvP }, + { 1344, Use1344PvP }, + { 1347, Use1347PvP } + }; + + foreach (var status in purifyStatuses) + { + if (status.Value && Player.HasStatus(true, (StatusID)status.Key)) + { + return PurifyPvP.CanUse(out action); + } + } + + return false; + } + + protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + if (TryPurify(out act)) return true; + if (UseRecuperatePvP && Player.CurrentHp / Player.MaxHp * 100 < RCValue && RecuperatePvP.CanUse(out act)) return true; + + return base.EmergencyAbility(nextGCD, out act); + } + + protected override bool AttackAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + + if (GeirskogulPvP.CanUse(out act)) return true; + if (NastrondPvP.CanUse(out act)) return true; + + if (HighJumpPvP.CanUse(out act)) return true; + + if (HorridRoarPvP.CanUse(out act)) return true; + + return base.AttackAbility(nextGCD, out act); + } + protected override bool GeneralAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + + return base.GeneralAbility(nextGCD, out act); + } + protected override bool GeneralGCD(out IAction? act) + { + act = null; + // Early exits for Guard status or Sprint usage + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + if (!Player.HasStatus(true, StatusID.Guard) && UseSprintPvP && !Player.HasStatus(true, StatusID.Sprint) && !InCombat && SprintPvP.CanUse(out act)) return true; + + if (WyrmwindThrustPvP.CanUse(out act)) return true; + + if (ChaoticSpringPvP.CanUse(out act)) return true; + + if (WheelingThrustPvP.CanUse(out act)) return true; + if (FangAndClawPvP.CanUse(out act)) return true; + if (RaidenThrustPvP.CanUse(out act)) return true; + + return base.GeneralGCD(out act); + } +} \ No newline at end of file diff --git a/BasicRotations/PVPRotations/Melee/MNK_Default.PVP.cs b/BasicRotations/PVPRotations/Melee/MNK_Default.PVP.cs new file mode 100644 index 000000000..f9b9aa21b --- /dev/null +++ b/BasicRotations/PVPRotations/Melee/MNK_Default.PVP.cs @@ -0,0 +1,128 @@ +namespace DefaultRotations.Melee; + +[Rotation("Default", CombatType.PvP, GameVersion = "7.00", Description = "Beta Rotation")] +[SourceCode(Path = "main/BasicRotations/PVPRotations/Melee/MNK_Default.PVP.cs")] +[Api(4)] +public sealed class MNK_DefaultPvP : MonkRotation +{ + [RotationConfig(CombatType.PvP, Name = "Sprint")] + public bool UseSprintPvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Recuperate")] + public bool UseRecuperatePvP { get; set; } = false; + + [Range(1, 100, ConfigUnitType.Percent, 1)] + [RotationConfig(CombatType.PvP, Name = "RecuperateHP%%?")] + public int RCValue { get; set; } = 75; + + [RotationConfig(CombatType.PvP, Name = "Use Purify")] + public bool UsePurifyPvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Stun")] + public bool Use1343PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on DeepFreeze")] + public bool Use3219PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on HalfAsleep")] + public bool Use3022PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Sleep")] + public bool Use1348PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Bind")] + public bool Use1345PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Heavy")] + public bool Use1344PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Silence")] + public bool Use1347PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Stop attacking while in Guard.")] + public bool GuardCancel { get; set; } = false; + + private bool TryPurify(out IAction? action) + { + action = null; + if (!UsePurifyPvP) return false; + + var purifyStatuses = new Dictionary + { + { 1343, Use1343PvP }, + { 3219, Use3219PvP }, + { 3022, Use3022PvP }, + { 1348, Use1348PvP }, + { 1345, Use1345PvP }, + { 1344, Use1344PvP }, + { 1347, Use1347PvP } + }; + + foreach (var status in purifyStatuses) + { + if (status.Value && Player.HasStatus(true, (StatusID)status.Key)) + { + return PurifyPvP.CanUse(out action); + } + } + + return false; + } + + protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + if (TryPurify(out act)) return true; + if (UseRecuperatePvP && Player.CurrentHp / Player.MaxHp * 100 < RCValue && RecuperatePvP.CanUse(out act)) return true; + + return base.EmergencyAbility(nextGCD, out act); + } + + protected override bool AttackAbility(IAction nextGCD, out IAction? act) + { + act = null; + + if (InCombat) + { + if (RisingPhoenixPvP.CanUse(out act)) return true; + } + if (InCombat) + { + if (ThunderclapPvP.CanUse(out act)) return true; + } + if (InCombat) + { + if (RiddleOfEarthPvP.CanUse(out act)) return true; + } + if (EarthsReplyPvP.CanUse(out act)) return true; + if (WindsReplyPvP.CanUse(out act)) return true; + if (FiresReplyPvP.CanUse(out act)) return true; + + return base.AttackAbility(nextGCD, out act); + } + + protected override bool GeneralGCD(out IAction? act) + { + act = null; + // Early exits for Guard status or Sprint usage + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + if (!Player.HasStatus(true, StatusID.Guard) && UseSprintPvP && !Player.HasStatus(true, StatusID.Sprint) && !InCombat && SprintPvP.CanUse(out act)) return true; + + if (FlintsReplyPvP.CanUse(out act)) return true; + + if (Player.HasStatus(true, StatusID.EarthResonance)) + { + if (EarthsReplyPvP.CanUse(out act)) return true; + } + if (PhantomRushPvP.CanUse(out act)) return true; + if (PouncingCoeurlPvP.CanUse(out act)) return true; + if (RisingRaptorPvP.CanUse(out act)) return true; + if (LeapingOpoPvP.CanUse(out act)) return true; + if (DemolishPvP.CanUse(out act)) return true; + if (TwinSnakesPvP.CanUse(out act)) return true; + if (DragonKickPvP.CanUse(out act)) return true; + + return false; + } +} \ No newline at end of file diff --git a/BasicRotations/PVPRotations/Melee/NIN_Default.PVP.cs b/BasicRotations/PVPRotations/Melee/NIN_Default.PVP.cs new file mode 100644 index 000000000..fcea5e83e --- /dev/null +++ b/BasicRotations/PVPRotations/Melee/NIN_Default.PVP.cs @@ -0,0 +1,111 @@ +namespace DefaultRotations.Melee; + +[Rotation("Default PVP", CombatType.PvP, GameVersion = "7.00", Description = "Beta Rotation")] +[SourceCode(Path = "main/BasicRotations/PVPRotations/Tank/NIN_Default.PvP.cs")] +[Api(4)] +public sealed class NIN_DefaultPvP : NinjaRotation +{ + [RotationConfig(CombatType.PvP, Name = "Sprint")] + public bool UseSprintPvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Recuperate")] + public bool UseRecuperatePvP { get; set; } = false; + + [Range(1, 100, ConfigUnitType.Percent, 1)] + [RotationConfig(CombatType.PvP, Name = "RecuperateHP%%?")] + public int RCValue { get; set; } = 75; + + [RotationConfig(CombatType.PvP, Name = "Use Purify")] + public bool UsePurifyPvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Stun")] + public bool Use1343PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on DeepFreeze")] + public bool Use3219PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on HalfAsleep")] + public bool Use3022PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Sleep")] + public bool Use1348PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Bind")] + public bool Use1345PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Heavy")] + public bool Use1344PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Silence")] + public bool Use1347PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Stop attacking while in Guard.")] + public bool GuardCancel { get; set; } = false; + + private bool TryPurify(out IAction? action) + { + action = null; + if (!UsePurifyPvP) return false; + + var purifyStatuses = new Dictionary + { + { 1343, Use1343PvP }, + { 3219, Use3219PvP }, + { 3022, Use3022PvP }, + { 1348, Use1348PvP }, + { 1345, Use1345PvP }, + { 1344, Use1344PvP }, + { 1347, Use1347PvP } + }; + + foreach (var status in purifyStatuses) + { + if (status.Value && Player.HasStatus(true, (StatusID)status.Key)) + { + return PurifyPvP.CanUse(out action); + } + } + + return false; + } + + protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + if (TryPurify(out act)) return true; + if (UseRecuperatePvP && Player.CurrentHp / Player.MaxHp * 100 < RCValue && RecuperatePvP.CanUse(out act)) return true; + + return base.EmergencyAbility(nextGCD, out act); + } + + protected override bool AttackAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + + return base.AttackAbility(nextGCD, out act); + } + protected override bool GeneralAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + + return base.GeneralAbility(nextGCD, out act); + } + protected override bool GeneralGCD(out IAction? act) + { + act = null; + // Early exits for Guard status or Sprint usage + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + if (!Player.HasStatus(true, StatusID.Guard) && UseSprintPvP && !Player.HasStatus(true, StatusID.Sprint) && !InCombat && SprintPvP.CanUse(out act)) return true; + + if (AeolianEdgePvP.CanUse(out act)) return true; + if (GustSlashPvP.CanUse(out act)) return true; + if (SpinningEdgePvP.CanUse(out act)) return true; + + if (FumaShurikenPvP.CanUse(out act)) return true; + + return base.GeneralGCD(out act); + } +} \ No newline at end of file diff --git a/BasicRotations/PVPRotations/Melee/RPR_Default.PVP.cs b/BasicRotations/PVPRotations/Melee/RPR_Default.PVP.cs new file mode 100644 index 000000000..c5db980a6 --- /dev/null +++ b/BasicRotations/PVPRotations/Melee/RPR_Default.PVP.cs @@ -0,0 +1,124 @@ +namespace DefaultRotations.Melee; + +[Rotation("Default PVP", CombatType.PvP, GameVersion = "7.00", Description = "Beta Rotation")] +[SourceCode(Path = "main/BasicRotations/PVPRotations/Tank/RPR_Default.PvP.cs")] +[Api(4)] +public sealed class RPR_DefaultPvP : ReaperRotation +{ + [RotationConfig(CombatType.PvP, Name = "Sprint")] + public bool UseSprintPvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Recuperate")] + public bool UseRecuperatePvP { get; set; } = false; + + [Range(1, 100, ConfigUnitType.Percent, 1)] + [RotationConfig(CombatType.PvP, Name = "RecuperateHP%%?")] + public int RCValue { get; set; } = 75; + + [RotationConfig(CombatType.PvP, Name = "Use Purify")] + public bool UsePurifyPvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Stun")] + public bool Use1343PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on DeepFreeze")] + public bool Use3219PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on HalfAsleep")] + public bool Use3022PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Sleep")] + public bool Use1348PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Bind")] + public bool Use1345PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Heavy")] + public bool Use1344PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Silence")] + public bool Use1347PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Stop attacking while in Guard.")] + public bool GuardCancel { get; set; } = false; + + private bool TryPurify(out IAction? action) + { + action = null; + if (!UsePurifyPvP) return false; + + var purifyStatuses = new Dictionary + { + { 1343, Use1343PvP }, + { 3219, Use3219PvP }, + { 3022, Use3022PvP }, + { 1348, Use1348PvP }, + { 1345, Use1345PvP }, + { 1344, Use1344PvP }, + { 1347, Use1347PvP } + }; + + foreach (var status in purifyStatuses) + { + if (status.Value && Player.HasStatus(true, (StatusID)status.Key)) + { + return PurifyPvP.CanUse(out action); + } + } + + return false; + } + + protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + if (TryPurify(out act)) return true; + if (UseRecuperatePvP && Player.CurrentHp / Player.MaxHp * 100 < RCValue && RecuperatePvP.CanUse(out act)) return true; + + if (ArcaneCrestPvP.CanUse(out act)) return true; + + return base.EmergencyAbility(nextGCD, out act); + } + + protected override bool AttackAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + + if (GrimSwathePvP.CanUse(out act)) return true; + + if (LemuresSlicePvP.CanUse(out act)) return true; + + if (HarvestMoonPvP.CanUse(out act)) return true; + + return base.AttackAbility(nextGCD, out act); + } + protected override bool GeneralAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + + return base.GeneralAbility(nextGCD, out act); + } + protected override bool GeneralGCD(out IAction? act) + { + act = null; + // Early exits for Guard status or Sprint usage + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + if (!Player.HasStatus(true, StatusID.Guard) && UseSprintPvP && !Player.HasStatus(true, StatusID.Sprint) && !InCombat && SprintPvP.CanUse(out act)) return true; + + //if (VoidReapingPvP.CanUse(out act, usedUp: true)) return true; + //if (CrossReapingPvP.CanUse(out act, usedUp: true)) return true; + //if (CommunioPvP.CanUse(out act, usedUp: true)) return true; + + + if (PlentifulHarvestPvP.CanUse(out act)) return true; + + if (InfernalSlicePvP.CanUse(out act)) return true; + if (WaxingSlicePvP.CanUse(out act)) return true; + if (SlicePvP.CanUse(out act)) return true; + + return base.GeneralGCD(out act); + } +} \ No newline at end of file diff --git a/BasicRotations/PVPRotations/Melee/SAM_Default.PVP.cs b/BasicRotations/PVPRotations/Melee/SAM_Default.PVP.cs new file mode 100644 index 000000000..c21927370 --- /dev/null +++ b/BasicRotations/PVPRotations/Melee/SAM_Default.PVP.cs @@ -0,0 +1,109 @@ +namespace DefaultRotations.Melee; + +[Rotation("Default PVP", CombatType.PvP, GameVersion = "7.00", Description = "Beta Rotation")] +[SourceCode(Path = "main/BasicRotations/PVPRotations/Tank/SAM_Default.PvP.cs")] +[Api(4)] +public sealed class SAM_DefaultPvP : SamuraiRotation +{ + [RotationConfig(CombatType.PvP, Name = "Sprint")] + public bool UseSprintPvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Recuperate")] + public bool UseRecuperatePvP { get; set; } = false; + + [Range(1, 100, ConfigUnitType.Percent, 1)] + [RotationConfig(CombatType.PvP, Name = "RecuperateHP%%?")] + public int RCValue { get; set; } = 75; + + [RotationConfig(CombatType.PvP, Name = "Use Purify")] + public bool UsePurifyPvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Stun")] + public bool Use1343PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on DeepFreeze")] + public bool Use3219PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on HalfAsleep")] + public bool Use3022PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Sleep")] + public bool Use1348PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Bind")] + public bool Use1345PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Heavy")] + public bool Use1344PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Silence")] + public bool Use1347PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Stop attacking while in Guard.")] + public bool GuardCancel { get; set; } = false; + + private bool TryPurify(out IAction? action) + { + action = null; + if (!UsePurifyPvP) return false; + + var purifyStatuses = new Dictionary + { + { 1343, Use1343PvP }, + { 3219, Use3219PvP }, + { 3022, Use3022PvP }, + { 1348, Use1348PvP }, + { 1345, Use1345PvP }, + { 1344, Use1344PvP }, + { 1347, Use1347PvP } + }; + + foreach (var status in purifyStatuses) + { + if (status.Value && Player.HasStatus(true, (StatusID)status.Key)) + { + return PurifyPvP.CanUse(out action); + } + } + + return false; + } + + protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + if (TryPurify(out act)) return true; + if (UseRecuperatePvP && Player.CurrentHp / Player.MaxHp * 100 < RCValue && RecuperatePvP.CanUse(out act)) return true; + + return base.EmergencyAbility(nextGCD, out act); + } + + protected override bool AttackAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + + return base.AttackAbility(nextGCD, out act); + } + protected override bool GeneralAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + + return base.GeneralAbility(nextGCD, out act); + } + protected override bool GeneralGCD(out IAction? act) + { + act = null; + // Early exits for Guard status or Sprint usage + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + if (!Player.HasStatus(true, StatusID.Guard) && UseSprintPvP && !Player.HasStatus(true, StatusID.Sprint) && !InCombat && SprintPvP.CanUse(out act)) return true; + + if (KashaPvP.CanUse(out act)) return true; + if (GekkoPvP.CanUse(out act)) return true; + if (YukikazePvP.CanUse(out act)) return true; + + return base.GeneralGCD(out act); + } +} \ No newline at end of file diff --git a/BasicRotations/PVPRotations/Melee/VPR_Default.PVP.cs b/BasicRotations/PVPRotations/Melee/VPR_Default.PVP.cs new file mode 100644 index 000000000..ac9b749da --- /dev/null +++ b/BasicRotations/PVPRotations/Melee/VPR_Default.PVP.cs @@ -0,0 +1,138 @@ +namespace DefaultRotations.Melee; + +[Rotation("Default PVP", CombatType.PvP, GameVersion = "7.05", Description = "Beta Rotation")] +[SourceCode(Path = "main/BasicRotations/PVPRotations/Tank/VPR_Default.PvP.cs")] +[Api(4)] +public sealed class VPR_DefaultPvP : ViperRotation +{ + private const double HealthThreshold = 0.7; + + [RotationConfig(CombatType.PvP, Name = "Sprint")] + public bool UseSprintPvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Recuperate")] + public bool UseRecuperatePvP { get; set; } = false; + + [Range(1, 100, ConfigUnitType.Percent, 1)] + [RotationConfig(CombatType.PvP, Name = "RecuperateHP%%?")] + public int RCValue { get; set; } = 75; + + [RotationConfig(CombatType.PvP, Name = "Use Purify")] + public bool UsePurifyPvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Stun")] + public bool Use1343PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on DeepFreeze")] + public bool Use3219PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on HalfAsleep")] + public bool Use3022PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Sleep")] + public bool Use1348PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Bind")] + public bool Use1345PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Heavy")] + public bool Use1344PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Silence")] + public bool Use1347PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Stop attacking while in Guard.")] + public bool GuardCancel { get; set; } = false; + + private bool TryPurify(out IAction? action) + { + action = null; + if (!UsePurifyPvP) return false; + + var purifyStatuses = new Dictionary + { + { 1343, Use1343PvP }, + { 3219, Use3219PvP }, + { 3022, Use3022PvP }, + { 1348, Use1348PvP }, + { 1345, Use1345PvP }, + { 1344, Use1344PvP }, + { 1347, Use1347PvP } + }; + + foreach (var status in purifyStatuses) + { + if (status.Value && Player.HasStatus(true, (StatusID)status.Key)) + { + return PurifyPvP.CanUse(out action); + } + } + + return false; + } + + protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (ShouldCancelGuard()) return false; + + if (Player.GetHealthRatio() < HealthThreshold && RecuperatePvP.CanUse(out act)) return true; + + if (SnakeScalesPvP.Cooldown.IsCoolingDown && UncoiledFuryPvP.Cooldown.IsCoolingDown && RattlingCoilPvP.CanUse(out act)) return true; + + return base.EmergencyAbility(nextGCD, out act); + } + + protected override bool AttackAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (ShouldCancelGuard()) return false; + + if (IsLastGCD((ActionID)UncoiledFuryPvP.ID) && UncoiledTwinfangPvP.CanUse(out act, skipAoeCheck: true)) return true; + if (IsLastGCD((ActionID)UncoiledFuryPvP.ID) && UncoiledTwinbloodPvP.CanUse(out act, skipAoeCheck: true)) return true; + + if (BacklashPvP_39187.CanUse(out act, skipAoeCheck: true)) return true; + + if (DeathRattlePvP.CanUse(out act, skipAoeCheck: true)) return true; + + return base.AttackAbility(nextGCD, out act); + } + + protected override bool GeneralAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (ShouldCancelGuard()) return false; + + return base.GeneralAbility(nextGCD, out act); + } + + protected override bool GeneralGCD(out IAction? act) + { + act = null; + if (ShouldCancelGuard()) return false; + + if (!Player.HasStatus(true, StatusID.Guard) && UseSprintPvP && !Player.HasStatus(true, StatusID.Sprint) && !InCombat && SprintPvP.CanUse(out act)) return true; + + if (Player.HasStatus(true, StatusID.HardenedScales)) return false; + + + if (UncoiledFuryPvP.CanUse(out act, skipAoeCheck: true)) return true; + + if (SanguineFeastPvP.CanUse(out act)) return true; + if (BloodcoilPvP.CanUse(out act)) return true; + + if (RavenousBitePvP.CanUse(out act)) return true; + if (SwiftskinsStingPvP.CanUse(out act)) return true; + if (PiercingFangsPvP.CanUse(out act)) return true; + if (BarbarousBitePvP.CanUse(out act)) return true; + if (HuntersStingPvP.CanUse(out act)) return true; + if (SteelFangsPvP.CanUse(out act)) return true; + + return base.GeneralGCD(out act); + } + + private bool ShouldCancelGuard() + { + return GuardCancel && Player.HasStatus(true, StatusID.Guard); + } +} \ No newline at end of file diff --git a/BasicRotations/PVPRotations/Ranged/BRD_Default.PVP.cs b/BasicRotations/PVPRotations/Ranged/BRD_Default.PVP.cs new file mode 100644 index 000000000..e99fac50c --- /dev/null +++ b/BasicRotations/PVPRotations/Ranged/BRD_Default.PVP.cs @@ -0,0 +1,131 @@ +namespace DefaultRotations.Ranged; + +[Rotation("Default PVP", CombatType.PvP, GameVersion = "7.1", Description = "Beta Rotation")] +[SourceCode(Path = "main/BasicRotations/PVPRotations/Ranged/BRD_Default.PvP.cs")] +[Api(4)] +public sealed class BRD_DefaultPvP : BardRotation +{ + [RotationConfig(CombatType.PvP, Name = "Sprint")] + public bool UseSprintPvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Recuperate")] + public bool UseRecuperatePvP { get; set; } = false; + + [Range(1, 100, ConfigUnitType.Percent, 1)] + [RotationConfig(CombatType.PvP, Name = "RecuperateHP%%?")] + public int RCValue { get; set; } = 75; + + [RotationConfig(CombatType.PvP, Name = "Use Purify")] + public bool UsePurifyPvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Stun")] + public bool Use1343PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on DeepFreeze")] + public bool Use3219PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on HalfAsleep")] + public bool Use3022PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Sleep")] + public bool Use1348PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Bind")] + public bool Use1345PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Heavy")] + public bool Use1344PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Silence")] + public bool Use1347PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Stop attacking while in Guard.")] + public bool GuardCancel { get; set; } = false; + + private bool TryPurify(out IAction? action) + { + action = null; + if (!UsePurifyPvP) return false; + + var purifyStatuses = new Dictionary + { + { 1343, Use1343PvP }, + { 3219, Use3219PvP }, + { 3022, Use3022PvP }, + { 1348, Use1348PvP }, + { 1345, Use1345PvP }, + { 1344, Use1344PvP }, + { 1347, Use1347PvP } + }; + + foreach (var status in purifyStatuses) + { + if (status.Value && Player.HasStatus(true, (StatusID)status.Key)) + { + return PurifyPvP.CanUse(out action); + } + } + + return false; + } + + protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + + if (TryPurify(out act)) return true; + + if (UseRecuperatePvP && Player.CurrentHp / Player.MaxHp * 100 < RCValue && RecuperatePvP.CanUse(out act)) return true; + + return base.EmergencyAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.TheWardensPaeanPvP)] + protected override bool DispelGCD(out IAction? act) + { + if (TheWardensPaeanPvP.CanUse(out act)) return true; + return base.DispelGCD(out act); + } + + protected override bool AttackAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + + if (EncoreOfLightPvP.CanUse(out act, skipAoeCheck: true)) return true; + + if (SilentNocturnePvP.CanUse(out act)) return true; + + if (RepellingShotPvP.CanUse(out act)) return true; + + return base.AttackAbility(nextGCD, out act); + + } + protected override bool GeneralAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + + return base.GeneralAbility(nextGCD, out act); + + } + protected override bool GeneralGCD(out IAction? act) + { + act = null; + // Early exits for Guard status or Sprint usage + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + if (!Player.HasStatus(true, StatusID.Guard) && UseSprintPvP && !Player.HasStatus(true, StatusID.Sprint) && !InCombat && SprintPvP.CanUse(out act)) return true; + + if (HarmonicArrowPvP_41964.CanUse(out act)) return true; + + if (BlastArrowPvP.CanUse(out act)) return true; + if (ApexArrowPvP.CanUse(out act)) return true; + + if (PitchPerfectPvP.CanUse(out act)) return true; + if (PowerfulShotPvP.CanUse(out act)) return true; + + return base.GeneralGCD(out act); + + } +} diff --git a/BasicRotations/PVPRotations/Ranged/DNC_Default.PVP.cs b/BasicRotations/PVPRotations/Ranged/DNC_Default.PVP.cs new file mode 100644 index 000000000..ed40b42ff --- /dev/null +++ b/BasicRotations/PVPRotations/Ranged/DNC_Default.PVP.cs @@ -0,0 +1,117 @@ +namespace DefaultRotations.Ranged; + +[Rotation("Default PVP", CombatType.PvP, GameVersion = "7.00", Description = "Beta Rotation")] +[SourceCode(Path = "main/BasicRotations/PVPRotations/Ranged/DNC_Default.PvP.cs")] +[Api(4)] +public sealed class DNC_DefaultPvP : DancerRotation +{ + [RotationConfig(CombatType.PvP, Name = "Sprint")] + public bool UseSprintPvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Recuperate")] + public bool UseRecuperatePvP { get; set; } = false; + + [Range(1, 100, ConfigUnitType.Percent, 1)] + [RotationConfig(CombatType.PvP, Name = "RecuperateHP%%?")] + public int RCValue { get; set; } = 75; + + [RotationConfig(CombatType.PvP, Name = "Use Purify")] + public bool UsePurifyPvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Stun")] + public bool Use1343PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on DeepFreeze")] + public bool Use3219PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on HalfAsleep")] + public bool Use3022PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Sleep")] + public bool Use1348PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Bind")] + public bool Use1345PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Heavy")] + public bool Use1344PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Silence")] + public bool Use1347PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Stop attacking while in Guard.")] + public bool GuardCancel { get; set; } = false; + + private bool TryPurify(out IAction? action) + { + action = null; + if (!UsePurifyPvP) return false; + + var purifyStatuses = new Dictionary + { + { 1343, Use1343PvP }, + { 3219, Use3219PvP }, + { 3022, Use3022PvP }, + { 1348, Use1348PvP }, + { 1345, Use1345PvP }, + { 1344, Use1344PvP }, + { 1347, Use1347PvP } + }; + + foreach (var status in purifyStatuses) + { + if (status.Value && Player.HasStatus(true, (StatusID)status.Key)) + { + return PurifyPvP.CanUse(out action); + } + } + + return false; + } + + protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + if (TryPurify(out act)) return true; + if (UseRecuperatePvP && Player.CurrentHp / Player.MaxHp * 100 < RCValue && RecuperatePvP.CanUse(out act)) return true; + + if (CuringWaltzPvP.CanUse(out act)) return true; + + return base.EmergencyAbility(nextGCD, out act); + } + + protected override bool AttackAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + + if (FanDancePvP.CanUse(out act)) return true; + + return base.AttackAbility(nextGCD, out act); + + } + protected override bool GeneralAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + + return base.GeneralAbility(nextGCD, out act); + + } + protected override bool GeneralGCD(out IAction? act) + { + act = null; + // Early exits for Guard status or Sprint usage + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + if (!Player.HasStatus(true, StatusID.Guard) && UseSprintPvP && !Player.HasStatus(true, StatusID.Sprint) && !InCombat && SprintPvP.CanUse(out act)) return true; + + if (StarfallDancePvP.CanUse(out act)) return true; + + if (FountainPvP.CanUse(out act)) return true; + if (CascadePvP.CanUse(out act)) return true; + + return base.GeneralGCD(out act); + + } +} diff --git a/BasicRotations/PVPRotations/Ranged/MCH_Default.PvP.cs b/BasicRotations/PVPRotations/Ranged/MCH_Default.PvP.cs new file mode 100644 index 000000000..652e4b431 --- /dev/null +++ b/BasicRotations/PVPRotations/Ranged/MCH_Default.PvP.cs @@ -0,0 +1,138 @@ +namespace DefaultRotations.Ranged; + +[Rotation("Default PVP", CombatType.PvP, GameVersion = "7.00", Description = "Beta Rotation")] +[SourceCode(Path = "main/BasicRotations/PVPRotations/Ranged/MCH_Default.PvP.cs")] +[Api(4)] +public sealed class MCH_DefaultPvP : MachinistRotation +{ + [RotationConfig(CombatType.PvP, Name = "Sprint")] + public bool UseSprintPvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Recuperate")] + public bool UseRecuperatePvP { get; set; } = false; + + [Range(1, 100, ConfigUnitType.Percent, 1)] + [RotationConfig(CombatType.PvP, Name = "RecuperateHP%%?")] + public int RCValue { get; set; } = 75; + + [RotationConfig(CombatType.PvP, Name = "Use Purify")] + public bool UsePurifyPvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Stun")] + public bool Use1343PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on DeepFreeze")] + public bool Use3219PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on HalfAsleep")] + public bool Use3022PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Sleep")] + public bool Use1348PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Bind")] + public bool Use1345PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Heavy")] + public bool Use1344PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Silence")] + public bool Use1347PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Stop attacking while in Guard.")] + public bool GuardCancel { get; set; } = false; + + + private bool TryPurify(out IAction? action) + { + action = null; + if (!UsePurifyPvP) return false; + + var purifyStatuses = new Dictionary + { + { 1343, Use1343PvP }, + { 3219, Use3219PvP }, + { 3022, Use3022PvP }, + { 1348, Use1348PvP }, + { 1345, Use1345PvP }, + { 1344, Use1344PvP }, + { 1347, Use1347PvP } + }; + + foreach (var status in purifyStatuses) + { + if (status.Value && Player.HasStatus(true, (StatusID)status.Key)) + { + return PurifyPvP.CanUse(out action); + } + } + + return false; + } + + protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + if (TryPurify(out act)) return true; + if (UseRecuperatePvP && Player.CurrentHp / Player.MaxHp * 100 < RCValue && RecuperatePvP.CanUse(out act)) return true; + + return base.EmergencyAbility(nextGCD, out act); + } + + protected override bool AttackAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + + if (BishopAutoturretPvP.CanUse(out act, true, true, true, true, true)) return true; + // Use WildfirePvP if Overheated + if (Player.HasStatus(true, StatusID.Overheated_3149) && WildfirePvP.CanUse(out act, skipAoeCheck: true, skipComboCheck: true)) return true; + + // Check if BioblasterPvP, AirAnchorPvP, or ChainSawPvP can be used + if (InCombat && !Player.HasStatus(true, StatusID.Analysis) && + (BioblasterPvP.CanUse(out act) && HostileTarget.DistanceToPlayer() <= 12 || AirAnchorPvP.CanUse(out act) || ChainSawPvP.CanUse(out act)) && + AnalysisPvP.CanUse(out act, usedUp: true)) return true; + + return base.AttackAbility(nextGCD, out act); + } + + protected override bool GeneralGCD(out IAction? act) + { + act = null; + if (Player.HasStatus(true, StatusID.Guard)) return false; + + if (!Player.HasStatus(true, StatusID.Overheated_3149) && ScattergunPvP.CanUse(out act, skipAoeCheck: true) && HostileTarget.DistanceToPlayer() <= 10) return true; + + if (Player.HasStatus(true, StatusID.Analysis)) + { + if (Player.HasStatus(true, StatusID.AirAnchorPrimed) && !Player.HasStatus(true, StatusID.BioblasterPrimed, StatusID.ChainSawPrimed, StatusID.DrillPrimed, StatusID.Overheated_3149) && AirAnchorPvP.CanUse(out act, usedUp: true)) return true; + if (Player.HasStatus(true, StatusID.BioblasterPrimed) && !Player.HasStatus(true, StatusID.AirAnchorPrimed, StatusID.ChainSawPrimed, StatusID.DrillPrimed, StatusID.Overheated_3149) && BioblasterPvP.CanUse(out act, skipAoeCheck: true, usedUp: true)) return true; + if (Player.HasStatus(true, StatusID.ChainSawPrimed) && !Player.HasStatus(true, StatusID.BioblasterPrimed, StatusID.BioblasterPrimed, StatusID.DrillPrimed, StatusID.Overheated_3149) && ChainSawPvP.CanUse(out act, skipAoeCheck: true)) return true; + if (Player.HasStatus(true, StatusID.DrillPrimed) && !Player.HasStatus(true, StatusID.BioblasterPrimed, StatusID.ChainSawPrimed, StatusID.AirAnchorPrimed, StatusID.Overheated_3149) && DrillPvP.CanUse(out act, usedUp: true)) return true; + } + + if (AirAnchorPvP.Cooldown.CurrentCharges == 2 && Player.HasStatus(true, StatusID.AirAnchorPrimed) && !Player.HasStatus(true, StatusID.BioblasterPrimed, StatusID.ChainSawPrimed, StatusID.DrillPrimed, StatusID.Overheated_3149) && AirAnchorPvP.CanUse(out act)) return true; + if (BioblasterPvP.Cooldown.CurrentCharges == 2 && Player.HasStatus(true, StatusID.BioblasterPrimed) && !Player.HasStatus(true, StatusID.AirAnchorPrimed, StatusID.ChainSawPrimed, StatusID.DrillPrimed, StatusID.Overheated_3149) && BioblasterPvP.CanUse(out act, skipAoeCheck: true)) return true; + if (ChainSawPvP.Cooldown.CurrentCharges == 2 && Player.HasStatus(true, StatusID.ChainSawPrimed) && !Player.HasStatus(true, StatusID.BioblasterPrimed, StatusID.BioblasterPrimed, StatusID.DrillPrimed, StatusID.Overheated_3149) && ChainSawPvP.CanUse(out act, skipAoeCheck: true)) return true; + if (DrillPvP.Cooldown.CurrentCharges == 2 && Player.HasStatus(true, StatusID.DrillPrimed) && !Player.HasStatus(true, StatusID.BioblasterPrimed, StatusID.ChainSawPrimed, StatusID.AirAnchorPrimed, StatusID.Overheated_3149) && DrillPvP.CanUse(out act)) return true; + + if (Player.HasStatus(true, StatusID.Overheated_3149)) + { + act = null; + + { + if (WildfirePvP.CanUse(out act)) return true; + } + if (WildfirePvP.IsInCooldown) + { + if (BlastChargePvP.CanUse(out act, skipCastingCheck: true)) return true; + } + return false; + } + + if (BlastChargePvP.CanUse(out act, skipCastingCheck: true)) return true; + + return base.GeneralGCD(out act); + } +} \ No newline at end of file diff --git a/BasicRotations/PVPRotations/Tank/DRK_Default.PVP.cs b/BasicRotations/PVPRotations/Tank/DRK_Default.PVP.cs new file mode 100644 index 000000000..33026244e --- /dev/null +++ b/BasicRotations/PVPRotations/Tank/DRK_Default.PVP.cs @@ -0,0 +1,114 @@ +namespace DefaultRotations.Tank; + +[Rotation("Default PVP", CombatType.PvP, GameVersion = "7.00", Description = "Beta Rotation")] +[SourceCode(Path = "main/BasicRotations/PVPRotations/Tank/DRK_Default.PvP.cs")] +[Api(4)] +public sealed class DRK_DefaultPvP : DarkKnightRotation +{ + [RotationConfig(CombatType.PvP, Name = "Sprint")] + public bool UseSprintPvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Recuperate")] + public bool UseRecuperatePvP { get; set; } = false; + + [Range(1, 100, ConfigUnitType.Percent, 1)] + [RotationConfig(CombatType.PvP, Name = "RecuperateHP%%?")] + public int RCValue { get; set; } = 75; + + [RotationConfig(CombatType.PvP, Name = "Use Purify")] + public bool UsePurifyPvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Stun")] + public bool Use1343PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on DeepFreeze")] + public bool Use3219PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on HalfAsleep")] + public bool Use3022PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Sleep")] + public bool Use1348PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Bind")] + public bool Use1345PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Heavy")] + public bool Use1344PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Silence")] + public bool Use1347PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Stop attacking while in Guard.")] + public bool GuardCancel { get; set; } = false; + + private bool TryPurify(out IAction? action) + { + action = null; + if (!UsePurifyPvP) return false; + + var purifyStatuses = new Dictionary + { + { 1343, Use1343PvP }, + { 3219, Use3219PvP }, + { 3022, Use3022PvP }, + { 1348, Use1348PvP }, + { 1345, Use1345PvP }, + { 1344, Use1344PvP }, + { 1347, Use1347PvP } + }; + + foreach (var status in purifyStatuses) + { + if (status.Value && Player.HasStatus(true, (StatusID)status.Key)) + { + return PurifyPvP.CanUse(out action); + } + } + + return false; + } + + protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + if (TryPurify(out act)) return true; + if (UseRecuperatePvP && Player.CurrentHp / Player.MaxHp * 100 < RCValue && RecuperatePvP.CanUse(out act)) return true; + + if (TheBlackestNightPvP.CanUse(out act)) return true; + + return base.EmergencyAbility(nextGCD, out act); + } + + protected override bool AttackAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + + if (SaltedEarthPvP.CanUse(out act)) return true; + if (SaltAndDarknessPvP.CanUse(out act)) return true; + if (PlungePvP.CanUse(out act)) return true; + + return base.AttackAbility(nextGCD, out act); + } + protected override bool GeneralAbility(IAction nextGCD, out IAction? act) + { + + return base.GeneralAbility(nextGCD, out act); + } + protected override bool GeneralGCD(out IAction? act) + { + act = null; + // Early exits for Guard status or Sprint usage + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + if (!Player.HasStatus(true, StatusID.Guard) && UseSprintPvP && !Player.HasStatus(true, StatusID.Sprint) && !InCombat && SprintPvP.CanUse(out act)) return true; + + + if (SouleaterPvP.CanUse(out act)) return true; + if (SyphonStrikePvP.CanUse(out act)) return true; + if (HardSlashPvP.CanUse(out act)) return true; + + return base.GeneralGCD(out act); + } +} \ No newline at end of file diff --git a/BasicRotations/PVPRotations/Tank/GNB_Default.PVP.cs b/BasicRotations/PVPRotations/Tank/GNB_Default.PVP.cs new file mode 100644 index 000000000..e088d344e --- /dev/null +++ b/BasicRotations/PVPRotations/Tank/GNB_Default.PVP.cs @@ -0,0 +1,110 @@ +namespace DefaultRotations.Tank; + +[Rotation("Default PVP", CombatType.PvP, GameVersion = "7.00", Description = "Beta Rotation")] +[SourceCode(Path = "main/BasicRotations/PVPRotations/Tank/GNB_Default.PvP.cs")] +[Api(4)] +public sealed class GNB_DefaultPvP : GunbreakerRotation +{ + [RotationConfig(CombatType.PvP, Name = "Sprint")] + public bool UseSprintPvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Recuperate")] + public bool UseRecuperatePvP { get; set; } = false; + + [Range(1, 100, ConfigUnitType.Percent, 1)] + [RotationConfig(CombatType.PvP, Name = "RecuperateHP%%?")] + public int RCValue { get; set; } = 75; + + [RotationConfig(CombatType.PvP, Name = "Use Purify")] + public bool UsePurifyPvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Stun")] + public bool Use1343PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on DeepFreeze")] + public bool Use3219PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on HalfAsleep")] + public bool Use3022PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Sleep")] + public bool Use1348PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Bind")] + public bool Use1345PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Heavy")] + public bool Use1344PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Silence")] + public bool Use1347PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Stop attacking while in Guard.")] + public bool GuardCancel { get; set; } = false; + + private bool TryPurify(out IAction? action) + { + action = null; + if (!UsePurifyPvP) return false; + + var purifyStatuses = new Dictionary + { + { 1343, Use1343PvP }, + { 3219, Use3219PvP }, + { 3022, Use3022PvP }, + { 1348, Use1348PvP }, + { 1345, Use1345PvP }, + { 1344, Use1344PvP }, + { 1347, Use1347PvP } + }; + + foreach (var status in purifyStatuses) + { + if (status.Value && Player.HasStatus(true, (StatusID)status.Key)) + { + return PurifyPvP.CanUse(out action); + } + } + + return false; + } + + protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + if (TryPurify(out act)) return true; + if (UseRecuperatePvP && Player.CurrentHp / Player.MaxHp * 100 < RCValue && RecuperatePvP.CanUse(out act)) return true; + + return base.EmergencyAbility(nextGCD, out act); + } + + protected override bool AttackAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + + return base.AttackAbility(nextGCD, out act); + } + protected override bool GeneralAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + + return base.GeneralAbility(nextGCD, out act); + } + protected override bool GeneralGCD(out IAction? act) + { + act = null; + // Early exits for Guard status or Sprint usage + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + if (!Player.HasStatus(true, StatusID.Guard) && UseSprintPvP && !Player.HasStatus(true, StatusID.Sprint) && !InCombat && SprintPvP.CanUse(out act)) return true; + + if (SolidBarrelPvP.CanUse(out act)) return true; + if (BrutalShellPvP.CanUse(out act)) return true; + if (KeenEdgePvP.CanUse(out act)) return true; + + + return base.GeneralGCD(out act); + } +} \ No newline at end of file diff --git a/BasicRotations/PVPRotations/Tank/PLD_Default.PVP.cs b/BasicRotations/PVPRotations/Tank/PLD_Default.PVP.cs new file mode 100644 index 000000000..a58be198f --- /dev/null +++ b/BasicRotations/PVPRotations/Tank/PLD_Default.PVP.cs @@ -0,0 +1,121 @@ +namespace DefaultRotations.Tank; + +[Rotation("Default PVP", CombatType.PvP, GameVersion = "7.00", Description = "Beta Rotation")] +[SourceCode(Path = "main/BasicRotations/PVPRotations/Tank/PLD_Default.PvP.cs")] +[Api(4)] +public sealed class PLD_DefaultPvP : PaladinRotation +{ + [RotationConfig(CombatType.PvP, Name = "Sprint")] + public bool UseSprintPvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Recuperate")] + public bool UseRecuperatePvP { get; set; } = false; + + [Range(1, 100, ConfigUnitType.Percent, 1)] + [RotationConfig(CombatType.PvP, Name = "RecuperateHP%%?")] + public int RCValue { get; set; } = 75; + + [RotationConfig(CombatType.PvP, Name = "Use Purify")] + public bool UsePurifyPvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Stun")] + public bool Use1343PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on DeepFreeze")] + public bool Use3219PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on HalfAsleep")] + public bool Use3022PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Sleep")] + public bool Use1348PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Bind")] + public bool Use1345PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Heavy")] + public bool Use1344PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Silence")] + public bool Use1347PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Stop attacking while in Guard.")] + public bool GuardCancel { get; set; } = false; + + private bool TryPurify(out IAction? action) + { + action = null; + if (!UsePurifyPvP) return false; + + var purifyStatuses = new Dictionary + { + { 1343, Use1343PvP }, + { 3219, Use3219PvP }, + { 3022, Use3022PvP }, + { 1348, Use1348PvP }, + { 1345, Use1345PvP }, + { 1344, Use1344PvP }, + { 1347, Use1347PvP } + }; + + foreach (var status in purifyStatuses) + { + if (status.Value && Player.HasStatus(true, (StatusID)status.Key)) + { + return PurifyPvP.CanUse(out action); + } + } + + return false; + } + + protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + if (TryPurify(out act)) return true; + if (UseRecuperatePvP && Player.CurrentHp / Player.MaxHp * 100 < RCValue && RecuperatePvP.CanUse(out act)) return true; + + if (GuardianPvP.CanUse(out act)) return true; + + return base.EmergencyAbility(nextGCD, out act); + } + + protected override bool AttackAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + + if (IntervenePvP.CanUse(out act)) return true; + + return base.AttackAbility(nextGCD, out act); + } + protected override bool GeneralAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + if (HolySheltronPvP.CanUse(out act)) return true; + + return base.GeneralAbility(nextGCD, out act); + } + protected override bool GeneralGCD(out IAction? act) + { + act = null; + // Early exits for Guard status or Sprint usage + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + if (!Player.HasStatus(true, StatusID.Guard) && UseSprintPvP && !Player.HasStatus(true, StatusID.Sprint) && !InCombat && SprintPvP.CanUse(out act)) return true; + + if (BladeOfValorPvP.CanUse(out act, skipAoeCheck: true)) return true; + if (BladeOfTruthPvP.CanUse(out act, skipAoeCheck: true)) return true; + if (BladeOfFaithPvP.CanUse(out act, skipAoeCheck: true) && Player.HasStatus(true, StatusID.BladeOfFaithReady)) return true; + + if (ConfiteorPvP.CanUse(out act, skipAoeCheck: true)) return true; + + if (RoyalAuthorityPvP.CanUse(out act)) return true; + if (RiotBladePvP.CanUse(out act)) return true; + if (FastBladePvP.CanUse(out act)) return true; + + + return base.GeneralGCD(out act); + } +} \ No newline at end of file diff --git a/BasicRotations/PVPRotations/Tank/WAR_Default.PVP.cs b/BasicRotations/PVPRotations/Tank/WAR_Default.PVP.cs new file mode 100644 index 000000000..0b324718a --- /dev/null +++ b/BasicRotations/PVPRotations/Tank/WAR_Default.PVP.cs @@ -0,0 +1,120 @@ +namespace DefaultRotations.Tank; + +[Rotation("Default PVP", CombatType.PvP, GameVersion = "7.00", Description = "Beta Rotation")] +[SourceCode(Path = "main/BasicRotations/PVPRotations/Tank/WAR_Default.PvP.cs")] +[Api(4)] +public sealed class WAR_DefaultPvP : WarriorRotation +{ + [RotationConfig(CombatType.PvP, Name = "Sprint")] + public bool UseSprintPvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Recuperate")] + public bool UseRecuperatePvP { get; set; } = false; + + [Range(1, 100, ConfigUnitType.Percent, 1)] + [RotationConfig(CombatType.PvP, Name = "RecuperateHP%%?")] + public int RCValue { get; set; } = 75; + + [RotationConfig(CombatType.PvP, Name = "Use Purify")] + public bool UsePurifyPvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Stun")] + public bool Use1343PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on DeepFreeze")] + public bool Use3219PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on HalfAsleep")] + public bool Use3022PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Sleep")] + public bool Use1348PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Bind")] + public bool Use1345PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Heavy")] + public bool Use1344PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Use Purify on Silence")] + public bool Use1347PvP { get; set; } = false; + + [RotationConfig(CombatType.PvP, Name = "Stop attacking while in Guard.")] + public bool GuardCancel { get; set; } = false; + + private bool TryPurify(out IAction? action) + { + action = null; + if (!UsePurifyPvP) return false; + + var purifyStatuses = new Dictionary + { + { 1343, Use1343PvP }, + { 3219, Use3219PvP }, + { 3022, Use3022PvP }, + { 1348, Use1348PvP }, + { 1345, Use1345PvP }, + { 1344, Use1344PvP }, + { 1347, Use1347PvP } + }; + + foreach (var status in purifyStatuses) + { + if (status.Value && Player.HasStatus(true, (StatusID)status.Key)) + { + return PurifyPvP.CanUse(out action); + } + } + + return false; + } + + protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + if (TryPurify(out act)) return true; + if (UseRecuperatePvP && Player.CurrentHp / Player.MaxHp * 100 < RCValue && RecuperatePvP.CanUse(out act)) return true; + + return base.EmergencyAbility(nextGCD, out act); + } + + protected override bool AttackAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + + if (OrogenyPvP.CanUse(out act)) return true; + if (OnslaughtPvP.CanUse(out act)) return true; + + return base.AttackAbility(nextGCD, out act); + } + protected override bool GeneralAbility(IAction nextGCD, out IAction? act) + { + act = null; + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + if (BloodwhettingPvP.CanUse(out act)) return true; + + return base.GeneralAbility(nextGCD, out act); + } + protected override bool GeneralGCD(out IAction? act) + { + act = null; + // Early exits for Guard status or Sprint usage + if (GuardCancel && Player.HasStatus(true, StatusID.Guard)) return false; + if (!Player.HasStatus(true, StatusID.Guard) && UseSprintPvP && !Player.HasStatus(true, StatusID.Sprint) && !InCombat && SprintPvP.CanUse(out act)) return true; + + if (PrimalRendPvP.CanUse(out act)) return true; + + if (ChaoticCyclonePvP.CanUse(out act)) return true; + + // if (FellCleavePvP.CanUse(out act)) return true; + + if (StormsPathPvP.CanUse(out act)) return true; + if (MaimPvP.CanUse(out act)) return true; + if (HeavySwingPvP.CanUse(out act)) return true; + + + return base.GeneralGCD(out act); + } +} \ No newline at end of file diff --git a/BasicRotations/Ranged/BRD_Default.cs b/BasicRotations/Ranged/BRD_Default.cs new file mode 100644 index 000000000..c6059bf11 --- /dev/null +++ b/BasicRotations/Ranged/BRD_Default.cs @@ -0,0 +1,295 @@ +namespace DefaultRotations.Ranged; + +[Rotation("Default", CombatType.PvE, GameVersion = "7.10", + Description = "Please make sure that the three song times add up to 120 seconds, Wanderers default first song for now.")] +[SourceCode(Path = "main/BasicRotations/Ranged/BRD_Default.cs")] +[Api(4)] +public sealed class BRD_Default : BardRotation +{ + #region Config Options + + [Range(1, 5, ConfigUnitType.Seconds, 0.1f)] + [RotationConfig(CombatType.PvE, Name = "Buff Alighnment Timer (Experimental, do not touch if you don't understand it)")] + public float BuffAlignment { get; set; } = 1; + + [RotationConfig(CombatType.PvE, Name = "Attempt to assign Raging Strikes, Battle Voice, and Radiant Finale to specific ogcd slots (Experimental)")] + public bool OGCDTimers { get; set; } = false; + + [Range(1, 45, ConfigUnitType.Seconds, 1)] + [RotationConfig(CombatType.PvE, Name = "Wanderer's Minuet Uptime")] + public float WANDTime { get; set; } = 43; + + [Range(0, 45, ConfigUnitType.Seconds, 1)] + [RotationConfig(CombatType.PvE, Name = "Mage's Ballad Uptime")] + public float MAGETime { get; set; } = 34; + + [Range(0, 45, ConfigUnitType.Seconds, 1)] + [RotationConfig(CombatType.PvE, Name = "Army's Paeon Uptime")] + public float ARMYTime { get; set; } = 43; + + [RotationConfig(CombatType.PvE, Name = "First Song")] + private Song FirstSong { get; set; } = Song.WANDERER; + + private float WANDRemainTime => 45 - WANDTime; + private float MAGERemainTime => 45 - MAGETime; + private float ARMYRemainTime => 45 - ARMYTime; + + private static bool InBurstStatus => (Player.Level > 50 && !Player.WillStatusEnd(0, true, StatusID.RagingStrikes)) + || (Player.Level >= 50 && Player.Level < 90 && !Player.WillStatusEnd(0, true, StatusID.RagingStrikes) && !Player.WillStatusEnd(0, true, StatusID.BattleVoice)) + || (MinstrelsCodaTrait.EnoughLevel && !Player.WillStatusEnd(0, true, StatusID.RagingStrikes) && !Player.WillStatusEnd(0, true, StatusID.RadiantFinale) && !Player.WillStatusEnd(0, true, StatusID.BattleVoice)); + + #endregion + + #region Countdown logic + // Defines logic for actions to take during the countdown before combat starts. + protected override IAction? CountDownAction(float remainTime) + { + // tincture needs to be used on -0.7s exactly + if (remainTime <= 0.7f && UseBurstMedicine(out var act)) return act; + return base.CountDownAction(remainTime); + } + #endregion + + #region oGCD Logic + protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) + { + if (nextGCD.IsTheSameTo(true, StraightShotPvE, VenomousBitePvE, WindbitePvE, IronJawsPvE)) + { + return base.EmergencyAbility(nextGCD, out act); + } + else if (!RagingStrikesPvE.EnoughLevel || Player.HasStatus(true, StatusID.RagingStrikes)) + { + if ((EmpyrealArrowPvE.Cooldown.IsCoolingDown && !EmpyrealArrowPvE.Cooldown.WillHaveOneChargeGCD(1) || !EmpyrealArrowPvE.EnoughLevel) && Repertoire != 3) + { + if (!Player.HasStatus(true, StatusID.HawksEye_3861) && BarragePvE.CanUse(out act)) return true; + } + } + + return base.EmergencyAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.TheWardensPaeanPvE)] + protected override bool DispelGCD(out IAction? act) + { + if (TheWardensPaeanPvE.CanUse(out act)) return true; + return base.DispelGCD(out act); + } + + [RotationDesc(ActionID.NaturesMinnePvE)] + protected override bool HealSingleAbility(IAction nextGCD, out IAction? act) + { + if (NaturesMinnePvE.CanUse(out act)) return true; + return base.HealSingleAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.TroubadourPvE)] + protected override bool DefenseAreaAbility(IAction nextGCD, out IAction act) + { + if (TroubadourPvE.CanUse(out act)) return true; + return false; + } + + protected override bool AttackAbility(IAction nextGCD, out IAction? act) + { + act = null; + + if (Song == Song.NONE && InCombat) + { + switch (FirstSong) + { + case Song.WANDERER: + if (TheWanderersMinuetPvE.CanUse(out act)) return true; + break; + + case Song.ARMY: + if (ArmysPaeonPvE.CanUse(out act)) return true; + break; + + case Song.MAGE: + if (MagesBalladPvE.CanUse(out act)) return true; + break; + } + if (TheWanderersMinuetPvE.CanUse(out act)) return true; + if (MagesBalladPvE.CanUse(out act)) return true; + if (ArmysPaeonPvE.CanUse(out act)) return true; + } + + if (IsBurst && Song != Song.NONE && MagesBalladPvE.EnoughLevel) + { + if (((!RadiantFinalePvE.EnoughLevel && !RagingStrikesPvE.Cooldown.IsCoolingDown) + || (RadiantFinalePvE.EnoughLevel && !RadiantFinalePvE.Cooldown.IsCoolingDown && RagingStrikesPvE.EnoughLevel && (!RagingStrikesPvE.Cooldown.IsCoolingDown || RagingStrikesPvE.Cooldown.WillHaveOneCharge(BuffAlignment)))) + && (HostileTarget?.HasStatus(true, StatusID.Windbite, StatusID.Stormbite) == true) && (HostileTarget?.HasStatus(true, StatusID.VenomousBite, StatusID.CausticBite) == true) && BattleVoicePvE.CanUse(out act, isLastAbility: OGCDTimers)) return true; + + if (!Player.WillStatusEnd(0, true, StatusID.BattleVoice) && RadiantFinalePvE.CanUse(out act, isFirstAbility: OGCDTimers)) return true; + + if (((RadiantFinalePvE.EnoughLevel && !Player.WillStatusEnd(0, true, StatusID.RadiantFinale) && !Player.WillStatusEnd(0, true, StatusID.BattleVoice)) + || (!RadiantFinalePvE.EnoughLevel && BattleVoicePvE.EnoughLevel && !Player.WillStatusEnd(0, true, StatusID.BattleVoice)) + || (!RadiantFinalePvE.EnoughLevel && !BattleVoicePvE.EnoughLevel)) + && RagingStrikesPvE.CanUse(out act, isLastAbility: OGCDTimers)) return true; + } + + if (RadiantFinalePvE.EnoughLevel && RadiantFinalePvE.Cooldown.IsCoolingDown && BattleVoicePvE.EnoughLevel && !BattleVoicePvE.Cooldown.IsCoolingDown) return false; + + if (TheWanderersMinuetPvE.CanUse(out act) && InCombat) + { + if (SongEndAfter(ARMYRemainTime) && (Song != Song.NONE || Player.HasStatus(true, StatusID.ArmysEthos))) return true; + } + + if (Song != Song.NONE && EmpyrealArrowPvE.CanUse(out act)) return true; + + if (PitchPerfectPvE.CanUse(out act, skipCastingCheck: true, skipAoeCheck: true, skipComboCheck: true)) + { + if (SongEndAfter(3) && Repertoire > 0) return true; + + if (Repertoire == 3) return true; + + if (Repertoire == 2 && EmpyrealArrowPvE.Cooldown.WillHaveOneChargeGCD() && RadiantFinalePvE.Cooldown.IsCoolingDown) return true; + } + + if (MagesBalladPvE.CanUse(out act) && InCombat) + { + if (Song == Song.WANDERER && SongEndAfter(WANDRemainTime) && Repertoire == 0) return true; + if (Song == Song.ARMY && SongEndAfterGCD(2) && TheWanderersMinuetPvE.Cooldown.IsCoolingDown) return true; + } + + if (ArmysPaeonPvE.CanUse(out act) && InCombat) + { + if (TheWanderersMinuetPvE.EnoughLevel && SongEndAfter(MAGERemainTime) && Song == Song.MAGE) return true; + if (TheWanderersMinuetPvE.EnoughLevel && SongEndAfter(2) && MagesBalladPvE.Cooldown.IsCoolingDown && Song == Song.WANDERER) return true; + if (!TheWanderersMinuetPvE.EnoughLevel && SongEndAfter(2)) return true; + } + + if (SidewinderPvE.CanUse(out act)) + { + if (Player.HasStatus(true, StatusID.BattleVoice) && (Player.HasStatus(true, StatusID.RadiantFinale) && RagingStrikesPvE.Cooldown.IsCoolingDown || !RadiantFinalePvE.EnoughLevel)) return true; + + if (!BattleVoicePvE.Cooldown.WillHaveOneCharge(10) && !RadiantFinalePvE.Cooldown.WillHaveOneCharge(10) && RagingStrikesPvE.Cooldown.IsCoolingDown) return true; + + if (RagingStrikesPvE.Cooldown.IsCoolingDown && !Player.HasStatus(true, StatusID.RagingStrikes)) return true; + } + + // Bloodletter Overcap protection + if (BloodletterPvE.Cooldown.WillHaveXCharges(BloodletterMax, 3f)) + { + if (RainOfDeathPvE.CanUse(out act, usedUp: true)) return true; + + if (HeartbreakShotPvE.CanUse(out act, usedUp: true)) return true; + + if (BloodletterPvE.CanUse(out act, usedUp: true)) return true; + } + + // Prevents Bloodletter bumpcapping when MAGE is the song due to Repetoire procs + if (BloodletterPvE.Cooldown.WillHaveXCharges(BloodletterMax, 7.5f) && Song == Song.MAGE) + { + if (RainOfDeathPvE.CanUse(out act, usedUp: true)) return true; + + if (HeartbreakShotPvE.CanUse(out act, usedUp: true)) return true; + + if (BloodletterPvE.CanUse(out act, usedUp: true)) return true; + } + + if (BetterBloodletterLogic(out act)) return true; + + return base.AttackAbility(nextGCD, out act); + } + #endregion + + #region GCD Logic + protected override bool GeneralGCD(out IAction? act) + { + if (IronJawsPvE.CanUse(out act)) return true; + if (IronJawsPvE.CanUse(out act, skipStatusProvideCheck: true) && (IronJawsPvE.Target.Target?.WillStatusEnd(30, true, IronJawsPvE.Setting.TargetStatusProvide ?? []) ?? false)) + { + if (Player.HasStatus(true, StatusID.BattleVoice) && Player.WillStatusEndGCD(1, 0, true, StatusID.BattleVoice)) return true; + } + + if (ResonantArrowPvE.CanUse(out act)) return true; + + if (CanUseApexArrow(out act)) return true; + if (RadiantEncorePvE.CanUse(out act, skipComboCheck: true)) + { + if (InBurstStatus) return true; + } + + if (BlastArrowPvE.CanUse(out act)) + { + if (!Player.HasStatus(true, StatusID.RagingStrikes)) return true; + if (Player.HasStatus(true, StatusID.RagingStrikes) && BarragePvE.Cooldown.IsCoolingDown) return true; + } + + //aoe + if (ShadowbitePvE.CanUse(out act)) return true; + if (WideVolleyPvE.CanUse(out act)) return true; + if (QuickNockPvE.CanUse(out act)) return true; + + if (IronJawsPvE.EnoughLevel && (HostileTarget?.HasStatus(true, StatusID.Windbite, StatusID.Stormbite) == true) && (HostileTarget?.HasStatus(true, StatusID.VenomousBite, StatusID.CausticBite) == true)) + { + // Do not use WindbitePvE or VenomousBitePvE if both statuses are present and IronJawsPvE has enough level + } + else + { + if (WindbitePvE.CanUse(out act)) return true; + if (VenomousBitePvE.CanUse(out act)) return true; + } + + + if (RefulgentArrowPvE.CanUse(out act, skipComboCheck: true)) return true; + if (StraightShotPvE.CanUse(out act)) return true; + if (HeavyShotPvE.CanUse(out act) && !Player.HasStatus(true, StatusID.HawksEye_3861)) return true; + + return base.GeneralGCD(out act); + } + #endregion + + #region Extra Methods + private bool CanUseApexArrow(out IAction act) + { + if (!ApexArrowPvE.CanUse(out act, skipAoeCheck: true)) return false; + + if (QuickNockPvE.CanUse(out _) && SoulVoice == 100) return true; + + if (SoulVoice == 100 && BattleVoicePvE.Cooldown.WillHaveOneCharge(25)) return false; + + if (SoulVoice >= 80 && Player.HasStatus(true, StatusID.RagingStrikes) && Player.WillStatusEnd(10, false, StatusID.RagingStrikes)) return true; + + if (SoulVoice == 100 && Player.HasStatus(true, StatusID.RagingStrikes) && Player.HasStatus(true, StatusID.BattleVoice)) return true; + + if (Song == Song.MAGE && SoulVoice >= 80 && SongEndAfter(22) && SongEndAfter(18)) return true; + + if (!Player.HasStatus(true, StatusID.RagingStrikes) && SoulVoice == 100) return true; + + return false; + } + private bool BetterBloodletterLogic(out IAction? act) + { + bool isRagingStrikesLevel = RagingStrikesPvE.EnoughLevel; + bool isBattleVoiceLevel = BattleVoicePvE.EnoughLevel; + bool isRadiantFinaleLevel = RadiantFinalePvE.EnoughLevel; + + if (HeartbreakShotPvE.CanUse(out act, usedUp: true)) + { + if ((!isRagingStrikesLevel) + || (isRagingStrikesLevel && !isBattleVoiceLevel && Player.HasStatus(true, StatusID.RagingStrikes)) + || (isBattleVoiceLevel && !isRadiantFinaleLevel && Player.HasStatus(true, StatusID.RagingStrikes) && Player.HasStatus(true, StatusID.BattleVoice)) + || isRadiantFinaleLevel && Player.HasStatus(true, StatusID.RagingStrikes) && Player.HasStatus(true, StatusID.BattleVoice) && Player.HasStatus(true, StatusID.RadiantFinale)) return true; + } + + if (RainOfDeathPvE.CanUse(out act, usedUp: true)) + { + if ((!isRagingStrikesLevel) + || (isRagingStrikesLevel && !isBattleVoiceLevel && Player.HasStatus(true, StatusID.RagingStrikes)) + || (isBattleVoiceLevel && !isRadiantFinaleLevel && Player.HasStatus(true, StatusID.RagingStrikes) && Player.HasStatus(true, StatusID.BattleVoice)) + || isRadiantFinaleLevel && Player.HasStatus(true, StatusID.RagingStrikes) && Player.HasStatus(true, StatusID.BattleVoice) && Player.HasStatus(true, StatusID.RadiantFinale)) return true; + } + + if (BloodletterPvE.CanUse(out act, usedUp: true)) + { + if ((!isRagingStrikesLevel) + || (isRagingStrikesLevel && !isBattleVoiceLevel && Player.HasStatus(true, StatusID.RagingStrikes)) + || (isBattleVoiceLevel && !isRadiantFinaleLevel && Player.HasStatus(true, StatusID.RagingStrikes) && Player.HasStatus(true, StatusID.BattleVoice)) + || isRadiantFinaleLevel && Player.HasStatus(true, StatusID.RagingStrikes) && Player.HasStatus(true, StatusID.BattleVoice) && Player.HasStatus(true, StatusID.RadiantFinale)) return true; + } + return false; + } + #endregion +} diff --git a/BasicRotations/Ranged/DNC_Default.cs b/BasicRotations/Ranged/DNC_Default.cs new file mode 100644 index 000000000..90bd9b146 --- /dev/null +++ b/BasicRotations/Ranged/DNC_Default.cs @@ -0,0 +1,323 @@ +namespace DefaultRotations.Ranged; + +[Rotation("Default", CombatType.PvE, GameVersion = "7.05", Description = "")] +[SourceCode(Path = "main/BasicRotations/Ranged/DNC_Default.cs")] +[Api(4)] +public sealed class DNC_Default : DancerRotation +{ + #region Config Options + [RotationConfig(CombatType.PvE, Name = "Holds Tech Step if no targets in range (Warning, will drift)")] + public bool HoldTechForTargets { get; set; } = true; + + [RotationConfig(CombatType.PvE, Name = "Holds Standard Step if no targets in range (Warning, will drift & Buff may fall off)")] + public bool HoldStepForTargets { get; set; } = false; + + [RotationConfig(CombatType.PvE, Name = "Dance Partner Name (If empty or not found uses default dance partner priority)")] + public string DancePartnerName { get; set; } = ""; + #endregion + bool shouldUseLastDance = true; + + #region Countdown Logic + // Override the method for actions to be taken during countdown phase of combat + protected override IAction? CountDownAction(float remainTime) + { + // If there are 15 or fewer seconds remaining in the countdown + if (remainTime <= 15) + { + // Attempt to use Standard Step if applicable + if (StandardStepPvE.CanUse(out var act, skipAoeCheck: true)) return act; + // Fallback to executing step GCD action if Standard Step is not used + if (ExecuteStepGCD(out act)) return act; + } + // If none of the above conditions are met, fallback to the base class method + return base.CountDownAction(remainTime); + } + #endregion + + #region oGCD Logic + // Override the method for handling emergency abilities + protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) + { + if (Player.HasStatus(true, StatusID.TechnicalFinish)) + { + if (DevilmentPvE.CanUse(out act)) return true; + } + + // Special handling if the last action was Quadruple Technical Finish and level requirement is met + if (IsLastGCD(ActionID.QuadrupleTechnicalFinishPvE) && TechnicalStepPvE.EnoughLevel) + { + // Attempt to use Devilment ignoring clipping checks + if (DevilmentPvE.CanUse(out act)) return true; + } + // Similar handling for Double Standard Finish when level requirement is not met + else if (IsLastGCD(ActionID.DoubleStandardFinishPvE) && !TechnicalStepPvE.EnoughLevel) + { + if (DevilmentPvE.CanUse(out act)) return true; + } + + // Use burst medicine if cooldown for Technical Step has elapsed sufficiently + if (TechnicalStepPvE.Cooldown.ElapsedAfter(115) + && UseBurstMedicine(out act)) return true; + + //If dancing or about to dance avoid using abilities to avoid animation lock delaying the dance, except for Devilment + if(!IsDancing && !(StandardStepPvE.Cooldown.ElapsedAfter(28) || TechnicalStepPvE.Cooldown.ElapsedAfter(118))) + return base.EmergencyAbility(nextGCD, out act); // Fallback to base class method if none of the above conditions are met + + act = null; + return false; + } + + [RotationDesc(ActionID.CuringWaltzPvE, ActionID.ImprovisationPvE)] + protected override bool HealAreaAbility(IAction nextGCD, out IAction act) + { + if (CuringWaltzPvE.CanUse(out act, usedUp: true)) return true; + if (ImprovisationPvE.CanUse(out act, usedUp: true)) return true; + return false; + } + + [RotationDesc(ActionID.ShieldSambaPvE)] + protected override bool DefenseAreaAbility(IAction nextGCD, out IAction act) + { + if (ShieldSambaPvE.CanUse(out act, usedUp: true)) return true; + return false; + } + + [RotationDesc(ActionID.EnAvantPvE)] + protected override bool MoveForwardAbility(IAction nextGCD, out IAction act) + { + if (EnAvantPvE.CanUse(out act, usedUp: true)) return true; + return false; + } + + // Override the method for handling attack abilities + protected override bool AttackAbility(IAction nextGCD, out IAction? act) + { + act = null; + + //If dancing or about to dance avoid using abilities to avoid animation lock delaying the dance + if (IsDancing || StandardStepPvE.Cooldown.ElapsedAfter(28) || TechnicalStepPvE.Cooldown.ElapsedAfter(118)) return false; + + // Prevent triple weaving by checking if an action was just used + if (nextGCD.AnimationLockTime > 0.75f) return false; + + // Skip using Flourish if Technical Step is about to come off cooldown + if (!TechnicalStepPvE.Cooldown.ElapsedAfter(116) || TillanaPvE.CanUse(out act)) + { + // Check for conditions to use Flourish + if (((Player.HasStatus(true, StatusID.Devilment)) && (Player.HasStatus(true, StatusID.TechnicalFinish))) || ((!Player.HasStatus(true, StatusID.Devilment)) && (!Player.HasStatus(true, StatusID.TechnicalFinish)))) + { + if (!Player.HasStatus(true, StatusID.ThreefoldFanDance) && FlourishPvE.CanUse(out act)) + { + return true; + } + } + } + + // Attempt to use Fan Dance III if available + if (FanDanceIiiPvE.CanUse(out act, skipAoeCheck: true)) return true; + + IAction[] FeathersGCDs = [ReverseCascadePvE, FountainfallPvE, RisingWindmillPvE, BloodshowerPvE]; + + //Use all feathers on burst or if about to overcap + if ((!DevilmentPvE.EnoughLevel || Player.HasStatus(true, StatusID.Devilment) || (Feathers > 3 && FeathersGCDs.Contains(nextGCD))) && !Player.HasStatus(true, StatusID.ThreefoldFanDance)) + { + if (FanDanceIiPvE.CanUse(out act)) return true; + if (FanDancePvE.CanUse(out act)) return true; + } + + // Other attacks + if (FanDanceIvPvE.CanUse(out act, skipAoeCheck: true)) return true; + if (UseClosedPosition(out act)) return true; + + return base.AttackAbility(nextGCD, out act); + } + #endregion + + #region GCD Logic + // Override the method for handling general Global Cooldown (GCD) actions + protected override bool GeneralGCD(out IAction? act) + { + // Attempt to use Closed Position if applicable + if (!InCombat && !Player.HasStatus(true, StatusID.ClosedPosition) && ClosedPositionPvE.CanUse(out act)) + { + + if (DancePartnerName != "") + foreach (var player in PartyMembers) + if (player.Name.ToString() == DancePartnerName) + ClosedPositionPvE.Target = new TargetResult(player, [player], player.Position); + + return true; + } + + // Try to finish the dance if applicable + if (FinishTheDance(out act)) + { + return true; + } + + // Execute a Step GCD if available + if (ExecuteStepGCD(out act)) + { + return true; + } + + // Use Technical Step in burst mode if applicable + if (HoldTechForTargets) + { + if (HasHostilesInMaxRange && IsBurst && InCombat && TechnicalStepPvE.CanUse(out act, skipAoeCheck: true)) + + { + return true; + } + } + else + { + if (IsBurst && InCombat && TechnicalStepPvE.CanUse(out act, skipAoeCheck: true)) + { + return true; + } + } + + // Attempt to use a general attack GCD if none of the above conditions are met + if (AttackGCD(out act, Player.HasStatus(true, StatusID.Devilment))) + { + return true; + } + + // Fallback to the base method if no custom GCD actions are found + return base.GeneralGCD(out act); + } + #endregion + + #region Extra Methods + // Helper method to handle attack actions during GCD based on certain conditions + private bool AttackGCD(out IAction? act, bool burst) + { + act = null; + + if (IsDancing) return false; + + if (!DevilmentPvE.CanUse(out _, skipComboCheck: true)) + { + if (TillanaPvE.CanUse(out act, skipAoeCheck: true)) return true; + } + + if (TechnicalStepPvE.Cooldown.ElapsedAfter(103)) + { + shouldUseLastDance = false; + } + + if (TechnicalStepPvE.Cooldown.ElapsedAfter(1) && !TechnicalStepPvE.Cooldown.ElapsedAfter(103)) + { + shouldUseLastDance = true; + } + + if (burst) + { + // Make sure Starfall gets used before end of burst + if (DevilmentPvE.Cooldown.ElapsedAfter(15) && StarfallDancePvE.CanUse(out act, skipAoeCheck: true)) return true; + + // Make sure to FM with enough time left in burst window to LD and SFD while leaving a GCD for a Sabre if needed + if (DevilmentPvE.Cooldown.ElapsedAfter(10) && FinishingMovePvE.CanUse(out act, skipAoeCheck: true)) return true; + } + + if (shouldUseLastDance) + { + if (LastDancePvE.CanUse(out act, skipAoeCheck: true)) return true; + } + + if (HoldStepForTargets) + { + if (HasHostilesInMaxRange && UseStandardStep(out act)) return true; + } + else + { + if (UseStandardStep(out act)) return true; + } + + if (FinishingMovePvE.CanUse(out act, skipAoeCheck: true)) return true; + + // Further prioritized GCD abilities + if ((burst || (Esprit >= 85 && !TechnicalStepPvE.Cooldown.ElapsedAfter(115))) && SaberDancePvE.CanUse(out act, skipAoeCheck: true)) return true; + + if (StarfallDancePvE.CanUse(out act, skipAoeCheck: true)) return true; + + bool standardReady = StandardStepPvE.Cooldown.ElapsedAfter(28); + bool technicalReady = TechnicalStepPvE.Cooldown.ElapsedAfter(118); + + if (!(standardReady || technicalReady) && + (!shouldUseLastDance || !LastDancePvE.CanUse(out act, skipAoeCheck: true))) + { + if (BloodshowerPvE.CanUse(out act)) return true; + if (FountainfallPvE.CanUse(out act)) return true; + if (RisingWindmillPvE.CanUse(out act)) return true; + if (ReverseCascadePvE.CanUse(out act)) return true; + if (BladeshowerPvE.CanUse(out act)) return true; + if (WindmillPvE.CanUse(out act)) return true; + if (FountainPvE.CanUse(out act)) return true; + if (CascadePvE.CanUse(out act)) return true; + } + + return false; + } + // Method for Standard Step Logic + private bool UseStandardStep(out IAction act) + { + // Attempt to use Standard Step if available and certain conditions are met + if (!StandardStepPvE.CanUse(out act, skipAoeCheck: true)) return false; + if (Player.WillStatusEnd(5f, true, StatusID.StandardFinish)) return true; + + // Check for hostiles in range and technical step conditions + if (!HasHostilesInRange) return false; + if (Player.HasStatus(true, StatusID.TechnicalFinish) && Player.WillStatusEndGCD(2, 0, true, StatusID.TechnicalFinish) || (TechnicalStepPvE.Cooldown.IsCoolingDown && TechnicalStepPvE.Cooldown.WillHaveOneCharge(5))) return false; + + return true; + } + + // Helper method to decide usage of Closed Position based on specific conditions + private bool UseClosedPosition(out IAction act) + { + // Attempt to use Closed Position if available and certain conditions are met + if (!ClosedPositionPvE.CanUse(out act)) return false; + + if (InCombat && Player.HasStatus(true, StatusID.ClosedPosition)) + { + // Check for party members with Closed Position status + foreach (var friend in PartyMembers) + { + if (friend.HasStatus(true, StatusID.ClosedPosition_2026)) + { + // Use Closed Position if target is not the same as the friend with the status + if (ClosedPositionPvE.Target.Target != friend) return true; + break; + } + } + } + return false; + } + // Rewrite of method to hold dance finish until target is in range 14 yalms + private bool FinishTheDance(out IAction? act) + { + bool areDanceTargetsInRange = AllHostileTargets.Any(hostile => hostile.DistanceToPlayer() < 14); + + // Check for Standard Step if targets are in range or status is about to end. + if (Player.HasStatus(true, StatusID.StandardStep) && CompletedSteps == 2 && + (areDanceTargetsInRange || Player.WillStatusEnd(1f, true, StatusID.StandardStep)) && + DoubleStandardFinishPvE.CanUse(out act, skipAoeCheck: true)) + { + return true; + } + + // Check for Technical Step if targets are in range or status is about to end. + if (Player.HasStatus(true, StatusID.TechnicalStep) && CompletedSteps == 4 && + (areDanceTargetsInRange || Player.WillStatusEnd(1f, true, StatusID.TechnicalStep)) && + QuadrupleTechnicalFinishPvE.CanUse(out act, skipAoeCheck: true)) + { + return true; + } + + act = null; + return false; + } + #endregion +} diff --git a/BasicRotations/Ranged/MCH_Default.cs b/BasicRotations/Ranged/MCH_Default.cs new file mode 100644 index 000000000..0989b6e13 --- /dev/null +++ b/BasicRotations/Ranged/MCH_Default.cs @@ -0,0 +1,228 @@ +namespace DefaultRotations.Ranged; + +[Rotation("Default", CombatType.PvE, GameVersion = "7.05")] +[SourceCode(Path = "main/BasicRotations/Ranged/MCH_Default.cs")] +[Api(4)] +public sealed class MCH_Default : MachinistRotation +{ + #region Config Options + [RotationConfig(CombatType.PvE, Name = "(Warning: Queen logic is new and untested, uncheck to test new logic) Skip Queen Logic and uses Rook Autoturret/Automaton Queen immediately whenever you get 50 battery")] + private bool SkipQueenLogic { get; set; } = true; + + [RotationConfig(CombatType.PvE, Name = "Prioritize Barrel Stabilizer use")] + private bool BSPrio { get; set; } = false; + #endregion + + #region Countdown logic + // Defines logic for actions to take during the countdown before combat starts. + protected override IAction? CountDownAction(float remainTime) + { + // ReassemblePvE's duration is 5s, need to fire the first GCD before it ends + if (remainTime < 5 && ReassemblePvE.CanUse(out var act)) return act; + // tincture needs to be used on -2s exactly + if (remainTime <= 2 && UseBurstMedicine(out act)) return act; + return base.CountDownAction(remainTime); + } + #endregion + + #region oGCD Logic + // Determines emergency actions to take based on the next planned GCD action. + protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) + { + // Reassemble Logic + // Check next GCD action and conditions for Reassemble. + bool isReassembleUsable = + //Reassemble current # of charges and double proc protection + ReassemblePvE.Cooldown.CurrentCharges > 0 && !Player.HasStatus(true, StatusID.Reassembled) && + (nextGCD.IsTheSameTo(true, [ChainSawPvE, ExcavatorPvE]) || nextGCD.IsTheSameTo(false, [AirAnchorPvE]) || + (!ChainSawPvE.EnoughLevel && nextGCD.IsTheSameTo(true, DrillPvE)) || + (!DrillPvE.EnoughLevel && nextGCD.IsTheSameTo(true, CleanShotPvE)) || + (!CleanShotPvE.EnoughLevel && nextGCD.IsTheSameTo(false, HotShotPvE))); + + // Keeps Ricochet and Gauss cannon Even + bool isRicochetMore = RicochetPvE.EnoughLevel && GaussRoundPvE.Cooldown.CurrentCharges <= RicochetPvE.Cooldown.CurrentCharges; + bool isGaussMore = !RicochetPvE.EnoughLevel || GaussRoundPvE.Cooldown.CurrentCharges > RicochetPvE.Cooldown.CurrentCharges; + + // Attempt to use Reassemble if it's ready + if (isReassembleUsable) + { + if (ReassemblePvE.CanUse(out act, skipComboCheck: true, usedUp: true)) return true; + } + + // Use Ricochet + if (isRicochetMore && ((!IsLastAction(true, GaussRoundPvE, RicochetPvE) && IsLastGCD(true, HeatBlastPvE, AutoCrossbowPvE)) || !IsLastGCD(true, HeatBlastPvE, AutoCrossbowPvE))) + { + if (RicochetPvE.CanUse(out act, skipAoeCheck: true, usedUp: true)) + return true; + } + + // Use Gauss + if (isGaussMore && ((!IsLastAction(true, GaussRoundPvE, RicochetPvE) && IsLastGCD(true, HeatBlastPvE, AutoCrossbowPvE)) || !IsLastGCD(true, HeatBlastPvE, AutoCrossbowPvE))) + { + if (GaussRoundPvE.CanUse(out act, usedUp: true)) + return true; + } + return base.EmergencyAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.TacticianPvE, ActionID.DismantlePvE)] + protected override bool DefenseAreaAbility(IAction nextGCD, out IAction act) + { + if (TacticianPvE.CanUse(out act, skipAoeCheck: true)) return true; + if (DismantlePvE.CanUse(out act, skipAoeCheck: true)) return true; + return false; + } + + // Logic for using attack abilities outside of GCD, focusing on burst windows and cooldown management. + protected override bool AttackAbility(IAction nextGCD, out IAction? act) + { + if (BSPrio && IsBurst) + { + if (BarrelStabilizerPvE.CanUse(out act)) return true; + } + + // Check for not burning Hypercharge below level 52 on AOE + bool LowLevelHyperCheck = !AutoCrossbowPvE.EnoughLevel && SpreadShotPvE.CanUse(out _); + + // If Wildfire is active, use Hypercharge.....Period + if (Player.HasStatus(true, StatusID.Wildfire_1946)) + { + return HyperchargePvE.CanUse(out act); + } + // Burst + if (IsBurst) + { + { + if ((IsLastAbility(false, HyperchargePvE) || Heat >= 50 || Player.HasStatus(true, StatusID.Hypercharged)) && !CombatElapsedLessGCD(5) && + (CombatElapsedLess(20) || ToolChargeSoon(out _)) && !LowLevelHyperCheck && WildfirePvE.CanUse(out act)) return true; + } + } + // Use Hypercharge if at least 12 seconds of combat and (if wildfire will not be up in 30 seconds or if you hit 100 heat) + if (!LowLevelHyperCheck && !CombatElapsedLess(12) && !Player.HasStatus(true, StatusID.Reassembled) && (!WildfirePvE.Cooldown.WillHaveOneCharge(30) || (Heat == 100))) + { + if (ToolChargeSoon(out act)) return true; + } + // Rook Autoturret/Queen Logic + if (!IsLastGCD(true, HeatBlastPvE, BlazingShotPvE) && CanUseQueenMeow(out act)) return true; + if (nextGCD.IsTheSameTo(true, CleanShotPvE, AirAnchorPvE, ChainSawPvE, ExcavatorPvE) && Battery == 100) + { + if (RookAutoturretPvE.CanUse(out act)) return true; + } + + if (IsBurst) + { + if (BarrelStabilizerPvE.CanUse(out act)) return true; + } + + return base.AttackAbility(nextGCD, out act); + } + #endregion + + #region GCD Logic + protected override bool GeneralGCD(out IAction? act) + { + // overheated aoe + if (AutoCrossbowPvE.CanUse(out act)) return true; + // overheated single + if (HeatBlastPvE.CanUse(out act)) return true; + + // drill's aoe version + if (BioblasterPvE.CanUse(out act, usedUp: true)) return true; + + // single target --- need to update this strange condition writing!!! + if (!SpreadShotPvE.CanUse(out _)) + { + // use AirAnchor if possible + if (HotShotMasteryTrait.EnoughLevel && AirAnchorPvE.CanUse(out act)) return true; + + // for opener: only use the first charge of Drill after AirAnchor when there are two + if (EnhancedMultiweaponTrait.EnoughLevel && DrillPvE.CanUse(out act, usedUp: false)) return true; + if (!EnhancedMultiweaponTrait.EnoughLevel && DrillPvE.CanUse(out act, usedUp: true)) return true; + + if (!AirAnchorPvE.EnoughLevel && HotShotPvE.CanUse(out act)) return true; + } + + // ChainSaw is always used after Drill + if (ChainSawPvE.CanUse(out act, skipAoeCheck: true)) return true; + // use combo finisher asap + if (ExcavatorPvE.CanUse(out act, skipAoeCheck: true)) return true; + // use FMF after ChainSaw combo in 'alternative opener' + if (FullMetalFieldPvE.CanUse(out act)) return true; + + // dont use the second charge of Drill if it's in opener, also save Drill for burst --- need to combine this with the logic above!!! + if (EnhancedMultiweaponTrait.EnoughLevel && !CombatElapsedLessGCD(6) && !ChainSawPvE.Cooldown.WillHaveOneCharge(6) && DrillPvE.CanUse(out act, usedUp: true)) return true; + + + // basic aoe + if (SpreadShotPvE.CanUse(out act)) return true; + + // single target 123 combo + if (CleanShotPvE.CanUse(out act)) return true; + if (SlugShotPvE.CanUse(out act)) return true; + if (SplitShotPvE.CanUse(out act)) return true; + + return base.GeneralGCD(out act); + } + #endregion + + #region Extra Methods + // Extra private helper methods for determining the usability of specific abilities under certain conditions. + // These methods simplify the main logic by encapsulating specific checks related to abilities' cooldowns and prerequisites. + // Logic for Hypercharge + private bool ToolChargeSoon(out IAction? act) + { + float REST_TIME = 8f; + if + //Cannot AOE + (!SpreadShotPvE.CanUse(out _) + && + // AirAnchor Enough Level % AirAnchor + ((AirAnchorPvE.EnoughLevel && AirAnchorPvE.Cooldown.WillHaveOneCharge(REST_TIME)) + || + // HotShot Charge Detection + (!AirAnchorPvE.EnoughLevel && HotShotPvE.EnoughLevel && HotShotPvE.Cooldown.WillHaveOneCharge(REST_TIME)) + || + // Drill Charge Detection + (DrillPvE.EnoughLevel && DrillPvE.Cooldown.WillHaveOneCharge(REST_TIME)) + || + // Chainsaw Charge Detection + (ChainSawPvE.EnoughLevel && ChainSawPvE.Cooldown.WillHaveOneCharge(REST_TIME)))) + { + act = null; + return false; + } + else + { + return HyperchargePvE.CanUse(out act); + } + } + + private bool CanUseQueenMeow(out IAction? act) + { + // Define conditions under which the Rook Autoturret/Queen can be used. + bool NoQueenLogic = SkipQueenLogic; + bool QueenOne = Battery >= 60 && CombatElapsedLess(25f); + bool QueenTwo = Battery >= 90 && !CombatElapsedLess(58f) && CombatElapsedLess(78f); + bool QueenThree = Battery >= 100 && !CombatElapsedLess(111f) && CombatElapsedLess(131f); + bool QueenFour = Battery >= 50 && !CombatElapsedLess(148f) && CombatElapsedLess(168f); + bool QueenFive = Battery >= 60 && !CombatElapsedLess(178f) && CombatElapsedLess(198f); + bool QueenSix = Battery >= 100 && !CombatElapsedLess(230f) && CombatElapsedLess(250f); + bool QueenSeven = Battery >= 50 && !CombatElapsedLess(268f) && CombatElapsedLess(288f); + bool QueenEight = Battery >= 70 && !CombatElapsedLess(296f) && CombatElapsedLess(316f); + bool QueenNine = Battery >= 100 && !CombatElapsedLess(350f) && CombatElapsedLess(370f); + bool QueenTen = Battery >= 50 && !CombatElapsedLess(388f) && CombatElapsedLess(408f); + bool QueenEleven = Battery >= 80 && !CombatElapsedLess(416f) && CombatElapsedLess(436f); + bool QueenTwelve = Battery >= 100 && !CombatElapsedLess(470f) && CombatElapsedLess(490f); + bool QueenThirteen = Battery >= 50 && !CombatElapsedLess(505f) && CombatElapsedLess(525f); + bool QueenFourteen = Battery >= 60 && !CombatElapsedLess(538f) && CombatElapsedLess(558f); + bool QueenFifteen = Battery >= 100 && !CombatElapsedLess(590f) && CombatElapsedLess(610f); + + if (NoQueenLogic || QueenOne || QueenTwo || QueenThree || QueenFour || QueenFive || QueenSix || QueenSeven || QueenEight || QueenNine || QueenTen || QueenEleven || QueenTwelve || QueenThirteen || QueenFourteen || QueenFifteen) + { + if (RookAutoturretPvE.CanUse(out act)) return true; + } + act = null; + return false; + } + #endregion +} \ No newline at end of file diff --git a/BasicRotations/Ranged/Queen Timings b/BasicRotations/Ranged/Queen Timings new file mode 100644 index 000000000..4572892ed --- /dev/null +++ b/BasicRotations/Ranged/Queen Timings @@ -0,0 +1,30 @@ +00:07:50 Opener Queen : 60 Battery +01:08:25 2nd Queen: 90 Battery (Hold Hypercharge, continue with main combo, delay Excavator for Heated Clean Shot to get our 90 Queen. Another way to achieve this is to Hypercharge as soon as we hit 50 Heat post Opener to skip the Heated Clean Shot, use Queen after Excavator.) +02:01:50 3rd Queen (2 Minute Burst): 100 Battery (After Air Anchor) +02:38:25 4th Queen: 50 Battery +03:08:25 5th Queen: 60 Battery (Delaying Excavator a GCD to get the Heated Clean Shot for the 60) +04:00:75 6th Queen (4 Minute Burst): 100 Battery (After Air Anchor) +04:38:25 7th Queen: 50 Battery +05:06:50 8th Queen: 70 Battery (After Chainsaw) +06:00:75 9th Queen (6 Minute Burst): 100 Battery +06:38:25 10th Queen: 50 Battery +07:06:50 11th Queen: 80 Battery (Delaying Excavator until the next Heated Clean Shot for the 80) +08:00:75 12th Queen (8 Minute Burst): 100 Battery +08:35:75 13th Queen: 50 Battery +09:08:25 14th Queen: 60 Battery (Delaying Excavator a GCD to get the Heated Clean Shot for the 60) +10:00:75 15th Queen (10 Minute Burst): 100 Battery +7.050 seconds +68.025 seconds +121.050 seconds +158.025 seconds +188.025 seconds +240.075 seconds +278.025 seconds +306.050 seconds +360.075 seconds +398.025 seconds +426.050 seconds +480.075 seconds +515.075 seconds +548.025 seconds +600.075 seconds diff --git a/BasicRotations/Ranged/zMCH_Beta.cs b/BasicRotations/Ranged/zMCH_Beta.cs new file mode 100644 index 000000000..0cf6d9ac4 --- /dev/null +++ b/BasicRotations/Ranged/zMCH_Beta.cs @@ -0,0 +1,223 @@ +namespace DefaultRotations.Ranged; + +[Rotation("zMCH Beta", CombatType.PvE, GameVersion = "7.05")] +[SourceCode(Path = "main/BasicRotations/Ranged/zMCH_Beta.cs")] +[Api(4)] +public sealed class zMCH_Beta : MachinistRotation +{ + #region Config Options + [RotationConfig(CombatType.PvE, Name = "Prioritize Barrel Stabilizer use")] + private bool BSPrio { get; set; } = true; + + [RotationConfig(CombatType.PvE, Name = "Delay Drill for combo GCD if have one charge and about to break combo")] + private bool HoldDrillForCombo { get; set; } = true; + + [RotationConfig(CombatType.PvE, Name = "Delay Hypercharge for combo GCD if about to break combo")] + private bool HoldHCForCombo { get; set; } = true; + + [RotationConfig(CombatType.PvE, Name = "Use burst medicine in countdown (requires auto burst option on)")] + private bool OpenerBurstMeds { get; set; } = false; + + [RotationConfig(CombatType.PvE, Name = "Use burst medicine when available for midfight burst phase (requires auto burst option on)")] + private bool MidfightBurstMeds { get; set; } = false; + + [RotationConfig(CombatType.PvE, Name = "Prevent the use of defense abilties during hypercharge burst")] + private bool BurstDefense { get; set; } = false; + #endregion + + #region Countdown logic + // Defines logic for actions to take during the countdown before combat starts. + protected override IAction? CountDownAction(float remainTime) + { + // ReassemblePvE's duration is 5s, need to fire the first GCD before it ends + if (remainTime < 5 && ReassemblePvE.CanUse(out var act)) return act; + if (IsBurst && OpenerBurstMeds && remainTime <= 1f && UseBurstMedicine(out act)) return act; + return base.CountDownAction(remainTime); + } + #endregion + + #region oGCD Logic + // Determines emergency actions to take based on the next planned GCD action. + protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) + { + if (IsBurst && MidfightBurstMeds && !CombatElapsedLessGCD(10) && TimeForBurstMeds(out act, nextGCD)) return true; + + // Reassemble Logic + // Check next GCD action and conditions for Reassemble. + bool isReassembleUsable = + //Reassemble current # of charges and double proc protection + ReassemblePvE.Cooldown.CurrentCharges > 0 && !Player.HasStatus(true, StatusID.Reassembled) && + (nextGCD.IsTheSameTo(true, [ChainSawPvE, ExcavatorPvE]) + || (!ChainSawPvE.EnoughLevel && nextGCD.IsTheSameTo(true, SpreadShotPvE) && ((IBaseAction)nextGCD).Target.AffectedTargets.Length >= (SpreadShotMasteryTrait.EnoughLevel ? 4 : 5)) + || nextGCD.IsTheSameTo(false, [AirAnchorPvE]) + || (!ChainSawPvE.EnoughLevel && nextGCD.IsTheSameTo(true, DrillPvE)) + || (!DrillPvE.EnoughLevel && nextGCD.IsTheSameTo(true, CleanShotPvE)) + || (!CleanShotPvE.EnoughLevel && nextGCD.IsTheSameTo(false, HotShotPvE))); + // Attempt to use Reassemble if it's ready + if (isReassembleUsable) + { + if (ReassemblePvE.CanUse(out act, skipComboCheck: true, usedUp: true)) return true; + } + return base.EmergencyAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.TacticianPvE, ActionID.DismantlePvE)] + protected override bool DefenseAreaAbility(IAction nextGCD, out IAction? act) + { + if ((!BurstDefense || (BurstDefense && !IsOverheated)) && TacticianPvE.CanUse(out act, skipAoeCheck: true)) return true; + if ((!BurstDefense || (BurstDefense && !IsOverheated)) && DismantlePvE.CanUse(out act, skipAoeCheck: true)) return true; + + return base.DefenseAreaAbility(nextGCD, out act); + } + + // Logic for using attack abilities outside of GCD, focusing on burst windows and cooldown management. + protected override bool AttackAbility(IAction nextGCD, out IAction? act) + { + // Keeps Ricochet and Gauss cannon Even + bool isRicochetMore = RicochetPvE.EnoughLevel && GaussRoundPvE.Cooldown.RecastTimeElapsed <= RicochetPvE.Cooldown.RecastTimeElapsed; + + // If Wildfire is active, use Hypercharge.....Period + if (Player.HasStatus(true, StatusID.Wildfire_1946) && HyperchargePvE.CanUse(out act)) return true; + + // Start Ricochet/Gauss cooldowns rolling + if (!RicochetPvE.Cooldown.IsCoolingDown && RicochetPvE.CanUse(out act, skipAoeCheck: true)) return true; + if (!GaussRoundPvE.Cooldown.IsCoolingDown && GaussRoundPvE.CanUse(out act, skipAoeCheck: true)) return true; + + // Check for not burning Hypercharge below level 52 on AOE + bool LowLevelHyperCheck = !AutoCrossbowPvE.EnoughLevel && SpreadShotPvE.CanUse(out _); + + if (IsBurst && BSPrio && BarrelStabilizerPvE.CanUse(out act)) return true; + + // Burst + if (IsBurst) + { + if (WildfirePvE.Cooldown.WillHaveOneChargeGCD(1) && (IsLastAbility(false, HyperchargePvE) || Heat >= 50 || Player.HasStatus(true, StatusID.Hypercharged)) && ToolChargeSoon(out _) && !LowLevelHyperCheck) + { + if (WeaponRemain < 1.25f && WildfirePvE.CanUse(out act)) return true; + act = null; + return false; + } + + } + // Use Hypercharge if wildfire will not be up in 30 seconds or if you hit 100 heat + if (!LowLevelHyperCheck && !Player.HasStatus(true, StatusID.Reassembled) && (!WildfirePvE.Cooldown.WillHaveOneCharge(30) || (Heat == 100))) + { + if ((!HoldHCForCombo || !(LiveComboTime <= 8f && LiveComboTime > 0f)) && ToolChargeSoon(out act)) return true; + } + + // Rook Autoturret/Queen Logic + if (CanUseQueenMeow(out act, nextGCD)) return true; + + // Use Ricochet and Gauss + if (isRicochetMore && RicochetPvE.CanUse(out act, skipAoeCheck: true, usedUp: true)) return true; + if (GaussRoundPvE.CanUse(out act, usedUp: true, skipAoeCheck: true)) return true; + + if (IsBurst && BarrelStabilizerPvE.CanUse(out act)) return true; + + return base.AttackAbility(nextGCD, out act); + } + #endregion + + #region GCD Logic + protected override bool GeneralGCD(out IAction? act) + { + // overheated aoe + if (AutoCrossbowPvE.CanUse(out act)) return true; + // overheated single + if (HeatBlastPvE.CanUse(out act)) return true; + + // drill's aoe version + if (BioblasterPvE.CanUse(out act, usedUp: true)) return true; + + // single target --- need to update this strange condition writing!!! + if (!SpreadShotPvE.CanUse(out _)) + { + // use AirAnchor if possible + if (HotShotMasteryTrait.EnoughLevel && AirAnchorPvE.CanUse(out act)) return true; + + // for opener: only use the first charge of Drill after AirAnchor when there are two + if (EnhancedMultiweaponTrait.EnoughLevel && DrillPvE.CanUse(out act, usedUp: false)) return true; + if (!EnhancedMultiweaponTrait.EnoughLevel && DrillPvE.CanUse(out act, usedUp: true)) return true; + + if (!AirAnchorPvE.EnoughLevel && HotShotPvE.CanUse(out act)) return true; + } + + // ChainSaw is always used after Drill + if (ChainSawPvE.CanUse(out act, skipAoeCheck: true)) return true; + // use combo finisher asap + if (ExcavatorPvE.CanUse(out act, skipAoeCheck: true)) return true; + // use FMF after ChainSaw combo in 'alternative opener' + if (FullMetalFieldPvE.CanUse(out act)) return true; + + // dont use the second charge of Drill if it's in opener, also save Drill for burst --- need to combine this with the logic above!!! + if (EnhancedMultiweaponTrait.EnoughLevel + && !CombatElapsedLessGCD(6) + && !ChainSawPvE.Cooldown.WillHaveOneCharge(6) + && (!HoldDrillForCombo || !(LiveComboTime <= 5) || (!CleanShotPvE.CanUse(out _) && !SlugShotPvE.CanUse(out _))) + && DrillPvE.CanUse(out act, usedUp: true)) return true; + + // basic aoe + if (SpreadShotPvE.CanUse(out act)) return true; + + // single target 123 combo + if (CleanShotPvE.CanUse(out act)) return true; + if (SlugShotPvE.CanUse(out act)) return true; + if (SplitShotPvE.CanUse(out act)) return true; + + return base.GeneralGCD(out act); + } + #endregion + + #region Extra Methods + // Extra private helper methods for determining the usability of specific abilities under certain conditions. + // These methods simplify the main logic by encapsulating specific checks related to abilities' cooldowns and prerequisites. + // Logic for Hypercharge + private bool ToolChargeSoon(out IAction? act) + { + float REST_TIME = 8f; + if + //Cannot AOE + (!SpreadShotPvE.CanUse(out _) + && + // AirAnchor Enough Level % AirAnchor + ((AirAnchorPvE.EnoughLevel && AirAnchorPvE.Cooldown.WillHaveOneCharge(REST_TIME)) + || + // HotShot Charge Detection + (!AirAnchorPvE.EnoughLevel && HotShotPvE.EnoughLevel && HotShotPvE.Cooldown.WillHaveOneCharge(REST_TIME)) + || + // Drill Charge Detection + (DrillPvE.EnoughLevel && DrillPvE.Cooldown.WillHaveXCharges(DrillPvE.Cooldown.MaxCharges, REST_TIME)) + || + // Chainsaw Charge Detection + (ChainSawPvE.EnoughLevel && ChainSawPvE.Cooldown.WillHaveOneCharge(REST_TIME)))) + { + act = null; + return false; + } + else + { + return HyperchargePvE.CanUse(out act); + } + } + + private bool TimeForBurstMeds(out IAction? act, IAction nextGCD) + { + if (AirAnchorPvE.Cooldown.WillHaveOneChargeGCD(1) && BarrelStabilizerPvE.Cooldown.WillHaveOneChargeGCD(6) && WildfirePvE.Cooldown.WillHaveOneChargeGCD(6)) return UseBurstMedicine(out act); + act = null; + return false; + } + + private bool CanUseQueenMeow(out IAction? act, IAction nextGCD) + { + if (WildfirePvE.Cooldown.WillHaveOneChargeGCD(4) + || !WildfirePvE.Cooldown.ElapsedAfter(10) + || (nextGCD.IsTheSameTo(true, CleanShotPvE) && Battery == 100) + || (nextGCD.IsTheSameTo(true, HotShotPvE, AirAnchorPvE, ChainSawPvE, ExcavatorPvE) && (Battery == 90 || Battery == 100))) + { + if (RookAutoturretPvE.CanUse(out act)) return true; + } + act = null; + return false; + } + #endregion +} \ No newline at end of file diff --git a/BasicRotations/Ranged/zMCH_Beta_2.cs b/BasicRotations/Ranged/zMCH_Beta_2.cs new file mode 100644 index 000000000..c36fde7a3 --- /dev/null +++ b/BasicRotations/Ranged/zMCH_Beta_2.cs @@ -0,0 +1,269 @@ +namespace DefaultRotations.Ranged; + +[Rotation("zMCH Beta 2", CombatType.PvE, GameVersion = "7.05")] +[SourceCode(Path = "main/BasicRotations/Ranged/zMCH_Beta_2.cs")] +[Api(4)] +public sealed class zMCH_Beta_2 : MachinistRotation +{ + #region Config Options + [RotationConfig(CombatType.PvE, Name = "Use hardcoded Queen timings\nSlight DPS gain if uninterrupted but possibly loses more from drift or death.")] + private bool UseBalanceQueenTimings { get; set; } + + [RotationConfig(CombatType.PvE, Name = "Use burst medicine in countdown")] + private bool OpenerBurstMeds { get; set; } = false; + + [RotationConfig(CombatType.PvE, Name = "Use burst medicine when available for midfight burst phase")] + private bool MidfightBurstMeds { get; set; } = false; + #endregion + + private const float HYPERCHARGE_DURATION = 8f; + + #region Countdown logic + // Defines logic for actions to take during the countdown before combat starts. + protected override IAction? CountDownAction(float remainTime) + { + // ReassemblePvE's duration is 5s, need to fire the first GCD before it ends + if (remainTime < 5 && ReassemblePvE.CanUse(out var act)) return act; + // tincture needs to be used on -2s exactly + if (OpenerBurstMeds && remainTime <= 2 && UseBurstMedicine(out act)) return act; + return base.CountDownAction(remainTime); + } + #endregion + + #region oGCD Logic + // Determines emergency actions to take based on the next planned GCD action. + protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) + { + if (IsBurst && MidfightBurstMeds && !CombatElapsedLessGCD(10) && TimeForBurstMeds(out act, nextGCD)) return true; + if (IsBurst) + { + + if (FullMetalFieldPvE.EnoughLevel) + { + // Use Wildfire before FMF in the second half of the GCD window to avoid wasting time in status + if (WeaponRemain < 1.25f && nextGCD.IsTheSameTo(true, FullMetalFieldPvE) + && Player.HasStatus(true, StatusID.Hypercharged) + && WildfirePvE.CanUse(out act, isLastAbility: true)) return true; + } + // Legacy logic for <100 + else if ((IsLastAbility(false, HyperchargePvE) + || Heat >= 50 + || Player.HasStatus(true, StatusID.Hypercharged)) + && ToolChargeSoon(out _) + && !LowLevelHyperCheck + && WildfirePvE.CanUse(out act)) return true; + } + + // Reassemble Logic + // Check next GCD action and conditions for Reassemble. + bool isReassembleUsable = + //Reassemble current # of charges and double proc protection + ReassemblePvE.Cooldown.CurrentCharges > 0 && !Player.HasStatus(true, StatusID.Reassembled) && + (nextGCD.IsTheSameTo(true, [ChainSawPvE, ExcavatorPvE]) || nextGCD.IsTheSameTo(false, [AirAnchorPvE]) || + (!ChainSawPvE.EnoughLevel && nextGCD.IsTheSameTo(true, DrillPvE)) || + (!DrillPvE.EnoughLevel && nextGCD.IsTheSameTo(true, CleanShotPvE)) || + (!CleanShotPvE.EnoughLevel && nextGCD.IsTheSameTo(false, HotShotPvE))); + // Attempt to use Reassemble if it's ready + if (isReassembleUsable) + { + if (ReassemblePvE.CanUse(out act, skipComboCheck: true, usedUp: true)) return true; + } + return base.EmergencyAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.TacticianPvE, ActionID.DismantlePvE)] + protected override bool DefenseAreaAbility(IAction nextGCD, out IAction act) + { + if (TacticianPvE.CanUse(out act, skipAoeCheck: true)) return true; + if (DismantlePvE.CanUse(out act, skipAoeCheck: true)) return true; + return false; + } + + // Logic for using attack abilities outside of GCD, focusing on burst windows and cooldown management. + protected override bool AttackAbility(IAction nextGCD, out IAction? act) + { + // If Wildfire is active, use Hypercharge.....Period + if (Player.HasStatus(true, StatusID.Wildfire_1946) && HyperchargePvE.CanUse(out act)) return true; + + // don't do anything that might fuck with burst timings at 100 + if (nextGCD.IsTheSameTo(true, FullMetalFieldPvE) || IsLastGCD(true, FullMetalFieldPvE)) + { + act = null; + return false; + } + + // Start Ricochet/Gauss cooldowns rolling + if (!RicochetPvE.Cooldown.IsCoolingDown && RicochetPvE.CanUse(out act, skipAoeCheck: true)) return true; + if (!GaussRoundPvE.Cooldown.IsCoolingDown && GaussRoundPvE.CanUse(out act, skipAoeCheck: true)) return true; + + if (IsBurst && IsLastGCD(true, DrillPvE) && BarrelStabilizerPvE.CanUse(out act)) return true; + + // Rook Autoturret/Queen Logic + if (CanUseQueenMeow(out act, nextGCD)) return true; + + // Use Hypercharge if wildfire will not be up in 30 seconds or if you hit 100 heat and it will not break your combo + if (!LowLevelHyperCheck + && !Player.HasStatus(true, StatusID.Reassembled) + && (!WildfirePvE.Cooldown.WillHaveOneCharge(30) || Heat == 100) + && !(LiveComboTime <= HYPERCHARGE_DURATION && LiveComboTime > 0f) + && ToolChargeSoon(out act)) return true; + + // Use Ricochet and Gauss if have pooled charges or is burst window + if (IsRicochetMore) + { + if ((IsLastGCD(true, BlazingShotPvE, HeatBlastPvE) + || RicochetPvE.Cooldown.RecastTimeElapsed >= 45 + || !BarrelStabilizerPvE.Cooldown.ElapsedAfter(20)) + && RicochetPvE.CanUse(out act, skipAoeCheck: true, usedUp: true)) + return true; + } + + if ((IsLastGCD(true, BlazingShotPvE, HeatBlastPvE) + || GaussRoundPvE.Cooldown.RecastTimeElapsed >= 45 + || !BarrelStabilizerPvE.Cooldown.ElapsedAfter(20)) + && GaussRoundPvE.CanUse(out act, usedUp: true, skipAoeCheck: true)) + return true; + + + if (IsBurst && !FullMetalFieldPvE.EnoughLevel) + { + if (BarrelStabilizerPvE.CanUse(out act)) return true; + } + + return base.AttackAbility(nextGCD, out act); + } + #endregion + + #region GCD Logic + protected override bool GeneralGCD(out IAction? act) + { + // use procs asap + if (ExcavatorPvE.CanUse(out act, skipAoeCheck: true)) return true; + if (!ChainSawPvE.Cooldown.WillHaveOneChargeGCD(2) && FullMetalFieldPvE.CanUse(out act)) return true; + + // overheated aoe + if (AutoCrossbowPvE.CanUse(out act)) return true; + // overheated single + if (HeatBlastPvE.CanUse(out act)) return true; + + // drill's aoe version + if (BioblasterPvE.CanUse(out act, usedUp: true)) return true; + + // single target --- need to update this strange condition writing!!! + if (!SpreadShotPvE.CanUse(out _)) + { + // use AirAnchor if possible + if (HotShotMasteryTrait.EnoughLevel && AirAnchorPvE.CanUse(out act)) return true; + + // for burst: use Drill after AirAnchor + if (IsLastGCD(true, AirAnchorPvE) && EnhancedMultiweaponTrait.EnoughLevel && DrillPvE.CanUse(out act, usedUp: true)) return true; + if (!EnhancedMultiweaponTrait.EnoughLevel && DrillPvE.CanUse(out act, usedUp: true)) return true; + + if (!AirAnchorPvE.EnoughLevel && HotShotPvE.CanUse(out act)) return true; + } + + // ChainSaw is always used after Drill + if (ChainSawPvE.CanUse(out act, skipAoeCheck: true)) return true; + + // save Drill for burst + if (EnhancedMultiweaponTrait.EnoughLevel + && !ChainSawPvE.Cooldown.WillHaveOneCharge(6) + && (!(LiveComboTime <= 6) || (!CleanShotPvE.CanUse(out _) && !SlugShotPvE.CanUse(out _))) + && DrillPvE.CanUse(out act, usedUp: true)) return true; + + // basic aoe + if (SpreadShotPvE.CanUse(out act)) return true; + + // single target 123 combo + if (CleanShotPvE.CanUse(out act)) return true; + if (SlugShotPvE.CanUse(out act)) return true; + if (SplitShotPvE.CanUse(out act)) return true; + + return base.GeneralGCD(out act); + } + #endregion + + #region Extra Methods + // Extra private helper methods for determining the usability of specific abilities under certain conditions. + // These methods simplify the main logic by encapsulating specific checks related to abilities' cooldowns and prerequisites. + // Logic for Hypercharge + private bool ToolChargeSoon(out IAction? act) + { + if + //Cannot AOE + (!SpreadShotPvE.CanUse(out _) + && + // AirAnchor Enough Level % AirAnchor + ((AirAnchorPvE.EnoughLevel && AirAnchorPvE.Cooldown.WillHaveOneCharge(HYPERCHARGE_DURATION)) + || + // HotShot Charge Detection + (!AirAnchorPvE.EnoughLevel && HotShotPvE.EnoughLevel && HotShotPvE.Cooldown.WillHaveOneCharge(HYPERCHARGE_DURATION)) + || + // Drill Charge Detection + (DrillPvE.EnoughLevel && DrillPvE.Cooldown.WillHaveXCharges(DrillPvE.Cooldown.MaxCharges, HYPERCHARGE_DURATION)) + || + // Chainsaw Charge Detection + (ChainSawPvE.EnoughLevel && ChainSawPvE.Cooldown.WillHaveOneCharge(HYPERCHARGE_DURATION)))) + { + act = null; + return false; + } + else + { + return HyperchargePvE.CanUse(out act); + } + } + + private bool CanUseQueenMeow(out IAction? act, IAction nextGCD) + { + bool QueenOne = Battery >= 60 && CombatElapsedLess(25f); + bool QueenTwo = Battery >= 90 && !CombatElapsedLess(58f) && CombatElapsedLess(78f); + bool QueenThree = Battery >= 100 && !CombatElapsedLess(111f) && CombatElapsedLess(131f); + bool QueenFour = Battery >= 50 && !CombatElapsedLess(148f) && CombatElapsedLess(168f); + bool QueenFive = Battery >= 60 && !CombatElapsedLess(178f) && CombatElapsedLess(198f); + bool QueenSix = Battery >= 100 && !CombatElapsedLess(230f) && CombatElapsedLess(250f); + bool QueenSeven = Battery >= 50 && !CombatElapsedLess(268f) && CombatElapsedLess(288f); + bool QueenEight = Battery >= 70 && !CombatElapsedLess(296f) && CombatElapsedLess(316f); + bool QueenNine = Battery >= 100 && !CombatElapsedLess(350f) && CombatElapsedLess(370f); + bool QueenTen = Battery >= 50 && !CombatElapsedLess(388f) && CombatElapsedLess(408f); + bool QueenEleven = Battery >= 80 && !CombatElapsedLess(416f) && CombatElapsedLess(436f); + bool QueenTwelve = Battery >= 100 && !CombatElapsedLess(470f) && CombatElapsedLess(490f); + bool QueenThirteen = Battery >= 50 && !CombatElapsedLess(505f) && CombatElapsedLess(525f); + bool QueenFourteen = Battery >= 60 && !CombatElapsedLess(538f) && CombatElapsedLess(558f); + bool QueenFifteen = Battery >= 100 && !CombatElapsedLess(590f) && CombatElapsedLess(610f); + + if (UseBalanceQueenTimings && (QueenOne || QueenTwo || QueenThree || QueenFour || QueenFive || QueenSix || QueenSeven || QueenEight || QueenNine || QueenTen || QueenEleven || QueenTwelve || QueenThirteen || QueenFourteen || QueenFifteen)) + { + if (RookAutoturretPvE.CanUse(out act)) return true; + } + // take over with normal logic after queen timings run out in long fights + else if ((!UseBalanceQueenTimings || !CombatElapsedLess(610f)) && + // ASAP in opener + (CombatElapsedLessGCD(10) + // In first ~10 seconds of 2 minute window + || (!AirAnchorPvE.Cooldown.ElapsedAfter(10) && (BarrelStabilizerPvE.Cooldown.WillHaveOneChargeGCD(4) || !BarrelStabilizerPvE.Cooldown.ElapsedAfter(5)) + // or if about to overcap + || (nextGCD.IsTheSameTo(true, CleanShotPvE) && Battery == 100) + || (nextGCD.IsTheSameTo(true, AirAnchorPvE, ChainSawPvE, ExcavatorPvE) && (Battery == 90 || Battery == 100)) + ))) + { + if (RookAutoturretPvE.CanUse(out act)) return true; + } + act = null; + return false; + } + + // Check for not burning Hypercharge below level 52 on AOE + private bool LowLevelHyperCheck => !AutoCrossbowPvE.EnoughLevel && SpreadShotPvE.CanUse(out _); + + private bool TimeForBurstMeds(out IAction? act, IAction nextGCD) + { + if (AirAnchorPvE.Cooldown.WillHaveOneChargeGCD(2) && BarrelStabilizerPvE.Cooldown.WillHaveOneChargeGCD(6) && WildfirePvE.Cooldown.WillHaveOneChargeGCD(6)) return UseBurstMedicine(out act); + act = null; + return false; + } + + // Keeps Ricochet and Gauss Cannon Even + private bool IsRicochetMore => RicochetPvE.EnoughLevel && GaussRoundPvE.Cooldown.RecastTimeElapsed <= RicochetPvE.Cooldown.RecastTimeElapsed; + #endregion +} \ No newline at end of file diff --git a/BasicRotations/RebornRotations.csproj b/BasicRotations/RebornRotations.csproj new file mode 100644 index 000000000..46f05cd80 --- /dev/null +++ b/BasicRotations/RebornRotations.csproj @@ -0,0 +1,72 @@ + + + $(AppData)\XIVLauncher\addon\Hooks\dev\ + $(MSBuildProjectName) + + + $(SolutionDir)\bin\$(Configuration) + + + $(SolutionDir)\bin\$(Configuration) + + + + + + + + + + + + + + + + $(DalamudLibPath)Dalamud.dll + False + + + $(DalamudLibPath)ImGui.NET.dll + False + + + $(DalamudLibPath)ImGuiScene.dll + False + + + $(DalamudLibPath)Lumina.dll + False + + + $(DalamudLibPath)Lumina.Excel.dll + False + + + $(DalamudLibPath)FFXIVClientStructs.dll + False + + + $(DalamudLibPath)Newtonsoft.Json.dll + False + + + + + + + + + + + + + + + + + + + + + diff --git a/BasicRotations/Tank/DRK_Default.cs b/BasicRotations/Tank/DRK_Default.cs new file mode 100644 index 000000000..399e8ae1f --- /dev/null +++ b/BasicRotations/Tank/DRK_Default.cs @@ -0,0 +1,215 @@ +namespace DefaultRotations.Tank; + +[Rotation("Default", CombatType.PvE, GameVersion = "7.05")] +[SourceCode(Path = "main/BasicRotations/Tank/DRK_Balance.cs")] +[Api(4)] +public sealed class DRK_Default : DarkKnightRotation +{ + #region Config Options + [RotationConfig(CombatType.PvE, Name = "Keep at least 3000 MP")] + public bool TheBlackestNight { get; set; } = true; + #endregion + + #region Countdown Logic + // Countdown logic to prepare for combat. + // Includes logic for using Provoke, tank stances, and burst medicines. + protected override IAction? CountDownAction(float remainTime) + { + //Provoke when has Shield. + if (remainTime <= CountDownAhead) + { + if (HasTankStance) + { + if (ProvokePvE.CanUse(out _)) return ProvokePvE; + } + } + if (remainTime <= 2 && UseBurstMedicine(out var act)) return act; + if (remainTime <= 3 && TheBlackestNightPvE.CanUse(out act)) return act; + if (remainTime <= 4 && BloodWeaponPvE.CanUse(out act)) return act; + return base.CountDownAction(remainTime); + } + #endregion + + #region oGCD Logic + // Decision-making for emergency abilities, focusing on Blood Weapon usage. + protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) + { + return base.EmergencyAbility(nextGCD, out act); + } + + // Determines healing actions based on The Blackest Night ability. + [RotationDesc(ActionID.TheBlackestNightPvE)] + protected override bool HealSingleAbility(IAction nextGCD, out IAction? act) + { + if (TheBlackestNightPvE.CanUse(out act)) return true; + return base.HealSingleAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.DarkMissionaryPvE, ActionID.ReprisalPvE)] + protected override bool DefenseAreaAbility(IAction nextGCD, out IAction? act) + { + if (!InTwoMIsBurst && DarkMissionaryPvE.CanUse(out act)) return true; + if (!InTwoMIsBurst && ReprisalPvE.CanUse(out act, skipAoeCheck: true)) return true; + + return base.DefenseAreaAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.TheBlackestNightPvE, ActionID.OblationPvE, ActionID.ReprisalPvE, ActionID.ShadowWallPvE, ActionID.RampartPvE, ActionID.DarkMindPvE)] + protected override bool DefenseSingleAbility(IAction nextGCD, out IAction? act) + { + act = null; + + if (Player.HasStatus(true, StatusID.BlackestNight)) return false; + + //10 + if (OblationPvE.CanUse(out act, usedUp: true)) return true; + + if (TheBlackestNightPvE.CanUse(out act)) return true; + //20 + if (DarkMindPvE.CanUse(out act)) return true; + + //30 + if ((!RampartPvE.Cooldown.IsCoolingDown || RampartPvE.Cooldown.ElapsedAfter(60)) && ShadowWallPvE.CanUse(out act)) return true; + if ((!RampartPvE.Cooldown.IsCoolingDown || RampartPvE.Cooldown.ElapsedAfter(60)) && ShadowedVigilPvE.CanUse(out act)) return true; + + //20 + if (ShadowWallPvE.Cooldown.IsCoolingDown && ShadowWallPvE.Cooldown.ElapsedAfter(60) && RampartPvE.CanUse(out act)) return true; + if (ShadowedVigilPvE.Cooldown.IsCoolingDown && ShadowedVigilPvE.Cooldown.ElapsedAfter(60) && RampartPvE.CanUse(out act)) return true; + + if (ReprisalPvE.CanUse(out act)) return true; + + return base.DefenseSingleAbility(nextGCD, out act); + } + + protected override bool AttackAbility(IAction nextGCD, out IAction? act) + { + if (CheckDarkSide) + { + if (FloodOfDarknessPvE.CanUse(out act)) return true; + if (EdgeOfDarknessPvE.CanUse(out act)) return true; + } + + if (IsBurst) + { + if (InCombat && DeliriumPvE.CanUse(out act)) return true; + if (DeliriumPvE.EnoughLevel && DeliriumPvE.Cooldown.ElapsedAfterGCD(1) && !DeliriumPvE.Cooldown.ElapsedAfterGCD(3) + && BloodWeaponPvE.CanUse(out act)) return true; + if (!DeliriumPvE.EnoughLevel) + { + if (BloodWeaponPvE.CanUse(out act)) return true; + } + if (LivingShadowPvE.CanUse(out act, skipAoeCheck: true)) return true; + } + + if (CombatElapsedLess(3)) + { + act = null; + return false; + } + + if (!IsMoving && SaltedEarthPvE.CanUse(out act, skipAoeCheck: true)) return true; + + if (ShadowbringerPvE.CanUse(out act, skipAoeCheck: true)) return true; + + if (NumberOfHostilesInRange >= 3 && AbyssalDrainPvE.CanUse(out act)) return true; + if (CarveAndSpitPvE.CanUse(out act)) return true; + + if (InTwoMIsBurst) + { + if (ShadowbringerPvE.CanUse(out act, usedUp: true, skipAoeCheck: true)) return true; + + } + + if (ShadowstridePvE.CanUse(out act, skipAoeCheck: true) && !IsMoving) return true; + + if (SaltAndDarknessPvE.CanUse(out act)) return true; + + if (InTwoMIsBurst) + { + if (ShadowstridePvE.CanUse(out act, skipAoeCheck: true) && !IsMoving) return true; + } + if (MergedStatus.HasFlag(AutoStatus.MoveForward) && MoveForwardAbility(nextGCD, out act)) return true; + return base.AttackAbility(nextGCD, out act); + } + #endregion + + #region GCD Logic + protected override bool GeneralGCD(out IAction? act) + { + if (ImpalementPvE.CanUse(out act, skipComboCheck: true)) return true; + if (QuietusPvE.CanUse(out act, skipComboCheck: true)) return true; + + if (IsLastGCD(true, ComeuppancePvE) && TorcleaverPvE.CanUse(out act, skipComboCheck: true)) return true; + if (IsLastGCD(true, ScarletDeliriumPvE) && ComeuppancePvE.CanUse(out act, skipComboCheck: true)) return true; + if (ScarletDeliriumPvE.CanUse(out act, skipComboCheck: true)) return true; + + if (DisesteemPvE.CanUse(out act)) return true; + + if (BloodspillerPvE.CanUse(out act, skipComboCheck: true)) return true; + + + + //AOE + if (StalwartSoulPvE.CanUse(out act)) return true; + if (UnleashPvE.CanUse(out act)) return true; + + //Single Target + if (SouleaterPvE.CanUse(out act)) return true; + if (SyphonStrikePvE.CanUse(out act)) return true; + if (HardSlashPvE.CanUse(out act)) return true; + + if (UnmendPvE.CanUse(out act)) return true; + + return base.GeneralGCD(out act); + } + #endregion + + #region Extra Methods + // Indicates whether the Dark Knight can heal using a single ability. + public override bool CanHealSingleAbility => false; + + // Logic to determine when to use blood-based abilities. + private bool UseBlood + { + get + { + // Conditions based on player statuses and ability cooldowns. + if (!DeliriumPvE.EnoughLevel || !LivingShadowPvE.EnoughLevel) return true; + if (Player.HasStatus(true, StatusID.Delirium_3836)) return true; + if ((Player.HasStatus(true, StatusID.Delirium_1972) || Player.HasStatus(true, StatusID.Delirium_3836)) && LivingShadowPvE.Cooldown.IsCoolingDown) return true; + if ((DeliriumPvE.Cooldown.WillHaveOneChargeGCD(1) && !LivingShadowPvE.Cooldown.WillHaveOneChargeGCD(3)) || Blood >= 90 && !LivingShadowPvE.Cooldown.WillHaveOneChargeGCD(1)) return true; + + return false; + + } + } + // Determines if currently in a burst phase based on cooldowns of key abilities. + private bool InTwoMIsBurst + { + get + { + if ((BloodWeaponPvE.Cooldown.IsCoolingDown && DeliriumPvE.Cooldown.IsCoolingDown && ((LivingShadowPvE.Cooldown.IsCoolingDown && !(LivingShadowPvE.Cooldown.ElapsedAfter(15))) || !LivingShadowPvE.EnoughLevel))) return true; + else return false; + } + } + + // Manages DarkSide ability based on several conditions. + private bool CheckDarkSide + { + get + { + if (DarkSideEndAfterGCD(3)) return true; + + if (CombatElapsedLess(3)) return false; + + if ((InTwoMIsBurst && HasDarkArts) || (HasDarkArts && Player.HasStatus(true, StatusID.BlackestNight)) || (HasDarkArts && DarkSideEndAfterGCD(3))) return true; + + if ((InTwoMIsBurst && BloodWeaponPvE.Cooldown.IsCoolingDown && LivingShadowPvE.Cooldown.IsCoolingDown && SaltedEarthPvE.Cooldown.IsCoolingDown && ShadowbringerPvE.Cooldown.CurrentCharges == 0 && CarveAndSpitPvE.Cooldown.IsCoolingDown)) return true; + + if (TheBlackestNight && CurrentMp < 6000) return false; + + return CurrentMp >= 8500; + } + } + #endregion +} \ No newline at end of file diff --git a/BasicRotations/Tank/GNB_Default.cs b/BasicRotations/Tank/GNB_Default.cs new file mode 100644 index 000000000..5c51623d3 --- /dev/null +++ b/BasicRotations/Tank/GNB_Default.cs @@ -0,0 +1,246 @@ +namespace DefaultRotations.Tank; + +[Rotation("Default", CombatType.PvE, GameVersion = "7.00")] +[SourceCode(Path = "main/BasicRotations/Tank/GNB_Default.cs")] +[Api(4)] +public sealed class GNB_Default : GunbreakerRotation +{ + #region Countdown Logic + protected override IAction? CountDownAction(float remainTime) + { + if (remainTime <= 0.7 && LightningShotPvE.CanUse(out var act)) return act; + if (remainTime <= 1.2 && UseBurstMedicine(out act)) return act; + return base.CountDownAction(remainTime); + } + #endregion + + #region oGCD Logic + protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) + { + if (base.EmergencyAbility(nextGCD, out act)) return true; + + if (InCombat && CombatElapsedLess(30)) + { + if (!CombatElapsedLessGCD(2) && NoMercyPvE.CanUse(out act, skipAoeCheck: true)) return true; + if (Player.HasStatus(true, StatusID.NoMercy) && BloodfestPvE.CanUse(out act, skipAoeCheck: true)) return true; + } + + return base.EmergencyAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.HeartOfLightPvE, ActionID.ReprisalPvE)] + protected override bool DefenseAreaAbility(IAction nextGCD, out IAction? act) + { + if (!Player.HasStatus(true, StatusID.NoMercy) && HeartOfLightPvE.CanUse(out act, skipAoeCheck: true)) return true; + if (!Player.HasStatus(true, StatusID.NoMercy) && ReprisalPvE.CanUse(out act, skipAoeCheck: true)) return true; + return base.DefenseAreaAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.HeartOfStonePvE, ActionID.NebulaPvE, ActionID.RampartPvE, ActionID.CamouflagePvE, ActionID.ReprisalPvE)] + protected override bool DefenseSingleAbility(IAction nextGCD, out IAction? act) + { + //10 + if (CamouflagePvE.CanUse(out act)) return true; + //15 + if (HeartOfStonePvE.CanUse(out act)) return true; + + //30 + if ((!RampartPvE.Cooldown.IsCoolingDown || RampartPvE.Cooldown.ElapsedAfter(60)) && NebulaPvE.CanUse(out act)) return true; + //20 + if (NebulaPvE.Cooldown.IsCoolingDown && NebulaPvE.Cooldown.ElapsedAfter(60) && RampartPvE.CanUse(out act)) return true; + + if (ReprisalPvE.CanUse(out act)) return true; + + return base.DefenseSingleAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.AuroraPvE)] + protected override bool HealSingleAbility(IAction nextGCD, out IAction? act) + { + if (AuroraPvE.CanUse(out act, usedUp: true)) return true; + return base.HealSingleAbility(nextGCD, out act); + } + protected override bool AttackAbility(IAction nextGCD, out IAction? act) + { + //if (IsBurst && CanUseNoMercy(out act)) return true; + + if (!CombatElapsedLessGCD(5) && NoMercyPvE.CanUse(out act, skipAoeCheck: true)) return true; + + if (JugularRipPvE.CanUse(out act)) return true; + + if (DangerZonePvE.CanUse(out act) && !DoubleDownPvE.EnoughLevel) + { + + if (!IsFullParty && !(DangerZonePvE.Target.Target?.IsBossFromTTK() ?? false)) return true; + + if (!GnashingFangPvE.EnoughLevel && (Player.HasStatus(true, StatusID.NoMercy) || !NoMercyPvE.Cooldown.WillHaveOneCharge(15))) return true; + + if (Player.HasStatus(true, StatusID.NoMercy) && GnashingFangPvE.Cooldown.IsCoolingDown) return true; + + if (!Player.HasStatus(true, StatusID.NoMercy) && !GnashingFangPvE.Cooldown.WillHaveOneCharge(20)) return true; + } + + if (Player.HasStatus(true, StatusID.NoMercy) && CanUseBowShock(out act)) return true; + + //if (TrajectoryPvE.CanUse(out act) && !IsMoving) return true; + if (GnashingFangPvE.Cooldown.IsCoolingDown && DoubleDownPvE.Cooldown.IsCoolingDown && Ammo == 0 && BloodfestPvE.CanUse(out act)) return true; + + if (AbdomenTearPvE.CanUse(out act)) return true; + if (EyeGougePvE.CanUse(out act)) return true; + if (FatedBrandPvE.CanUse(out act)) return true; + if (HypervelocityPvE.CanUse(out act)) return true; + if (MergedStatus.HasFlag(AutoStatus.MoveForward) && MoveForwardAbility(nextGCD, out act)) return true; + return base.AttackAbility(nextGCD, out act); + } + #endregion + + #region GCD Logic + protected override bool GeneralGCD(out IAction? act) + { + bool areDDTargetsInRange = AllHostileTargets.Any(hostile => hostile.DistanceToPlayer() < 4.5f); + + if (Player.HasStatus(true, StatusID.NoMercy) && BloodfestPvE.CanUse(out act)) return true; + + if (IsLastGCD(false, NobleBloodPvE) && LionHeartPvE.CanUse(out act, skipComboCheck: true)) return true; + if (IsLastGCD(false, ReignOfBeastsPvE) && NobleBloodPvE.CanUse(out act, skipComboCheck: true)) return true; + if (ReignOfBeastsPvE.CanUse(out act)) return true; + + if (Player.HasStatus(true, StatusID.NoMercy) && SonicBreakPvE.CanUse(out act)) return true; + + if (areDDTargetsInRange) + { + if (Player.HasStatus(true, StatusID.NoMercy) && CanUseDoubleDown(out act)) return true; + if (Player.HasStatus(true, StatusID.NoMercy) && IsLastGCD(ActionID.DoubleDownPvE) && BlastingZonePvE.CanUse(out act)) return true; + } + + if (NoMercyPvE.Cooldown.IsCoolingDown && BloodfestPvE.Cooldown.IsCoolingDown && BlastingZonePvE.CanUse(out act)) return true; + + if (CanUseGnashingFang(out act)) return true; + + if (SavageClawPvE.CanUse(out act, skipComboCheck: true)) return true; + if (WickedTalonPvE.CanUse(out act, skipComboCheck: true)) return true; + + if (CanUseBurstStrike(out act)) return true; + + if (FatedCirclePvE.CanUse(out act)) return true; + if (DemonSlaughterPvE.CanUse(out act)) return true; + if (DemonSlicePvE.CanUse(out act)) return true; + + if (Ammo == MaxAmmo && IsLastGCD(ActionID.BrutalShellPvE) && BurstStrikePvE.CanUse(out act)) return true; + + if (SolidBarrelPvE.CanUse(out act)) return true; + if (BrutalShellPvE.CanUse(out act)) return true; + if (KeenEdgePvE.CanUse(out act)) return true; + + if (LightningShotPvE.CanUse(out act)) return true; + + return base.GeneralGCD(out act); + } + #endregion + + #region Extra Methods + public override bool CanHealSingleSpell => false; + + public override bool CanHealAreaSpell => false; + + //private bool CanUseNoMercy(out IAction act) + //{ + // if (!NoMercy.CanUse(out act, CanUseOption.OnLastAbility)) return false; + + // if (!IsFullParty && !IsTargetBoss && !IsMoving && DemonSlice.CanUse(out _)) return true; + + // if (!BurstStrike.EnoughLevel) return true; + + // if (BurstStrike.EnoughLevel) + // { + // if (IsLastGCD((ActionID)KeenEdge.ID) && Ammo == 1 && !GnashingFang.IsCoolingDown && !BloodFest.IsCoolingDown) return true; + // else if (Ammo == (Level >= 88 ? 3 : 2)) return true; + // else if (Ammo == 2 && GnashingFang.IsCoolingDown) return true; + // } + + // act = null; + // return false; + //} + + private bool CanUseGnashingFang(out IAction? act) + { + if (GnashingFangPvE.CanUse(out act)) + { + //AOE Check: Mobs = NO, Boss = YES + if (DemonSlicePvE.CanUse(out _)) return false; + + if (Player.HasStatus(true, StatusID.NoMercy) || !NoMercyPvE.Cooldown.WillHaveOneCharge(55)) return true; + + if (Ammo > 0 && !NoMercyPvE.Cooldown.WillHaveOneCharge(17) && NoMercyPvE.Cooldown.WillHaveOneCharge(35)) return true; + + if (Ammo <= 3 && IsLastGCD((ActionID)BrutalShellPvE.ID) && NoMercyPvE.Cooldown.WillHaveOneCharge(3)) return true; + + if (Ammo == 1 && !NoMercyPvE.Cooldown.WillHaveOneCharge(55) && BloodfestPvE.Cooldown.WillHaveOneCharge(5)) return true; + + if (Ammo == 1 && !NoMercyPvE.Cooldown.WillHaveOneCharge(55) && (!BloodfestPvE.Cooldown.IsCoolingDown && BloodfestPvE.EnoughLevel || !BloodfestPvE.EnoughLevel)) return true; + } + return false; + } + + /*private bool CanUseSonicBreak(out IAction act) + { + if (SonicBreakPvE.CanUse(out act)) + { + + if (!GnashingFangPvE.EnoughLevel && Player.HasStatus(true, StatusID.NoMercy)) return true; + + if (!DoubleDownPvE.EnoughLevel && Player.HasStatus(true, StatusID.ReadyToRip) + && GnashingFangPvE.Cooldown.IsCoolingDown) return true; + + } + return false; + }*/ + + private bool CanUseDoubleDown(out IAction? act) + { + if (DoubleDownPvE.CanUse(out act, skipAoeCheck: true)) + { + if (SonicBreakPvE.Cooldown.IsCoolingDown && Player.HasStatus(true, StatusID.NoMercy)) return true; + if (Player.HasStatus(true, StatusID.NoMercy) && !NoMercyPvE.Cooldown.WillHaveOneCharge(55) && BloodfestPvE.Cooldown.WillHaveOneCharge(5)) return true; + + } + return false; + } + + private bool CanUseBurstStrike(out IAction act) + { + if (BurstStrikePvE.CanUse(out act)) + { + if (DemonSlicePvE.CanUse(out _)) return false; + + if (SonicBreakPvE.Cooldown.IsCoolingDown && SonicBreakPvE.Cooldown.WillHaveOneCharge(0.5f) && GnashingFangPvE.EnoughLevel) return false; + + if (Player.HasStatus(true, StatusID.NoMercy) && + AmmoComboStep == 0 && + !GnashingFangPvE.Cooldown.WillHaveOneCharge(1)) return true; + + if (!CartridgeChargeIiTrait.EnoughLevel && Ammo == 2) return true; + + if (IsLastGCD((ActionID)BrutalShellPvE.ID) && + (Ammo == MaxAmmo || + BloodfestPvE.Cooldown.WillHaveOneCharge(6) && Ammo <= 2 && !NoMercyPvE.Cooldown.WillHaveOneCharge(10) && BloodfestPvE.EnoughLevel)) return true; + + } + return false; + } + + private bool CanUseBowShock(out IAction act) + { + if (BowShockPvE.CanUse(out act, skipAoeCheck: true)) + { + //AOE CHECK + if (DemonSlicePvE.CanUse(out _) && !IsFullParty) return true; + + if (!SonicBreakPvE.EnoughLevel && Player.HasStatus(true, StatusID.NoMercy)) return true; + + if (Player.HasStatus(true, StatusID.NoMercy) && SonicBreakPvE.Cooldown.IsCoolingDown) return true; + } + return false; + } + #endregion +} \ No newline at end of file diff --git a/BasicRotations/Tank/PLD_Default.cs b/BasicRotations/Tank/PLD_Default.cs new file mode 100644 index 000000000..7bc4830d9 --- /dev/null +++ b/BasicRotations/Tank/PLD_Default.cs @@ -0,0 +1,252 @@ +namespace DefaultRotations.Tank; + +[Rotation("Default", CombatType.PvE, GameVersion = "7.05")] +[SourceCode(Path = "main/BasicRotations/Tank/PLD_Default.cs")] +[Api(4)] +public class PLD_Default : PaladinRotation +{ + #region Config Options + + [RotationConfig(CombatType.PvE, Name = "Use Hallowed Ground with Cover")] + private bool HallowedWithCover { get; set; } = true; + + [Range(1, 8, ConfigUnitType.Pixels)] + [RotationConfig(CombatType.PvE, Name = "How many GCDs to delay burst by (Assumes you open with Holy Spirit, 2 is best for melee opening) ")] + private int AdjustedBurst { get; set; } = 3; + + [RotationConfig(CombatType.PvE, Name = "Prioritize Atonement Combo During Fight or Flight outside of Opener (Might not good for Dungeons Packs)")] + private bool PrioritizeAtonementCombo { get; set; } = false; + + [RotationConfig(CombatType.PvE, Name = "Use Holy Spirit First (For if you want to MinMax it)")] + private bool MinMaxHolySpirit { get; set; } = false; + + [RotationConfig(CombatType.PvE, Name = "Use Divine Veil at 15 seconds remaining on Countdown")] + private bool UseDivineVeilPre { get; set; } = false; + + [RotationConfig(CombatType.PvE, Name = "Use Holy Circle or Holy Spirit when out of melee range")] + private bool UseHolyWhenAway { get; set; } = false; + + [RotationConfig(CombatType.PvE, Name = "Use Shield Bash when Low Blow is cooling down")] + private bool UseShieldBash { get; set; } = true; + + [RotationConfig(CombatType.PvE, Name = "Allow the Use of Shield Lob")] + private bool UseShieldLob { get; set; } = true; + + [RotationConfig(CombatType.PvE, Name = "Maximize Damage if Target if considered dying")] + private bool BurstTargetIfConsideredDying { get; set; } = false; + + [Range(0, 100, ConfigUnitType.Pixels)] + [RotationConfig(CombatType.PvE, Name = "Use Sheltron at minimum X Oath to prevent over cap (Set to 0 to disable)")] + private int WhenToSheltron { get; set; } = 100; + + [Range(0, 1, ConfigUnitType.Percent)] + [RotationConfig(CombatType.PvE, Name = "Health threshold for Intervention (Set to 0 to disable)")] + private float InterventionRatio { get; set; } = 0.6f; + + [Range(0, 1, ConfigUnitType.Percent)] + [RotationConfig(CombatType.PvE, Name = "Health threshold for Cover (Set to 0 to disable)")] + private float CoverRatio { get; set; } = 0.3f; + + private bool HasAtonementReady => Player.HasStatus(true, StatusID.AtonementReady); + private bool HasSupplicationReady => Player.HasStatus(true, StatusID.SupplicationReady); + private bool HasSepulchreReady => Player.HasStatus(true, StatusID.SepulchreReady); + private bool HasHonorReady => Player.HasStatus(true, StatusID.BladeOfHonorReady); + private bool TargetIsDying => (HostileTarget?.IsDying() ?? false) && BurstTargetIfConsideredDying; + + private bool HolySpiritFirst(out IAction? act) + { + act = null; + if (MinMaxHolySpirit && HasDivineMight && HolySpiritPvE.CanUse(out act)) return true; + return false; + } + + private const ActionID ConfiteorPvEActionId = (ActionID)16459; + private new readonly IBaseAction ConfiteorPvE = new BaseAction(ConfiteorPvEActionId); + #endregion + + #region Countdown Logic + protected override IAction? CountDownAction(float remainTime) + { + if (remainTime < HolySpiritPvE.Info.CastTime + CountDownAhead + && HolySpiritPvE.CanUse(out var act)) return act; + + if (remainTime < 15 && UseDivineVeilPre + && DivineVeilPvE.CanUse(out act)) return act; + + return base.CountDownAction(remainTime); + } + #endregion + + #region oGCD Logic + protected override bool EmergencyAbility(IAction nextGCD, out IAction? act) + { + { + if (Player.HasStatus(true, StatusID.Cover) && HallowedWithCover && HallowedGroundPvE.CanUse(out act)) return true; + + if (HallowedGroundPvE.CanUse(out act) + && Player.GetHealthRatio() <= HealthForDyingTanks) return true; + + if ((Player.HasStatus(true, StatusID.Rampart) || Player.HasStatus(true, StatusID.Sentinel)) && + InterventionPvE.CanUse(out act) && + InterventionPvE.Target.Target?.GetHealthRatio() < 0.6) return true; + + if (CoverPvE.CanUse(out act) && CoverPvE.Target.Target?.DistanceToPlayer() < 10 && + CoverPvE.Target.Target?.GetHealthRatio() < CoverRatio) return true; + } + return base.EmergencyAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.IntervenePvE)] + protected override bool MoveForwardAbility(IAction nextGCD, out IAction? act) + { + if (IntervenePvE.CanUse(out act)) return true; + return base.MoveForwardAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.SentinelPvE, ActionID.RampartPvE, ActionID.BulwarkPvE, ActionID.SheltronPvE, ActionID.ReprisalPvE)] + protected override bool DefenseSingleAbility(IAction nextGCD, out IAction? act) + { + + // If the player has the Hallowed Ground status, don't use any abilities. + if (!Player.HasStatus(true, StatusID.HallowedGround)) + { + // If Bulwark can be used, use it and return true. + if (BulwarkPvE.CanUse(out act, skipAoeCheck: true)) return true; + + // If Oath can be used, use it and return true. + if (UseOath(out act)) return true; + + // If Rampart is not cooling down or has been cooling down for more than 60 seconds, and Sentinel can be used, use Sentinel and return true. + if ((!RampartPvE.Cooldown.IsCoolingDown || RampartPvE.Cooldown.ElapsedAfter(60)) && SentinelPvE.CanUse(out act)) return true; + + // If Sentinel is at an enough level and is cooling down for more than 60 seconds, or if Sentinel is not at an enough level, and Rampart can be used, use Rampart and return true. + if ((SentinelPvE.EnoughLevel && SentinelPvE.Cooldown.IsCoolingDown && SentinelPvE.Cooldown.ElapsedAfter(60) || !SentinelPvE.EnoughLevel) && RampartPvE.CanUse(out act)) return true; + + // If Reprisal can be used, use it and return true. + if (ReprisalPvE.CanUse(out act, skipAoeCheck: true)) return true; + + } + return base.DefenseSingleAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.DivineVeilPvE, ActionID.PassageOfArmsPvE)] + protected override bool DefenseAreaAbility(IAction nextGCD, out IAction? act) + { + if (DivineVeilPvE.CanUse(out act)) return true; + + if (PassageOfArmsPvE.CanUse(out act)) return true; + + return base.DefenseAreaAbility(nextGCD, out act); + } + + protected override bool AttackAbility(IAction nextGCD, out IAction? act) + { + if (WeaponRemain > 0.42f) + { + act = null; + + if (HasHonorReady && BladeOfHonorPvE.CanUse(out act, skipAoeCheck: true)) return true; + + if ((InCombat && !CombatElapsedLessGCD(AdjustedBurst))) + { + if (FightOrFlightPvE.CanUse(out act)) return true; + if (RequiescatPvE.CanUse(out act, skipAoeCheck: true, usedUp: HasFightOrFlight)) return true; + if (OathGauge >= WhenToSheltron && WhenToSheltron > 0 && UseOath(out act)) return true; + } + + if (CombatElapsedLessGCD(AdjustedBurst + 1)) return false; + + if (FightOrFlightPvE.CanUse(out act)) return true; + if (RequiescatPvE.CanUse(out act, skipAoeCheck: true, usedUp: HasFightOrFlight)) return true; + if (OathGauge >= WhenToSheltron && WhenToSheltron > 0 && UseOath(out act)) return true; + + if (CircleOfScornPvE.CanUse(out act, skipAoeCheck: true)) return true; + if (SpiritsWithinPvE.CanUse(out act, skipAoeCheck: true)) return true; + + if (!IsMoving && IntervenePvE.CanUse(out act, skipAoeCheck: true, usedUp: HasFightOrFlight)) return true; + } + + return base.AttackAbility(nextGCD, out act); + } + #endregion + + #region GCD Logic + protected override bool GeneralGCD(out IAction? act) + { + //Minimizes Accidents in EX and Savage Hopefully + if (IsInHighEndDuty && !InCombat) { act = null; return false; } + + if (Player.HasStatus(true, StatusID.Requiescat)) + { + if ((Player.Level >= 90) && (Player.StatusStack(true, StatusID.Requiescat) < 4)) + { + if (!TargetIsDying && PrioritizeAtonementCombo && !CombatElapsedLess(30) && (Player.StatusTime(true, StatusID.FightOrFlight) > 12) && AtonementCombo(out act)) return true; + if (ConfiteorPvE.CanUse(out act, skipAoeCheck: true)) return true; + } + if ((Player.Level >= 80) && (Player.StatusStack(true, StatusID.Requiescat) > 3)) + { + if (!TargetIsDying && PrioritizeAtonementCombo && !CombatElapsedLess(30) && (Player.StatusTime(true, StatusID.FightOrFlight) > 12) && AtonementCombo(out act)) return true; + if (ConfiteorPvE.CanUse(out act, skipAoeCheck: true)) return true; + } + if (HolyCirclePvE.CanUse(out act)) return true; + if (HolySpiritPvE.CanUse(out act)) return true; + } + + //AOE + if (HasDivineMight && HolyCirclePvE.CanUse(out act)) return true; + if (ProminencePvE.CanUse(out act)) return true; + if (TotalEclipsePvE.CanUse(out act)) return true; + + //Single + if (UseShieldBash && ShieldBashPvE.CanUse(out act)) return true; + + if (Player.HasStatus(true, StatusID.FightOrFlight) && AtonementCombo(out act)) return true; + if (TargetIsDying && AtonementCombo(out act)) return true; + + //Helps ensure Atonement combo is ready for FoF in cases of unfortunate downtime + if (((!HasAtonementReady && (HasSepulchreReady || HasSupplicationReady || HasDivineMight)) || + (HasAtonementReady && !HasDivineMight)) && + !Player.HasStatus(true, StatusID.Medicated) && !RageOfHalonePvE.CanUse(out act, skipComboCheck: false)) + { + if (RiotBladePvE.CanUse(out act) || FastBladePvE.CanUse(out act)) return true; + } + + if (!(HasSupplicationReady || HasSepulchreReady || HasDivineMight || HasAtonementReady) && RageOfHalonePvE.CanUse(out act)) return true; + + if (AtonementCombo(out act)) return true; + + if (RiotBladePvE.CanUse(out act) || FastBladePvE.CanUse(out act)) return true; + + //Range + if (UseHolyWhenAway && Player.CurrentMp > 3000) + { + if (HolyCirclePvE.CanUse(out act) || HolySpiritPvE.CanUse(out act)) + return true; + } + + if (UseShieldLob && ShieldLobPvE.CanUse(out act)) return true; + + return base.GeneralGCD(out act); + } + + [RotationDesc(ActionID.ClemencyPvE)] + protected override bool HealSingleGCD(out IAction? act) + { + if (ClemencyPvE.CanUse(out act)) return true; + return base.HealSingleGCD(out act); + } + #endregion + + #region Extra Methods + + private bool AtonementCombo(out IAction? act) => HolySpiritFirst(out act) || GoringBladePvE.CanUse(out act) || AtonementPvE.CanUse(out act) || SupplicationPvE.CanUse(out act) || SepulchrePvE.CanUse(out act) || HasDivineMight && HolyCirclePvE.CanUse(out act) || HasDivineMight && HolySpiritPvE.CanUse(out act); + + private bool UseOath(out IAction? act) + { + act = null; + if ((InterventionPvE.Target.Target?.GetHealthRatio() <= InterventionRatio) && InterventionPvE.CanUse(out act)) return true; + if (SheltronPvE.CanUse(out act)) return true; + return false; + } + #endregion +} diff --git a/BasicRotations/Tank/WAR_Default.cs b/BasicRotations/Tank/WAR_Default.cs new file mode 100644 index 000000000..071bfb331 --- /dev/null +++ b/BasicRotations/Tank/WAR_Default.cs @@ -0,0 +1,197 @@ +namespace DefaultRotations.Tank; + +[Rotation("Default", CombatType.PvE, GameVersion = "7.05")] +[SourceCode(Path = "main/BasicRotations/Tank/WAR_Default.cs")] +[Api(4)] +public sealed class WAR_Default : WarriorRotation +{ + #region Config Options + [RotationConfig(CombatType.PvE, Name = "Only use Nascent Flash if Tank Stance is off")] + public bool NeverscentFlash { get; set; } = false; + + [RotationConfig(CombatType.PvE, Name = "Use Bloodwhetting/Raw intuition on single enemies")] + public bool SoloIntuition { get; set; } = false; + + [RotationConfig(CombatType.PvE, Name = "Use Primal Rend while moving (Danger)")] + public bool YEET { get; set; } = false; + + [Range(1, 20, ConfigUnitType.Yalms)] + [RotationConfig(CombatType.PvE, Name = "Max distance you can be from the boss for Primal Rend use (Danger, setting too high will get you killed)")] + public float PrimalRendDistance { get; set; } = 2; + + [Range(0, 1, ConfigUnitType.Percent)] + [RotationConfig(CombatType.PvE, Name = "Nascent Flash Heal Threshold")] + public float FlashHeal { get; set; } = 0.6f; + + [Range(0, 1, ConfigUnitType.Percent)] + [RotationConfig(CombatType.PvE, Name = "Thrill Of Battle Heal Threshold")] + public float ThrillOfBattleHeal { get; set; } = 0.6f; + + [Range(0, 1, ConfigUnitType.Percent)] + [RotationConfig(CombatType.PvE, Name = "Equilibrium Heal Threshold")] + public float EquilibriumHeal { get; set; } = 0.6f; + + #endregion + + #region Countdown Logic + protected override IAction? CountDownAction(float remainTime) + { + if (remainTime < 0.54f && TomahawkPvE.CanUse(out var act)) return act; + return base.CountDownAction(remainTime); + } + #endregion + + #region oGCD Logic + protected override bool AttackAbility(IAction nextGCD, out IAction? act) + { + if (InfuriatePvE.CanUse(out act, gcdCountForAbility: 3)) return true; + + if (!InnerReleasePvE.EnoughLevel && Player.HasStatus(true, StatusID.Berserk) && InfuriatePvE.CanUse(out act, usedUp: true)) return true; + + if (CombatElapsedLessGCD(1)) return false; + + if (!Player.WillStatusEndGCD(2, 0, true, StatusID.SurgingTempest) + || !StormsEyePvE.EnoughLevel) + { + if (BerserkPvE.CanUse(out act)) return true; + } + + if (IsBurstStatus && (InnerReleaseStacks == 0 || InnerReleaseStacks == 3)) + { + if (InfuriatePvE.CanUse(out act, usedUp: true)) return true; + } + + if (CombatElapsedLessGCD(4)) return false; + + if (OrogenyPvE.CanUse(out act)) return true; + + if (UpheavalPvE.CanUse(out act)) return true; + + if (Player.HasStatus(false, StatusID.Wrathful) && PrimalWrathPvE.CanUse(out act, skipAoeCheck: true)) return true; + + if (OnslaughtPvE.CanUse(out act, usedUp: IsBurstStatus) && + !IsMoving && + !IsLastAction(true, OnslaughtPvE) && + !IsLastAction(true, UpheavalPvE) && + Player.HasStatus(false, StatusID.SurgingTempest)) + { + return true; + } + + if (MergedStatus.HasFlag(AutoStatus.MoveForward) && MoveForwardAbility(nextGCD, out act)) return true; + return base.AttackAbility(nextGCD, out act); + } + + protected override bool GeneralAbility(IAction nextGCD, out IAction? act) + { + if (Player.GetHealthRatio() < ThrillOfBattleHeal) + { + if (ThrillOfBattlePvE.CanUse(out act)) return true; + } + + if (!Player.HasStatus(true, StatusID.Holmgang_409)) + { + if (Player.GetHealthRatio() < EquilibriumHeal) + { + if (EquilibriumPvE.CanUse(out act)) return true; + } + } + return base.GeneralAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.RawIntuitionPvE, ActionID.VengeancePvE, ActionID.RampartPvE, ActionID.RawIntuitionPvE, ActionID.ReprisalPvE)] + protected override bool DefenseSingleAbility(IAction nextGCD, out IAction? act) + { + bool RawSingleTargets = SoloIntuition; + act = null; + + if (Player.HasStatus(true, StatusID.Holmgang_409) && Player.GetHealthRatio() < 0.3f) return false; + + if (RawIntuitionPvE.CanUse(out act) && (RawSingleTargets || NumberOfHostilesInRange > 2)) return true; + + if (!Player.WillStatusEndGCD(0, 0, true, StatusID.Bloodwhetting, StatusID.RawIntuition)) return false; + + if (ReprisalPvE.CanUse(out act, skipAoeCheck: true)) return true; + + if ((!RampartPvE.Cooldown.IsCoolingDown || RampartPvE.Cooldown.ElapsedAfter(60)) && VengeancePvE.CanUse(out act)) return true; + + if (((VengeancePvE.Cooldown.IsCoolingDown && VengeancePvE.Cooldown.ElapsedAfter(60)) || !VengeancePvE.EnoughLevel) && RampartPvE.CanUse(out act)) return true; + + return base.DefenseSingleAbility(nextGCD, out act); + } + + [RotationDesc(ActionID.ShakeItOffPvE, ActionID.ReprisalPvE)] + protected override bool DefenseAreaAbility(IAction nextGCD, out IAction? act) + { + act = null; + + if (ShakeItOffPvE.Cooldown.IsCoolingDown && !ShakeItOffPvE.Cooldown.WillHaveOneCharge(60) + || ReprisalPvE.Cooldown.IsCoolingDown && !ReprisalPvE.Cooldown.WillHaveOneCharge(50)) return false; + + if (ShakeItOffPvE.CanUse(out act, skipAoeCheck: true)) return true; + + return base.DefenseAreaAbility(nextGCD, out act); + } + #endregion + + #region GCD Logic + protected override bool GeneralGCD(out IAction? act) + { + if (!Player.WillStatusEndGCD(3, 0, true, StatusID.SurgingTempest)) + { + if (ChaoticCyclonePvE.CanUse(out act)) return true; + if (InnerChaosPvE.CanUse(out act)) return true; + } + + if (!Player.WillStatusEndGCD(3, 0, true, StatusID.SurgingTempest) && !Player.HasStatus(true, StatusID.NascentChaos) && InnerReleaseStacks > 0) + { + if (DecimatePvE.CanUse(out act, skipStatusProvideCheck: true)) return true; + if (FellCleavePvE.CanUse(out act, skipStatusProvideCheck: true)) return true; + } + + if (!Player.WillStatusEndGCD(3, 0, true, StatusID.SurgingTempest) && InnerReleaseStacks == 0) + { + if ((YEET || (!YEET && !IsMoving)) && PrimalRendPvE.CanUse(out act, skipAoeCheck: true)) + { + if (PrimalRendPvE.Target.Target?.DistanceToPlayer() < PrimalRendDistance) return true; + } + if (PrimalRuinationPvE.CanUse(out act)) return true; + } + + // AOE + if (!Player.WillStatusEndGCD(3, 0, true, StatusID.SurgingTempest) && DecimatePvE.CanUse(out act, skipStatusProvideCheck: true)) return true; + if (!SteelCycloneMasteryTrait.IsEnabled && !Player.WillStatusEndGCD(3, 0, true, StatusID.SurgingTempest) && SteelCyclonePvE.CanUse(out act)) return true; + if (MythrilTempestPvE.CanUse(out act)) return true; + if (OverpowerPvE.CanUse(out act)) return true; + + // Single Target + if (!Player.WillStatusEndGCD(3, 0, true, StatusID.SurgingTempest) && FellCleavePvE.CanUse(out act, skipStatusProvideCheck: true)) return true; + if (!InnerBeastMasteryTrait.IsEnabled && (!StormsEyePvE.EnoughLevel || !Player.WillStatusEndGCD(3, 0, true, StatusID.SurgingTempest)) && InnerBeastPvE.CanUse(out act)) return true; + if (StormsEyePvE.CanUse(out act)) return true; + if (StormsPathPvE.CanUse(out act)) return true; + if (MaimPvE.CanUse(out act)) return true; + if (HeavySwingPvE.CanUse(out act)) return true; + + // Ranged + if (TomahawkPvE.CanUse(out act)) return true; + + return base.GeneralGCD(out act); + } + + [RotationDesc(ActionID.NascentFlashPvE)] + protected override bool HealSingleGCD(out IAction? act) + { + if (!NeverscentFlash && NascentFlashPvE.CanUse(out act) + && (InCombat && NascentFlashPvE.Target.Target?.GetHealthRatio() < FlashHeal)) return true; + + if (NeverscentFlash && NascentFlashPvE.CanUse(out act) + && (InCombat && !Player.HasStatus(true, StatusID.Defiance) && NascentFlashPvE.Target.Target?.GetHealthRatio() < FlashHeal)) return true; + + return base.HealSingleGCD(out act); + } + #endregion + + #region Extra Methods + private static bool IsBurstStatus => !Player.WillStatusEndGCD(0, 0, false, StatusID.InnerStrength); + #endregion +} diff --git a/RotationSolver.Basic/Configuration/Configs.cs b/RotationSolver.Basic/Configuration/Configs.cs index 995a821d2..459e769d4 100644 --- a/RotationSolver.Basic/Configuration/Configs.cs +++ b/RotationSolver.Basic/Configuration/Configs.cs @@ -34,10 +34,7 @@ public const string public List Events { get; private set; } = []; public SortedSet DisabledJobs { get; private set; } = []; - [JsonIgnore] - public static string[] DefaultRotations = ["https://github.com/FFXIV-CombatReborn/RebornRotations/releases/latest/download/RebornRotations.dll"]; - - public string[] RotationLibs { get; set; } = DefaultRotations; + public string[] RotationLibs { get; set; } = []; public List TargetingTypes { get; set; } = []; public MacroInfo DutyStart { get; set; } = new MacroInfo(); @@ -202,7 +199,10 @@ public const string private static readonly bool _inDebug = false; [ConditionBool, UI("Load rotations automatically at startup", Filter = Rotations)] - private static readonly bool _autoLoadRotations = false; + private static readonly bool _loadRotationsAtStartup = true; + + [ConditionBool, UI("Load default rotations", Description = "Load the rotations provided by the Combat Reborn team", Filter = Rotations)] + private static readonly bool _loadDefaultRotations = true; [ConditionBool, UI("Download custom rotations from the internet", Description = "This will allow RSR to download custom rotations from the internet. This is a security risk and should only be enabled if you trust the source of the rotations.", diff --git a/RotationSolver.Basic/Configuration/RotationSolverRecord.cs b/RotationSolver.Basic/Configuration/RotationSolverRecord.cs index 5e2690aa8..bd71e2ad3 100644 --- a/RotationSolver.Basic/Configuration/RotationSolverRecord.cs +++ b/RotationSolver.Basic/Configuration/RotationSolverRecord.cs @@ -9,9 +9,4 @@ public class RotationSolverRecord /// Gets or sets the number of times the Rotation Solver has clicked for you. /// public uint ClickingCount { get; set; } = 0; - - /// - /// Gets or sets the users that have already been greeted. - /// - public HashSet SaidUsers { get; set; } = new HashSet(); } \ No newline at end of file diff --git a/RotationSolver.sln b/RotationSolver.sln index 0022105ef..84642ec36 100644 --- a/RotationSolver.sln +++ b/RotationSolver.sln @@ -29,6 +29,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RotationSolver.GameData", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RotationSolver.DummyRotations", "RotationSolver.DummyRotations\RotationSolver.DummyRotations.csproj", "{2C9BACA6-A791-478C-84BB-8A798123468A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RebornRotations", "BasicRotations\RebornRotations.csproj", "{054B46FA-5639-45A0-A5A1-4A8ED860DDC9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -59,6 +61,10 @@ Global {2C9BACA6-A791-478C-84BB-8A798123468A}.Debug|x64.Build.0 = Debug|x64 {2C9BACA6-A791-478C-84BB-8A798123468A}.Release|x64.ActiveCfg = Release|x64 {2C9BACA6-A791-478C-84BB-8A798123468A}.Release|x64.Build.0 = Release|x64 + {054B46FA-5639-45A0-A5A1-4A8ED860DDC9}.Debug|x64.ActiveCfg = Debug|Any CPU + {054B46FA-5639-45A0-A5A1-4A8ED860DDC9}.Debug|x64.Build.0 = Debug|Any CPU + {054B46FA-5639-45A0-A5A1-4A8ED860DDC9}.Release|x64.ActiveCfg = Release|Any CPU + {054B46FA-5639-45A0-A5A1-4A8ED860DDC9}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/RotationSolver/Data/UiString.cs b/RotationSolver/Data/UiString.cs index d1ec321ad..e0ac8a501 100644 --- a/RotationSolver/Data/UiString.cs +++ b/RotationSolver/Data/UiString.cs @@ -160,9 +160,6 @@ internal enum UiString [Description("Conditions for automatic use of action being disabled.")] ConfigWindow_Actions_DisabledConditionSet_Description, - [Description("It looks like this might be your first time here. Rotation Solver Reborn does not come with rotations out of the box, but you can download ones created by the community. You can also create your own rotations! For your convenience, Rotation Solver Reborn comes pre-loaded with links to well-known community rotations, but it still your responsibility to install them.")] - ConfigWindow_Rotations_FirstTime, - [Description("Custom rotations are just like plugins and have full access to the game and your computer")] ConfigWindow_Rotations_Warning, @@ -676,26 +673,8 @@ internal enum UiString [Description("It looks like you might be new here! Let's get you started!")] WelcomeWindow_Welcome, - [Description("Rotation Solver Reborn does not come with rotations out of the box, but for your convenience a link to a set of rotations maintained by the Combat Reborn team is included by default.")] - WelcomeWindow_FirstTime, - - [Description("Would you like to install the default rotations now?")] - WelcomeWindow_FirstTime2, - - [Description("Some other settings you may want to consider:")] - WelcomeWindow_FirstTime3, - - [Description("Click here to install")] - WelcomeWindow_SaveAndInstall, - [Description("Recent Changes:")] WelcomeWindow_Changelog, - - [Description("Do you want your rotations to update and reload automatically upon login?")] - WelcomeWindow_LoadAtStartup, - - [Description("Do you want to automatically reload local rotations when they are updated? (Developer Mode)")] - WelcomeWindow_AutoReload, } public static class EnumExtensions diff --git a/RotationSolver/RotationSolver.csproj b/RotationSolver/RotationSolver.csproj index b6c97e665..3dc11910a 100644 --- a/RotationSolver/RotationSolver.csproj +++ b/RotationSolver/RotationSolver.csproj @@ -6,6 +6,12 @@ $(AppData)\XIVLauncher\addon\Hooks\dev\ x64 + + $(SolutionDir)\bin\$(Configuration) + + + $(SolutionDir)\bin\$(Configuration) + diff --git a/RotationSolver/RotationSolverPlugin.cs b/RotationSolver/RotationSolverPlugin.cs index 58dc6b43c..3ae476d40 100644 --- a/RotationSolver/RotationSolverPlugin.cs +++ b/RotationSolver/RotationSolverPlugin.cs @@ -188,7 +188,7 @@ static void DutyState_DutyWiped(object? sender, ushort e) Task.Run(async () => { await DownloadHelper.DownloadAsync(); - if (Service.Config.AutoLoadRotations) await RotationUpdater.GetAllCustomRotationsAsync(DownloadOption.Download); + if (Service.Config.LoadRotationsAtStartup) await RotationUpdater.GetAllCustomRotationsAsync(DownloadOption.Download); }); } diff --git a/RotationSolver/UI/RotationConfigWindow.cs b/RotationSolver/UI/RotationConfigWindow.cs index 545226367..41ad1bc36 100644 --- a/RotationSolver/UI/RotationConfigWindow.cs +++ b/RotationSolver/UI/RotationConfigWindow.cs @@ -970,17 +970,17 @@ private void DrawAutoduty() } ImGui.Spacing(); // Display the Auto Load Rotations status - ImGui.TextWrapped($"Auto Load Rotations: {Service.Config.AutoLoadRotations}"); + ImGui.TextWrapped($"Auto Load Rotations: {Service.Config.LoadRotationsAtStartup}"); if (ImGui.Button("Enable Auto Loading Rotations")) { - Service.Config.AutoLoadRotations.Value = true; + Service.Config.LoadRotationsAtStartup.Value = true; } ImGui.Spacing(); // Display the Download Custom Rotations status ImGui.TextWrapped($"Download Custom Rotations: {Service.Config.DownloadCustomRotations}"); if (ImGui.Button("Enable Downloading Custom Rotations")) { - Service.Config.AutoLoadRotations.Value = true; + Service.Config.LoadRotationsAtStartup.Value = true; } ImGui.Spacing(); // Display the Auto Off Between Area status @@ -1719,15 +1719,6 @@ private static void DrawRotations() }, width, textWidth); ImGui.PopFont(); - if (DataCenter.RightNowRotation == null) - { - text = UiString.ConfigWindow_Rotations_FirstTime.GetDescription(); - textWidth = ImGuiHelpers.GetButtonSize(text).X; - ImGui.TextWrapped(text); - } - - ImGui.Separator(); - ImGui.Separator(); DrawRotationsSettings(); diff --git a/RotationSolver/UI/WelcomeWindow.cs b/RotationSolver/UI/WelcomeWindow.cs index 3be3b36b5..829bb248c 100644 --- a/RotationSolver/UI/WelcomeWindow.cs +++ b/RotationSolver/UI/WelcomeWindow.cs @@ -146,59 +146,6 @@ public override void Draw() ImGui.Separator(); // Separator for aesthetic or logical separation - if (!Service.Config.FirstTimeSetupDone) - { - text = UiString.WelcomeWindow_FirstTime.GetDescription(); - ImGui.PushFont(FontManager.GetFont(fontSize + 3)); - textSize = ImGui.CalcTextSize(text).X; - ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudOrange); - ImGui.TextWrapped(text); - ImGui.PopStyleColor(); - ImGui.PopFont(); - - text = UiString.WelcomeWindow_FirstTime3.GetDescription(); - ImGui.TextWrapped(text); - var autoUpdate = Service.Config.AutoLoadRotations.Value; - if (ImGui.Checkbox(UiString.WelcomeWindow_LoadAtStartup.GetDescription(), ref autoUpdate)) - { - Service.Config.AutoLoadRotations.Value = autoUpdate; - Service.Config.Save(); - } - var autoReload = Service.Config.AutoReloadRotations.Value; - if (ImGui.Checkbox(UiString.WelcomeWindow_AutoReload.GetDescription(), ref autoReload)) - { - Service.Config.AutoReloadRotations.Value = autoReload; - Service.Config.Save(); - } - - text = UiString.WelcomeWindow_FirstTime2.GetDescription(); - ImGui.PushFont(FontManager.GetFont(fontSize + 2)); - textSize = ImGui.CalcTextSize(text).X; - ImGuiHelper.DrawItemMiddle(() => - { - ImGui.TextColored(ImGuiColors.DalamudOrange, text); - }, windowWidth, textSize); - ImGui.PopFont(); - - text = UiString.WelcomeWindow_SaveAndInstall.GetDescription(); - textSize = ImGui.CalcTextSize(text).X; - ImGuiHelper.DrawItemMiddle(async () => - { - if (ImGui.Button(text)) - { - Service.Config.FirstTimeSetupDone = true; - Service.Config.Save(); - await Task.Run(async () => - { - await RotationUpdater.GetAllCustomRotationsAsync(DownloadOption.Download | DownloadOption.Local | DownloadOption.ShowList); - Service.Config.FirstTimeSetupDone = true; - Service.Config.Save(); - }); - } - }, windowWidth, textSize); - ImGui.Separator(); - } - DrawChangeLog(); ImGui.Separator(); diff --git a/RotationSolver/Updaters/RotationUpdater.cs b/RotationSolver/Updaters/RotationUpdater.cs index 886721b36..436058fd4 100644 --- a/RotationSolver/Updaters/RotationUpdater.cs +++ b/RotationSolver/Updaters/RotationUpdater.cs @@ -26,7 +26,6 @@ internal record CustomRotationGroup(Job JobId, Job[] ClassJobIds, Type[] Rotatio public static Task ResetToDefaults() { - Service.Config.RotationLibs = Configs.DefaultRotations; try { var relayFolder = Svc.PluginInterface.ConfigDirectory.FullName + "\\Rotations"; @@ -93,6 +92,18 @@ public static async Task GetAllCustomRotationsAsync(DownloadOption option) } } + private static Assembly? LoadDefaultRotationsFromLocal() + { + var directory = Svc.PluginInterface.AssemblyLocation.Directory; + if (directory == null || !directory.Exists) + { + Svc.Log.Error("Failed to find main assembly directory"); + return null; + } + var assemblyPath = Path.Combine(directory.ToString(), "RebornRotations.dll"); + return LoadOne(assemblyPath); + } + /// /// This method loads custom rotation groups from local directories and assemblies, creates a sorted list of /// author hashes, and creates a sorted list of custom rotations grouped by job role. @@ -106,12 +117,25 @@ private static void LoadRotationsFromLocal(string relayFolder) var assemblies = new List(); + if (Service.Config.LoadDefaultRotations) + { + var defaultAssembly = LoadDefaultRotationsFromLocal(); + if (defaultAssembly == null) + { + Svc.Log.Error("Failed to load default rotations from local directory"); + return; + } + assemblies.Add(defaultAssembly); + } + foreach (var dir in directories) { if (Directory.Exists(dir)) { foreach (var dll in Directory.GetFiles(dir, "*.dll")) { + if (dll.Contains("RebornRotations.dll")) + continue; var assembly = LoadOne(dll); if (assembly != null && !assemblies.Any(a => a.FullName == assembly.FullName))