From b82e941ac56be91fec2879bcc2ccf2b8ff79f4a6 Mon Sep 17 00:00:00 2001 From: Taurenkey Date: Tue, 26 Nov 2024 20:59:54 +0000 Subject: [PATCH 01/12] Add AI movement delay --- BossMod/AI/AIBehaviour.cs | 10 +++++++++- BossMod/AI/AIConfig.cs | 3 +++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/BossMod/AI/AIBehaviour.cs b/BossMod/AI/AIBehaviour.cs index 781ede8d18..d0244172cd 100644 --- a/BossMod/AI/AIBehaviour.cs +++ b/BossMod/AI/AIBehaviour.cs @@ -18,6 +18,8 @@ sealed class AIBehaviour(AIController ctrl, RotationModuleManager autorot) : IDi private bool _followMaster; // if true, our navigation target is master rather than primary target - this happens e.g. in outdoor or in dungeons during gathering trash private WPos _masterPrevPos; private DateTime _masterLastMoved; + private DateTime? _navDecisionTimeMade; + private TimeSpan _navDecisionTime => _navDecisionTimeMade != null ? WorldState.CurrentTime - _navDecisionTimeMade!.Value : TimeSpan.Zero; public void Dispose() { @@ -62,6 +64,12 @@ public void Execute(Actor player, Actor master) bool moveWithMaster = masterIsMoving && _followMaster && master != player; ForceMovementIn = moveWithMaster || gazeImminent || pyreticImminent ? 0 : _naviDecision.LeewaySeconds; + if (_naviDecision.Destination == null) + _navDecisionTimeMade = null; + + if (_naviDecision.Destination != null && _navDecisionTimeMade == null) + _navDecisionTimeMade = WorldState.CurrentTime; + UpdateMovement(player, master, target, gazeImminent || pyreticImminent, misdirectionMode ? autorot.Hints.MisdirectionThreshold : default, !forbidTargeting ? autorot.Hints.ActionsToExecute : null); } @@ -199,7 +207,7 @@ private void UpdateMovement(Actor player, Actor master, Targeting target, bool g { var toDest = _naviDecision.Destination != null ? _naviDecision.Destination.Value - player.Position : new(); var distSq = toDest.LengthSq(); - ctrl.NaviTargetPos = _naviDecision.Destination; + ctrl.NaviTargetPos = _navDecisionTime.TotalMilliseconds >= (_config.MoveDelay * 1000) ? _naviDecision.Destination : null; ctrl.NaviTargetVertical = master != player ? master.PosRot.Y : null; ctrl.AllowInterruptingCastByMovement = player.CastInfo != null && _naviDecision.LeewaySeconds <= player.CastInfo.RemainingTime - 0.5; ctrl.ForceCancelCast = false; diff --git a/BossMod/AI/AIConfig.cs b/BossMod/AI/AIConfig.cs index 722a0a2417..05f423bdc3 100644 --- a/BossMod/AI/AIConfig.cs +++ b/BossMod/AI/AIConfig.cs @@ -18,6 +18,9 @@ public enum Slot { One, Two, Three, Four } [PropertyDisplay("Show AI status in the server info bar")] public bool ShowDTR = true; + [PropertyDisplay("Delay on Movement (seconds)", tooltip: "Keep this value low! Too high and it won't move in time for some mechanics")] + public float MoveDelay = 0f; + // ai settings // TODO: this is really bad, it should not be here! it's a transient thing, doesn't make sense to preserve in config [PropertyDisplay($"Follow slot")] From af5b8d86e3be15b332ce007f91c67f7b6307c42c Mon Sep 17 00:00:00 2001 From: Taurenkey Date: Tue, 26 Nov 2024 21:09:55 +0000 Subject: [PATCH 02/12] Fix naming violation --- BossMod/AI/AIBehaviour.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BossMod/AI/AIBehaviour.cs b/BossMod/AI/AIBehaviour.cs index d0244172cd..b561dc3ebc 100644 --- a/BossMod/AI/AIBehaviour.cs +++ b/BossMod/AI/AIBehaviour.cs @@ -19,7 +19,7 @@ sealed class AIBehaviour(AIController ctrl, RotationModuleManager autorot) : IDi private WPos _masterPrevPos; private DateTime _masterLastMoved; private DateTime? _navDecisionTimeMade; - private TimeSpan _navDecisionTime => _navDecisionTimeMade != null ? WorldState.CurrentTime - _navDecisionTimeMade!.Value : TimeSpan.Zero; + private TimeSpan NavDecisionTime => _navDecisionTimeMade != null ? WorldState.CurrentTime - _navDecisionTimeMade!.Value : TimeSpan.Zero; public void Dispose() { @@ -207,7 +207,7 @@ private void UpdateMovement(Actor player, Actor master, Targeting target, bool g { var toDest = _naviDecision.Destination != null ? _naviDecision.Destination.Value - player.Position : new(); var distSq = toDest.LengthSq(); - ctrl.NaviTargetPos = _navDecisionTime.TotalMilliseconds >= (_config.MoveDelay * 1000) ? _naviDecision.Destination : null; + ctrl.NaviTargetPos = NavDecisionTime.TotalMilliseconds >= (_config.MoveDelay * 1000) ? _naviDecision.Destination : null; ctrl.NaviTargetVertical = master != player ? master.PosRot.Y : null; ctrl.AllowInterruptingCastByMovement = player.CastInfo != null && _naviDecision.LeewaySeconds <= player.CastInfo.RemainingTime - 0.5; ctrl.ForceCancelCast = false; From 237945ac8d697a5572dcc8acfcd0f97edda00afe Mon Sep 17 00:00:00 2001 From: Akechi-kun <167795370+Akechi-kun@users.noreply.github.com> Date: Sun, 1 Dec 2024 04:54:14 -0800 Subject: [PATCH 03/12] PvP implementation --- BossMod/ActionQueue/ClassShared.cs | 38 +- BossMod/ActionQueue/Tanks/GNB.cs | 88 ++ .../Autorotation/Utility/RolePvPUtility.cs | 286 ++++++ .../akechi/{AkechiDRG.cs => PvE/DRG.cs} | 350 ++++---- BossMod/Autorotation/akechi/PvE/GNB.cs | 825 ++++++++++++++++++ .../akechi/{AkechiPLD.cs => PvE/PLD.cs} | 184 ++-- BossMod/Autorotation/akechi/PvP/GNB.cs | 411 +++++++++ 7 files changed, 1916 insertions(+), 266 deletions(-) create mode 100644 BossMod/Autorotation/Utility/RolePvPUtility.cs rename BossMod/Autorotation/akechi/{AkechiDRG.cs => PvE/DRG.cs} (74%) create mode 100644 BossMod/Autorotation/akechi/PvE/GNB.cs rename BossMod/Autorotation/akechi/{AkechiPLD.cs => PvE/PLD.cs} (77%) create mode 100644 BossMod/Autorotation/akechi/PvP/GNB.cs diff --git a/BossMod/ActionQueue/ClassShared.cs b/BossMod/ActionQueue/ClassShared.cs index fd63ab4312..67df2742c6 100644 --- a/BossMod/ActionQueue/ClassShared.cs +++ b/BossMod/ActionQueue/ClassShared.cs @@ -2,6 +2,7 @@ public enum AID : uint { + #region PvE None = 0, Sprint = 3, @@ -59,11 +60,22 @@ public enum AID : uint AethericSiphon = 9102, Shatterstone = 9823, Deflect = 10006, - DeflectVeryEasy = 18863 + DeflectVeryEasy = 18863, + #endregion + + #region PvP + Elixir = 29055, + Recuperate = 29711, + Purify = 29056, + Guard = 29054, + //Guard = 29735, + SprintPvP = 29057, + #endregion } public enum SID : uint { + #region PvE None = 0, // Tank @@ -80,12 +92,27 @@ public enum SID : uint Addle = 1203, // applied by Addle to target Swiftcast = 167, // applied by Swiftcast to self Raise = 148, // applied by Raise to target + #endregion + + #region PvP + SprintPvP = 1342, + Guard = 3054, + Silence = 1347, + Bind = 1345, + StunPvP = 1343, + HalfAsleep = 3022, + Sleep = 1348, + DeepFreeze = 3219, + Heavy = 1344, + Unguarded = 3021, + #endregion } public sealed class Definitions : IDisposable { public Definitions(ActionDefinitions d) { + #region PvE d.RegisterSpell(AID.Sprint); // Tank @@ -143,6 +170,15 @@ public Definitions(ActionDefinitions d) d.RegisterSpell(AID.Shatterstone); d.RegisterSpell(AID.Deflect); d.RegisterSpell(AID.DeflectVeryEasy); + #endregion + + #region PvP + d.RegisterSpell(AID.Elixir); + d.RegisterSpell(AID.Recuperate); + d.RegisterSpell(AID.Purify); + d.RegisterSpell(AID.Guard); + d.RegisterSpell(AID.SprintPvP); + #endregion Customize(d); } diff --git a/BossMod/ActionQueue/Tanks/GNB.cs b/BossMod/ActionQueue/Tanks/GNB.cs index e5e804ed5b..7e8bf67f30 100644 --- a/BossMod/ActionQueue/Tanks/GNB.cs +++ b/BossMod/ActionQueue/Tanks/GNB.cs @@ -2,6 +2,7 @@ public enum AID : uint { + #region PvE None = 0, Sprint = ClassShared.AID.Sprint, @@ -55,6 +56,38 @@ public enum AID : uint Reprisal = ClassShared.AID.Reprisal, // L22, instant, 60.0s CD (group 44), range 0, AOE 5 circle, targets=self ArmsLength = ClassShared.AID.ArmsLength, // L32, instant, 120.0s CD (group 48), range 0, single-target, targets=self Shirk = ClassShared.AID.Shirk, // L48, instant, 120.0s CD (group 49), range 25, single-target, targets=party + #endregion + + #region PvP + KeenEdgePvP = 29098, // instant, GCD, range 3, single-target, targets=hostile + BrutalShellPvP = 29099, // instant, GCD, range 3, single-target, targets=hostile + SolidBarrelPvP = 29100, // instant, GCD, range 3, single-target, targets=hostile + BurstStrikePvP = 29101, // instant, GCD, range 5, single-target, targets=hostile + GnashingFangPvP = 29102, // instant, GCD, range 5, single-target, targets=hostile + FatedCirclePvP = 41511, // instant, GCD, range 0, AOE 5, targets=self + ContinuationPvP = 29106, // instant, oGCD, range 5, single-target, targets=hostile + RoughDividePvP = 29123, // instant, oGCD, range 20, single-target, targets=hostile + BlastingZonePvP = 29128, // instant, oGCD, range 5, single-target, targets=hostile + HeartOfCorundumPvP = 41443, // instant, oGCD, range 20, single-target, targets=party + SavageClawPvP = 29103, // instant, GCD, range 5, single-target, targets=hostile + WickedTalonPvP = 29104, // instant, GCD, range 5, single-target, targets=hostile + HypervelocityPvP = 29107, // instant, oGCD, range 5, single-target, targets=hostile + FatedBrandPvP = 41442, // instant, oGCD, range 5, single-target, targets=hostile + JugularRipPvP = 29108, // instant, oGCD, range 5, single-target, targets=hostile + AbdomenTearPvP = 29109, // instant, oGCD, range 5, single-target, targets=hostile + EyeGougePvP = 29110, // instant, oGCD, range 5, single-target, targets=hostile + + //LBs + RelentlessRushPvP = 29130, + TerminalTriggerPvP = 29131, + + //Shared + Elixir = ClassShared.AID.Elixir, + Recuperate = ClassShared.AID.Recuperate, + Purify = ClassShared.AID.Purify, + Guard = ClassShared.AID.Guard, + SprintPvP = ClassShared.AID.SprintPvP + #endregion } public enum TraitID : uint @@ -77,6 +110,7 @@ public enum TraitID : uint // TODO: regenerate public enum SID : uint { + #region PvE None = 0, BrutalShell = 1898, // applied by Brutal Shell to self NoMercy = 1831, // applied by No Mercy to self @@ -105,6 +139,36 @@ public enum SID : uint //Shared Reprisal = ClassShared.SID.Reprisal, // applied by Reprisal to target + #endregion PvE + + #region PvP + ReadyToBlastPvP = 3041, // applied by Burst Strike to self + ReadyToRazePvP = 4293, // applied by Fated Circle to self + ReadyToRipPvP = 2002, // applied by Gnashing Fang to self + ReadyToTearPvP = 2003, // applied by Savage Claw to self + ReadyToGougePvP = 2004, // applied by Wicked Talon to self + NebulaPvP = 3051, // applied by Nebula to self + NoMercyPvP = 3042, // applied by No Mercy to self + HeartOfCorundumPvP = 4295, // applied by Heart of Corundum to self + CatharsisOfCorundumPvP = 4296, // applied by Heart of Corundum to self + RelentlessRushPvP = 3052, + + //Shared + Elixir = ClassShared.AID.Elixir, + Recuperate = ClassShared.AID.Recuperate, + Purify = ClassShared.AID.Purify, + Guard = ClassShared.AID.Guard, + SprintPvP = ClassShared.AID.SprintPvP, + Silence = ClassShared.SID.Silence, + Bind = ClassShared.SID.Bind, + StunPvP = ClassShared.SID.StunPvP, + HalfAsleep = ClassShared.SID.HalfAsleep, + Sleep = ClassShared.SID.Sleep, + DeepFreeze = ClassShared.SID.DeepFreeze, + Heavy = ClassShared.SID.Heavy, + Unguarded = ClassShared.SID.Unguarded, + + #endregion } public sealed class Definitions : IDisposable @@ -113,6 +177,7 @@ public sealed class Definitions : IDisposable public Definitions(ActionDefinitions d) { + #region PvE d.RegisterSpell(AID.GunmetalSoul, instantAnimLock: 3.86f); d.RegisterSpell(AID.KeenEdge); d.RegisterSpell(AID.NoMercy); @@ -151,6 +216,29 @@ public Definitions(ActionDefinitions d) d.RegisterSpell(AID.ReignOfBeasts); d.RegisterSpell(AID.NobleBlood); d.RegisterSpell(AID.LionHeart); + #endregion + + #region PvP + d.RegisterSpell(AID.KeenEdgePvP); + d.RegisterSpell(AID.BrutalShellPvP); + d.RegisterSpell(AID.SolidBarrelPvP); + d.RegisterSpell(AID.BurstStrikePvP); + d.RegisterSpell(AID.GnashingFangPvP); + d.RegisterSpell(AID.SavageClawPvP); + d.RegisterSpell(AID.WickedTalonPvP); + d.RegisterSpell(AID.AbdomenTearPvP); + d.RegisterSpell(AID.JugularRipPvP); + d.RegisterSpell(AID.EyeGougePvP); + d.RegisterSpell(AID.ContinuationPvP); + d.RegisterSpell(AID.FatedCirclePvP); + d.RegisterSpell(AID.BlastingZonePvP); + d.RegisterSpell(AID.HeartOfCorundumPvP); + d.RegisterSpell(AID.HypervelocityPvP); + d.RegisterSpell(AID.FatedBrandPvP); + d.RegisterSpell(AID.RoughDividePvP); + d.RegisterSpell(AID.RelentlessRushPvP); + d.RegisterSpell(AID.TerminalTriggerPvP); + #endregion Customize(d); } diff --git a/BossMod/Autorotation/Utility/RolePvPUtility.cs b/BossMod/Autorotation/Utility/RolePvPUtility.cs new file mode 100644 index 0000000000..a07c13236e --- /dev/null +++ b/BossMod/Autorotation/Utility/RolePvPUtility.cs @@ -0,0 +1,286 @@ +using AID = BossMod.GNB.AID; +using SID = BossMod.GNB.SID; + +namespace BossMod.Autorotation.Utility; +//Contribution by Akechi +//Discord @akechdz or 'Akechi' on Puni.sh for maintenance + +public sealed class RolePvPUtility(RotationModuleManager manager, Actor player) : RotationModule(manager, player) +{ + #region Enums: Abilities / Strategies + public enum Track + { + Elixir, //Solid Barrel combo tracking + Recuperate, //Gnashing Fang action tracking + Guard, //Rough Divide ability tracking + Purify, //Fated Circle ability tracking + Sprint, //Burst tracking + } + public enum ElixirStrategy + { + Automatic, + Close, + Mid, + Far, + Force, + Hold + } + + public enum RecuperateStrategy + { + Automatic, //Automatically execute based on conditions + Seventy, //Execute at 70% HP + Fifty, //Execute at 50% HP + Thirty, //Execute at 30% HP + Force, //Force burst actions regardless of conditions + Hold //Conserve resources and cooldowns + } + + public enum GuardStrategy + { + Automatic, //Automatically execute based on conditions + Seventy, //Execute at 70% HP + Fifty, //Execute at 50% HP + Thirty, //Execute at 30% HP + Force, //Force burst actions regardless of conditions + Hold //Conserve resources and cooldowns + } + + public enum DefensiveStrategy + { + Automatic, //Automatically decide when to use Defensive abilities + Force, //Force the use of Defensive abilities regardless of conditions + Delay //Delay the use of Defensive abilities for strategic reasons + } + + #endregion + + public static RotationModuleDefinition Definition() + { + var res = new RotationModuleDefinition("PvP: Utility", "PvP Rotation Module", "Utility Actions (PvP)", "Akechi", RotationModuleQuality.Excellent, + BitMask.Build( + Class.PLD, Class.WAR, Class.DRK, Class.GNB, //Tank + Class.WHM, Class.SCH, Class.AST, Class.SGE, //Healer + Class.MNK, Class.DRG, Class.NIN, Class.SAM, Class.RPR, Class.VPR, //Melee + Class.BRD, Class.MCH, Class.DNC, //Ranged + Class.BLM, Class.SMN, Class.RDM, Class.PCT), 30); //Caster + + res.Define(Track.Elixir).As("Elixir", uiPriority: 150) + .AddOption(ElixirStrategy.Automatic, "Automatic", "Use normally") + .AddOption(ElixirStrategy.Close, "Close", "Use when target is close (within 10y or further)") + .AddOption(ElixirStrategy.Mid, "Mid", "Use when target is mid-range (within 20y or further)") + .AddOption(ElixirStrategy.Far, "Far", "Use when target is far (within 30y or further)") + .AddOption(ElixirStrategy.Force, "Force", "Force") + .AddOption(ElixirStrategy.Hold, "Hold", "Hold") + .AddAssociatedActions(AID.Elixir); + res.Define(Track.Recuperate).As("Recuperate", uiPriority: 150) + .AddOption(RecuperateStrategy.Automatic, "Automatic", "Use normally") + .AddOption(RecuperateStrategy.Seventy, "Seventy", "Use at 70% HP") + .AddOption(RecuperateStrategy.Fifty, "Fifty", "Use at 50% HP") + .AddOption(RecuperateStrategy.Thirty, "Thirty", "Use at 30% HP") + .AddOption(RecuperateStrategy.Force, "Force", "Force") + .AddOption(RecuperateStrategy.Hold, "Hold", "Hold") + .AddAssociatedActions(AID.Recuperate); + res.Define(Track.Guard).As("Guard", uiPriority: 150) + .AddOption(GuardStrategy.Automatic, "Automatic", "Use normally") + .AddOption(GuardStrategy.Seventy, "Seventy", "Use at 70% HP") + .AddOption(GuardStrategy.Fifty, "Fifty", "Use at 50% HP") + .AddOption(GuardStrategy.Thirty, "Thirty", "Use at 30% HP") + .AddOption(GuardStrategy.Force, "Force", "Force") + .AddOption(GuardStrategy.Hold, "Hold", "Hold") + .AddAssociatedActions(AID.Guard); + res.Define(Track.Purify).As("Purify", uiPriority: 150) + .AddOption(DefensiveStrategy.Automatic, "Automatic", "Use normally") + .AddOption(DefensiveStrategy.Force, "Force", "Force") + .AddOption(DefensiveStrategy.Delay, "Delay", "Delay") + .AddAssociatedActions(AID.Purify); + res.Define(Track.Sprint).As("Sprint", uiPriority: 150) + .AddOption(DefensiveStrategy.Automatic, "Automatic", "Use normally") + .AddOption(DefensiveStrategy.Force, "Force", "Force") + .AddOption(DefensiveStrategy.Delay, "Delay", "Delay") + .AddAssociatedActions(AID.Sprint); + return res; + + } + + #region Priorities + //Priority for GCDs used + public enum GCDPriority + { + None = 0, + Elixir = 500, + ForcedGCD = 900, + } + //Priority for oGCDs used + public enum OGCDPriority + { + None = 0, + Sprint = 300, + Recuperate = 400, + Guard = 600, + Purify = 700, + ForcedOGCD = 900, + } + #endregion + + #region Placeholders for Variables + //Cooldown Related + private bool hasSprint; //Checks if Sprint is active + private bool canElixir; //can Keen Edge + private bool canRecuperate; //can Brutal Shell + private bool canGuard; //can Solid Barrel + private bool canPurify; //can Burst Strike + private bool canSprint; //can Gnashing Fang + + //Misc + public float GCDLength; //Current GCD length, adjusted by skill speed/haste (2.5s baseline) + public AID NextGCD; //Next global cooldown action to be used (needed for cartridge management) + private GCDPriority NextGCDPrio; //Priority of the next GCD, used for decision making on cooldowns + #endregion + + #region Module Helpers + private bool In10y(Actor? target) => Player.DistanceToHitbox(target) <= 9.9; //Check if the target is within 10 yalms + private bool In20y(Actor? target) => Player.DistanceToHitbox(target) <= 19.9; //Check if the target is within 20 yalms + private bool In30y(Actor? target) => Player.DistanceToHitbox(target) <= 29.9; //Check if the target is within 30 yalms + private bool IsOffCooldown(AID aid) => World.Client.Cooldowns[ActionDefinitions.Instance.Spell(aid)!.MainCooldownGroup].Remaining < 0.6f; //Check if the desired action is ready (cooldown less than 0.6 seconds) + public bool HasEffect(SID sid) => SelfStatusLeft(sid) > 0; //Check if the player has the specified status effect + public bool TargetHasEffect(SID sid, Actor? target) => StatusDetails(target, sid, Player.InstanceID, 1000).Left > 0; //Check if the target has the specified status effect + #endregion + public float DebuffsLeft(Actor? target) + { + return target == null ? 0f + : Utils.MaxAll( + StatusDetails(target, SID.Silence, Player.InstanceID, 5).Left, + StatusDetails(target, SID.Stun, Player.InstanceID, 5).Left, + StatusDetails(target, SID.Bind, Player.InstanceID, 5).Left, + StatusDetails(target, SID.Heavy, Player.InstanceID, 5).Left, + StatusDetails(target, SID.Sleep, Player.InstanceID, 5).Left, + StatusDetails(target, SID.HalfAsleep, Player.InstanceID, 5).Left + ); + } + public bool HasAnyDebuff(Actor? target) => DebuffsLeft(target) > 0; + + public override void Execute(StrategyValues strategy, Actor? primaryTarget, float estimatedAnimLockDelay, bool isMoving) //Executes our actions + { + #region Variables + hasSprint = HasEffect(SID.SprintPvP); //Checks if Sprint is active + + #region Minimal Requirements + canElixir = IsOffCooldown(AID.Elixir) && strategy.Option(Track.Elixir).As() != ElixirStrategy.Hold; + canRecuperate = Player.HPMP.CurMP >= 2500 && strategy.Option(Track.Recuperate).As() != RecuperateStrategy.Hold; + canGuard = IsOffCooldown(AID.Guard) && strategy.Option(Track.Guard).As() != GuardStrategy.Hold; + canPurify = IsOffCooldown(AID.Purify) && strategy.Option(Track.Purify).As() != DefensiveStrategy.Delay; + canSprint = !hasSprint && strategy.Option(Track.Sprint).As() != DefensiveStrategy.Delay; + #endregion + #endregion + + //Elixir execution + var elixirStrat = strategy.Option(Track.Elixir).As(); + if (ShouldUseElixir(elixirStrat, primaryTarget)) + QueueGCD(AID.Elixir, Player, elixirStrat == ElixirStrategy.Force ? GCDPriority.ForcedGCD : GCDPriority.Elixir); + + //Recuperate execution + var recuperateStrat = strategy.Option(Track.Recuperate).As(); + if (ShouldUseRecuperate(recuperateStrat)) + QueueOGCD(AID.Recuperate, Player, recuperateStrat == RecuperateStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.Recuperate); + + //Guard execution + var guardStrat = strategy.Option(Track.Guard).As(); + if (ShouldUseGuard(guardStrat, primaryTarget)) + QueueOGCD(AID.Guard, Player, guardStrat == GuardStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.Guard); + + //Purify execution + var purifyStrat = strategy.Option(Track.Purify).As(); + if (ShouldUsePurify(purifyStrat, primaryTarget)) + QueueOGCD(AID.Purify, Player, purifyStrat == DefensiveStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.Purify); + + //Sprint execution + var sprintStrat = strategy.Option(Track.Sprint).As(); + if (ShouldUseSprint(sprintStrat, primaryTarget)) + QueueOGCD(AID.SprintPvP, Player, sprintStrat == DefensiveStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.Sprint); + } + + #region Core Execution Helpers + //QueueGCD execution + private void QueueGCD(AID aid, Actor? target, GCDPriority prio) + { + if (prio != GCDPriority.None) + { + Hints.ActionsToExecute.Push(ActionID.MakeSpell(aid), target, ActionQueue.Priority.High + (int)prio); + if (prio > NextGCDPrio) + { + NextGCD = aid; + NextGCDPrio = prio; + } + } + } + //QueueOGCD execution + private void QueueOGCD(AID aid, Actor? target, OGCDPriority prio, float basePrio = ActionQueue.Priority.Medium) + { + if (prio != OGCDPriority.None) + { + Hints.ActionsToExecute.Push(ActionID.MakeSpell(aid), target, basePrio + (int)prio); + } + } + #endregion + + public bool ShouldUseElixir(ElixirStrategy strategy, Actor? target) => strategy switch + { + ElixirStrategy.Automatic => + canElixir && + Player.HPMP.CurHP <= 2500 && + (In20y(target) || target != null), + ElixirStrategy.Close => Player.HPMP.CurHP <= 4000 && In10y(target), + ElixirStrategy.Mid => Player.HPMP.CurHP <= 4000 && In20y(target), + ElixirStrategy.Far => Player.HPMP.CurHP <= 4000 && In30y(target), + ElixirStrategy.Force => canElixir, + ElixirStrategy.Hold => false, + _ => false, + }; + + public bool ShouldUseRecuperate(RecuperateStrategy strategy) => strategy switch + { + RecuperateStrategy.Automatic => + canRecuperate && + Player.HPMP.CurHP <= 4000, + RecuperateStrategy.Seventy => canRecuperate && Player.HPMP.CurHP <= 7000, + RecuperateStrategy.Fifty => canRecuperate && Player.HPMP.CurHP <= 5000, + RecuperateStrategy.Thirty => canRecuperate && Player.HPMP.CurHP <= 3000, + RecuperateStrategy.Force => canRecuperate, + RecuperateStrategy.Hold => false, + _ => false, + }; + + public bool ShouldUseGuard(GuardStrategy strategy, Actor? target) => strategy switch + { + GuardStrategy.Automatic => + canGuard && + Player.HPMP.CurHP <= 3500, + GuardStrategy.Seventy => canGuard && Player.HPMP.CurHP <= 7000, + GuardStrategy.Fifty => canGuard && Player.HPMP.CurHP <= 5000, + GuardStrategy.Thirty => canGuard && Player.HPMP.CurHP <= 3000, + GuardStrategy.Force => canGuard, + GuardStrategy.Hold => false, + _ => false, + }; + + public bool ShouldUsePurify(DefensiveStrategy strategy, Actor? target) => strategy switch + { + DefensiveStrategy.Automatic => + canPurify && + HasAnyDebuff(target), + DefensiveStrategy.Force => canPurify, + DefensiveStrategy.Delay => false, + _ => false, + }; + + public bool ShouldUseSprint(DefensiveStrategy strategy, Actor? target) => strategy switch + { + DefensiveStrategy.Automatic => + !Player.InCombat && + canSprint, + DefensiveStrategy.Force => true, + DefensiveStrategy.Delay => false, + _ => false, + }; +} diff --git a/BossMod/Autorotation/akechi/AkechiDRG.cs b/BossMod/Autorotation/akechi/PvE/DRG.cs similarity index 74% rename from BossMod/Autorotation/akechi/AkechiDRG.cs rename to BossMod/Autorotation/akechi/PvE/DRG.cs index 80f7c527f0..aae0321cf7 100644 --- a/BossMod/Autorotation/akechi/AkechiDRG.cs +++ b/BossMod/Autorotation/akechi/PvE/DRG.cs @@ -1,10 +1,12 @@ using FFXIVClientStructs.FFXIV.Client.Game.Gauge; +using AID = BossMod.DRG.AID; +using SID = BossMod.DRG.SID; -namespace BossMod.Autorotation.akechi; +namespace BossMod.Autorotation.akechi.PvE; //Contribution by Akechi //Discord: @akechdz or 'Akechi' on Puni.sh for maintenance -public sealed class AkechiDRG(RotationModuleManager manager, Actor player) : RotationModule(manager, player) +public sealed class DRG(RotationModuleManager manager, Actor player) : RotationModule(manager, player) { #region Enums: Abilities / Strategies @@ -171,7 +173,7 @@ public static RotationModuleDefinition Definition() .AddOption(SurgeStrategy.Force, "Force", "Force Life Surge usage", 40, 5, ActionTargets.Hostile, 6, 87) .AddOption(SurgeStrategy.ForceEX, "ForceEX", "Force Life Surge (2 charges)", 40, 5, ActionTargets.Hostile, 88) //2 charges .AddOption(SurgeStrategy.Delay, "Delay", "Delay the use of Life Surge", 0, 0, ActionTargets.None, 6) - .AddAssociatedActions(DRG.AID.LifeSurge); + .AddAssociatedActions(AID.LifeSurge); //Jump strategy res.Define(Track.Jump).As("Jump", uiPriority: 110) @@ -180,7 +182,7 @@ public static RotationModuleDefinition Definition() .AddOption(JumpStrategy.ForceEX, "Force Jump (EX)", "Force Jump usage (Grants Dive Ready buff)", 30, 15, ActionTargets.Self, 68, 74) .AddOption(JumpStrategy.ForceEX2, "Force High Jump", "Force High Jump usage", 30, 15, ActionTargets.Self, 75) .AddOption(JumpStrategy.Delay, "Delay", "Delay Jump usage", 0, 0, ActionTargets.None, 30) - .AddAssociatedActions(DRG.AID.Jump, DRG.AID.HighJump); + .AddAssociatedActions(AID.Jump, AID.HighJump); //Dragonfire Dive strategy res.Define(Track.DragonfireDive).As("Dragonfire Dive", "D.Dive", uiPriority: 150) @@ -188,7 +190,7 @@ public static RotationModuleDefinition Definition() .AddOption(DragonfireStrategy.Force, "Force", "Force Dragonfire Dive usage", 120, 0, ActionTargets.Hostile, 50, 91) .AddOption(DragonfireStrategy.ForceEX, "ForceEX", "Force Dragonfire Dive (Grants Dragon's Flight)", 120, 30, ActionTargets.Hostile, 92) .AddOption(DragonfireStrategy.Delay, "Delay", "Delay Dragonfire Dive usage", 0, 0, ActionTargets.None, 50) - .AddAssociatedActions(DRG.AID.DragonfireDive); + .AddAssociatedActions(AID.DragonfireDive); //Geirskogul strategy res.Define(Track.Geirskogul).As("Geirskogul", "Geirs.", uiPriority: 130) @@ -196,7 +198,7 @@ public static RotationModuleDefinition Definition() .AddOption(GeirskogulStrategy.Force, "Force", "Force Geirskogul usage", 60, 0, ActionTargets.Hostile, 60, 69) .AddOption(GeirskogulStrategy.ForceEX, "ForceEX", "Force Geirskogul (Grants Life of the Dragon & 3x Nastrond)", 60, 20, ActionTargets.Hostile, 70) .AddOption(GeirskogulStrategy.Delay, "Delay", "Delay Geirskogul usage", 0, 0, ActionTargets.None, 60) - .AddAssociatedActions(DRG.AID.Geirskogul); + .AddAssociatedActions(AID.Geirskogul); //Stardiver strategy res.Define(Track.Stardiver).As("Stardiver", "S.diver", uiPriority: 140) @@ -204,14 +206,14 @@ public static RotationModuleDefinition Definition() .AddOption(StardiverStrategy.Force, "Force", "Force Stardiver usage", 30, 0, ActionTargets.Hostile, 80, 99) .AddOption(StardiverStrategy.ForceEX, "ForceEX", "Force Stardiver (Grants Starcross Ready)", 30, 0, ActionTargets.Hostile, 100) .AddOption(StardiverStrategy.Delay, "Delay", "Delay Stardiver usage", 0, 0, ActionTargets.None, 80) - .AddAssociatedActions(DRG.AID.Stardiver); + .AddAssociatedActions(AID.Stardiver); //Piercing Talon strategy res.Define(Track.PiercingTalon).As("Piercing Talon", "Talon", uiPriority: 20) .AddOption(PiercingTalonStrategy.Forbid, "Forbid", "Forbid use of Piercing Talon") .AddOption(PiercingTalonStrategy.Allow, "Allow", "Allow use of Piercing Talon only if already in combat & outside melee range") .AddOption(PiercingTalonStrategy.Force, "Force", "Force Piercing Talon usage ASAP (even in melee range)") - .AddAssociatedActions(DRG.AID.PiercingTalon); + .AddAssociatedActions(AID.PiercingTalon); //True North strategy res.Define(Track.TrueNorth).As("True North", "T.North", uiPriority: 10) @@ -231,49 +233,49 @@ public static RotationModuleDefinition Definition() .AddOption(OffensiveStrategy.Automatic, "Automatic", "Use Lance Charge normally") .AddOption(OffensiveStrategy.Force, "Force", "Force Lance Charge usage ASAP (even during downtime)", 60, 20, ActionTargets.Self, 30) .AddOption(OffensiveStrategy.Delay, "Delay", "Delay Lance Charge usage", 0, 0, ActionTargets.None, 30) - .AddAssociatedActions(DRG.AID.LanceCharge); + .AddAssociatedActions(AID.LanceCharge); //Battle Litany strategy res.Define(Track.BattleLitany).As("Battle Litany", "B.Litany", uiPriority: 170) .AddOption(OffensiveStrategy.Automatic, "Automatic", "Use Battle Litany normally") .AddOption(OffensiveStrategy.Force, "Force", "Force Battle Litany usage ASAP (even during downtime)", 120, 20, ActionTargets.Self, 52) .AddOption(OffensiveStrategy.Delay, "Delay", "Delay Battle Litany usage", 0, 0, ActionTargets.None, 52) - .AddAssociatedActions(DRG.AID.BattleLitany); + .AddAssociatedActions(AID.BattleLitany); //Mirage Dive strategy res.Define(Track.MirageDive).As("Mirage Dive", "M.Dive", uiPriority: 105) .AddOption(OffensiveStrategy.Automatic, "Automatic", "Use Mirage Dive normally") .AddOption(OffensiveStrategy.Force, "Force", "Force Mirage Dive usage", 0, 0, ActionTargets.Hostile, 68) .AddOption(OffensiveStrategy.Delay, "Delay", "Delay Mirage Dive usage", 0, 0, ActionTargets.None, 68) - .AddAssociatedActions(DRG.AID.MirageDive); + .AddAssociatedActions(AID.MirageDive); //Nastrond strategy res.Define(Track.Nastrond).As("Nastrond", "Nast.", uiPriority: 125) .AddOption(OffensiveStrategy.Automatic, "Automatic", "Use Nastrond normally") .AddOption(OffensiveStrategy.Force, "Force", "Force Nastrond usage", 0, 2, ActionTargets.Hostile, 70) .AddOption(OffensiveStrategy.Delay, "Delay", "Delay Nastrond usage", 0, 0, ActionTargets.None, 70) - .AddAssociatedActions(DRG.AID.Nastrond); + .AddAssociatedActions(AID.Nastrond); //Wyrmwind Thrust strategy res.Define(Track.WyrmwindThrust).As("Wyrmwind Thrust", "W.Thrust", uiPriority: 120) .AddOption(OffensiveStrategy.Automatic, "Automatic", "Use Wyrmwind Thrust normally") .AddOption(OffensiveStrategy.Force, "Force", "Force Wyrmwind Thrust usage ASAP", 0, 10, ActionTargets.Hostile, 90) .AddOption(OffensiveStrategy.Delay, "Delay", "Delay Wyrmwind Thrust usage", 0, 0, ActionTargets.None, 90) - .AddAssociatedActions(DRG.AID.WyrmwindThrust); + .AddAssociatedActions(AID.WyrmwindThrust); //Rise Of The Dragon strategy res.Define(Track.RiseOfTheDragon).As("Rise Of The Dragon", "RotD", uiPriority: 145) .AddOption(OffensiveStrategy.Automatic, "Automatic", "Use Rise Of The Dragon normally") .AddOption(OffensiveStrategy.Force, "Force", "Force Rise Of The Dragon usage", 0, 0, ActionTargets.Hostile, 92) .AddOption(OffensiveStrategy.Delay, "Delay", "Delay Rise Of The Dragon usage", 0, 0, ActionTargets.None, 92) - .AddAssociatedActions(DRG.AID.RiseOfTheDragon); + .AddAssociatedActions(AID.RiseOfTheDragon); //Starcross strategy res.Define(Track.Starcross).As("Starcross", "S.cross", uiPriority: 135) .AddOption(OffensiveStrategy.Automatic, "Automatic", "Use Starcross normally") .AddOption(OffensiveStrategy.Force, "Force", "Force Starcross usage", 0, 0, ActionTargets.Self, 100) .AddOption(OffensiveStrategy.Delay, "Delay", "Delay Starcross usage", 0, 0, ActionTargets.None, 100) - .AddAssociatedActions(DRG.AID.Starcross); + .AddAssociatedActions(AID.Starcross); #endregion @@ -353,7 +355,7 @@ public enum OGCDPriority private bool canROTD; //Ability to use Rise of the Dragon private bool canSC; //Ability to use Starcross - public DRG.AID NextGCD; //Next global cooldown action to be used + public AID NextGCD; //Next global cooldown action to be used private GCDPriority NextGCDPrio; //Priority of the next GCD for cooldown decision making #endregion @@ -361,13 +363,13 @@ public enum OGCDPriority #region Module Helpers //Check if the desired ability is unlocked - private bool Unlocked(DRG.AID aid) => ActionUnlocked(ActionID.MakeSpell(aid)); + private bool Unlocked(AID aid) => ActionUnlocked(ActionID.MakeSpell(aid)); //Get remaining cooldown time for the specified action - private float CD(DRG.AID aid) => World.Client.Cooldowns[ActionDefinitions.Instance.Spell(aid)!.MainCooldownGroup].Remaining; + private float CD(AID aid) => World.Client.Cooldowns[ActionDefinitions.Instance.Spell(aid)!.MainCooldownGroup].Remaining; //Get the last action used in the combo sequence - private DRG.AID ComboLastMove => (DRG.AID)World.Client.ComboState.Action; + private AID ComboLastMove => (AID)World.Client.ComboState.Action; //Check if the target is within melee range (3 yalms) private bool In3y(Actor? target) => Player.DistanceToHitbox(target) <= 3; @@ -376,7 +378,7 @@ public enum OGCDPriority private bool In15y(Actor? target) => Player.DistanceToHitbox(target) <= 14.75; //Check if the desired action is ready (cooldown < 0.6 seconds) - private bool ActionReady(DRG.AID aid) => World.Client.Cooldowns[ActionDefinitions.Instance.Spell(aid)!.MainCooldownGroup].Remaining < 0.6f; + private bool ActionReady(AID aid) => World.Client.Cooldowns[ActionDefinitions.Instance.Spell(aid)!.MainCooldownGroup].Remaining < 0.6f; //Check if the potion should be used before raid buffs expire private bool IsPotionBeforeRaidbuffs() => RaidBuffsLeft == 0 && PotionLeft > RaidBuffsIn + 17.5f; @@ -434,33 +436,33 @@ public override void Execute(StrategyValues strategy, Actor? primaryTarget, floa hasLOTD = gauge.LotdTimer > 0; //Check if Life of the Dragon (LOTD) is active //Cooldown Checks - lcCD = CD(DRG.AID.LanceCharge); //Get cooldown for Lance Charge - lcLeft = SelfStatusLeft(DRG.SID.LanceCharge, 20); //Get remaining time for Lance Charge effect - powerLeft = SelfStatusLeft(DRG.SID.PowerSurge, 30); //Get remaining time for Power Surge effect - chaosLeft = MathF.Max(StatusDetails(primaryTarget, DRG.SID.ChaosThrust, Player.InstanceID).Left, - StatusDetails(primaryTarget, DRG.SID.ChaoticSpring, Player.InstanceID).Left); //Get max remaining time for Chaos Thrust or Chaotic Spring - blCD = CD(DRG.AID.BattleLitany); //Get cooldown for Battle Litany + lcCD = CD(AID.LanceCharge); //Get cooldown for Lance Charge + lcLeft = SelfStatusLeft(SID.LanceCharge, 20); //Get remaining time for Lance Charge effect + powerLeft = SelfStatusLeft(SID.PowerSurge, 30); //Get remaining time for Power Surge effect + chaosLeft = MathF.Max(StatusDetails(primaryTarget, SID.ChaosThrust, Player.InstanceID).Left, + StatusDetails(primaryTarget, SID.ChaoticSpring, Player.InstanceID).Left); //Get max remaining time for Chaos Thrust or Chaotic Spring + blCD = CD(AID.BattleLitany); //Get cooldown for Battle Litany GCDLength = ActionSpeed.GCDRounded(World.Client.PlayerStats.SkillSpeed, World.Client.PlayerStats.Haste, Player.Level); //Calculate rounded global cooldown (GCD) duration - hasMD = HasEffect(DRG.SID.DiveReady); //Check if Mirage Dive is ready - hasNastrond = HasEffect(DRG.SID.NastrondReady); //Check if Nastrond is ready + hasMD = HasEffect(SID.DiveReady); //Check if Mirage Dive is ready + hasNastrond = HasEffect(SID.NastrondReady); //Check if Nastrond is ready hasLC = lcCD is >= 40 and <= 60; //Check if Lance Charge is within cooldown range hasBL = blCD is >= 100 and <= 120; //Check if Battle Litany is within cooldown range - hasDF = HasEffect(DRG.SID.DragonsFlight); //Check if Dragon's Flight effect is active - hasSC = HasEffect(DRG.SID.StarcrossReady); //Check if Starcross is ready + hasDF = HasEffect(SID.DragonsFlight); //Check if Dragon's Flight effect is active + hasSC = HasEffect(SID.StarcrossReady); //Check if Starcross is ready //Minimum Conditions - canLC = Unlocked(DRG.AID.LanceCharge) && ActionReady(DRG.AID.LanceCharge); //minimum condition(s) to execute Lance Charge - canBL = Unlocked(DRG.AID.BattleLitany) && ActionReady(DRG.AID.BattleLitany); //minimum condition(s) to execute Battle Litany - canLS = Unlocked(DRG.AID.LifeSurge); //minimum condition(s) to execute Life Surge - canJump = Unlocked(DRG.AID.Jump) && ActionReady(DRG.AID.Jump); //minimum condition(s) to execute Jump - canDD = Unlocked(DRG.AID.DragonfireDive) && ActionReady(DRG.AID.DragonfireDive); //minimum condition(s) to execute Dragonfire Dive - canGeirskogul = Unlocked(DRG.AID.Geirskogul) && ActionReady(DRG.AID.Geirskogul); //minimum condition(s) to execute Geirskogul - canMD = Unlocked(DRG.AID.MirageDive) && hasMD; //minimum condition(s) to execute Mirage Dive - canNastrond = Unlocked(DRG.AID.Nastrond) && hasNastrond; //minimum condition(s) to execute Nastrond - canSD = Unlocked(DRG.AID.Stardiver) && ActionReady(DRG.AID.Stardiver); //minimum condition(s) to execute Stardiver - canWT = Unlocked(DRG.AID.WyrmwindThrust) && ActionReady(DRG.AID.WyrmwindThrust); //minimum condition(s) to execute Wyrmwind Thrust - canROTD = Unlocked(DRG.AID.RiseOfTheDragon) && hasDF; //minimum condition(s) to execute Rise of the Dragon - canSC = Unlocked(DRG.AID.Starcross) && hasSC; //minimum condition(s) to execute Starcross + canLC = Unlocked(AID.LanceCharge) && ActionReady(AID.LanceCharge); //minimum condition(s) to execute Lance Charge + canBL = Unlocked(AID.BattleLitany) && ActionReady(AID.BattleLitany); //minimum condition(s) to execute Battle Litany + canLS = Unlocked(AID.LifeSurge); //minimum condition(s) to execute Life Surge + canJump = Unlocked(AID.Jump) && ActionReady(AID.Jump); //minimum condition(s) to execute Jump + canDD = Unlocked(AID.DragonfireDive) && ActionReady(AID.DragonfireDive); //minimum condition(s) to execute Dragonfire Dive + canGeirskogul = Unlocked(AID.Geirskogul) && ActionReady(AID.Geirskogul); //minimum condition(s) to execute Geirskogul + canMD = Unlocked(AID.MirageDive) && hasMD; //minimum condition(s) to execute Mirage Dive + canNastrond = Unlocked(AID.Nastrond) && hasNastrond; //minimum condition(s) to execute Nastrond + canSD = Unlocked(AID.Stardiver) && ActionReady(AID.Stardiver); //minimum condition(s) to execute Stardiver + canWT = Unlocked(AID.WyrmwindThrust) && ActionReady(AID.WyrmwindThrust); //minimum condition(s) to execute Wyrmwind Thrust + canROTD = Unlocked(AID.RiseOfTheDragon) && hasDF; //minimum condition(s) to execute Rise of the Dragon + canSC = Unlocked(AID.Starcross) && hasSC; //minimum condition(s) to execute Starcross #endregion @@ -468,7 +470,7 @@ public override void Execute(StrategyValues strategy, Actor? primaryTarget, floa downtimeIn = Manager.Planner?.EstimateTimeToNextDowntime().Item2 ?? float.MaxValue; //Estimate downtime until next action PotionLeft = PotionStatusLeft(); //Get remaining potion status (RaidBuffsLeft, RaidBuffsIn) = EstimateRaidBuffTimings(primaryTarget); //Estimate remaining raid buffs - NextGCD = DRG.AID.None; //Set next GCD ability + NextGCD = AID.None; //Set next GCD ability NextGCDPrio = GCDPriority.None; //Set next GCD priority #endregion @@ -513,12 +515,12 @@ public override void Execute(StrategyValues strategy, Actor? primaryTarget, floa #region Targeting //Check if Doom Spike is unlocked; if so, determine the best AOE target and count - var (AOEBestTarget, AOETargetCount) = Unlocked(DRG.AID.DoomSpike) + var (AOEBestTarget, AOETargetCount) = Unlocked(AID.DoomSpike) ? CheckAOETargeting(AOEStrategy, primaryTarget, 10, NumTargetsHitByAOE, IsHitByAOE) : (null, 0); //Set to null and count 0 if not unlocked //Check if Geirskogul is unlocked; if so, determine the best spear target and count - var (SpearBestTarget, SpearTargetCount) = Unlocked(DRG.AID.Geirskogul) + var (SpearBestTarget, SpearTargetCount) = Unlocked(AID.Geirskogul) ? CheckAOETargeting(AOEStrategy, primaryTarget, 15, NumTargetsHitBySpear, IsHitBySpear) : (null, 0); //Set to null and count 0 if not unlocked @@ -543,67 +545,67 @@ public override void Execute(StrategyValues strategy, Actor? primaryTarget, floa //Execute Lance Charge if available var lcStrat = strategy.Option(Track.LanceCharge).As(); if (!hold && ShouldUseLanceCharge(lcStrat, primaryTarget)) - QueueOGCD(DRG.AID.LanceCharge, Player, lcStrat is OffensiveStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.Buffs); + QueueOGCD(AID.LanceCharge, Player, lcStrat is OffensiveStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.Buffs); //Execute Battle Litany if available var blStrat = strategy.Option(Track.BattleLitany).As(); if (!hold && ShouldUseBattleLitany(blStrat, primaryTarget)) - QueueOGCD(DRG.AID.BattleLitany, Player, blStrat is OffensiveStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.Buffs); + QueueOGCD(AID.BattleLitany, Player, blStrat is OffensiveStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.Buffs); //Execute Life Surge if conditions met var lsStrat = strategy.Option(Track.LifeSurge).As(); if (!hold && ShouldUseLifeSurge(lsStrat, primaryTarget)) - QueueOGCD(DRG.AID.LifeSurge, Player, lsStrat is SurgeStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.Buffs); + QueueOGCD(AID.LifeSurge, Player, lsStrat is SurgeStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.Buffs); //Execute Jump ability if available var jumpStrat = strategy.Option(Track.Jump).As(); if (!hold && ShouldUseJump(jumpStrat, primaryTarget)) - QueueOGCD(Unlocked(DRG.AID.HighJump) ? DRG.AID.HighJump : DRG.AID.Jump, primaryTarget, jumpStrat == JumpStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.Jump); + QueueOGCD(Unlocked(AID.HighJump) ? AID.HighJump : AID.Jump, primaryTarget, jumpStrat == JumpStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.Jump); //Execute Dragonfire Dive if available var ddStrat = strategy.Option(Track.DragonfireDive).As(); if (!hold && ShouldUseDragonfireDive(ddStrat, primaryTarget)) - QueueOGCD(DRG.AID.DragonfireDive, primaryTarget, ddStrat is DragonfireStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.DragonfireDive); + QueueOGCD(AID.DragonfireDive, primaryTarget, ddStrat is DragonfireStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.DragonfireDive); //Execute Geirskogul if available var geirskogul = strategy.Option(Track.Geirskogul).As(); if (!hold && ShouldUseGeirskogul(geirskogul, primaryTarget)) - QueueOGCD(DRG.AID.Geirskogul, bestSpeartarget, geirskogul == GeirskogulStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.Geirskogul); + QueueOGCD(AID.Geirskogul, bestSpeartarget, geirskogul == GeirskogulStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.Geirskogul); //Execute Mirage Dive if available var mirageStrat = strategy.Option(Track.MirageDive).As(); if (!hold && ShouldUseMirageDive(mirageStrat, primaryTarget)) - QueueOGCD(DRG.AID.MirageDive, primaryTarget, mirageStrat == OffensiveStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.MirageDive); + QueueOGCD(AID.MirageDive, primaryTarget, mirageStrat == OffensiveStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.MirageDive); //Execute Nastrond if available var nastrondStrat = strategy.Option(Track.Nastrond).As(); if (!hold && ShouldUseNastrond(nastrondStrat, primaryTarget)) - QueueOGCD(DRG.AID.Nastrond, bestSpeartarget, nastrondStrat == OffensiveStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.Nastrond); + QueueOGCD(AID.Nastrond, bestSpeartarget, nastrondStrat == OffensiveStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.Nastrond); //Execute Stardiver if available var sdStrat = strategy.Option(Track.Stardiver).As(); if (!hold && ShouldUseStardiver(sdStrat, primaryTarget)) - QueueOGCD(DRG.AID.Stardiver, primaryTarget, sdStrat == StardiverStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.Stardiver); + QueueOGCD(AID.Stardiver, primaryTarget, sdStrat == StardiverStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.Stardiver); //Execute Wyrmwind Thrust if available var wtStrat = strategy.Option(Track.WyrmwindThrust).As(); if (!hold && ShouldUseWyrmwindThrust(wtStrat, primaryTarget)) - QueueOGCD(DRG.AID.WyrmwindThrust, bestSpeartarget, wtStrat == OffensiveStrategy.Force ? OGCDPriority.ForcedOGCD : (HasEffect(DRG.SID.LanceCharge) ? OGCDPriority.WyrmwindThrustOpti : OGCDPriority.WyrmwindThrust)); + QueueOGCD(AID.WyrmwindThrust, bestSpeartarget, wtStrat == OffensiveStrategy.Force ? OGCDPriority.ForcedOGCD : HasEffect(SID.LanceCharge) ? OGCDPriority.WyrmwindThrustOpti : OGCDPriority.WyrmwindThrust); //Execute Rise of the Dragon if available var riseStrat = strategy.Option(Track.RiseOfTheDragon).As(); if (!hold && ShouldUseRiseOfTheDragon(riseStrat, primaryTarget)) - QueueOGCD(DRG.AID.RiseOfTheDragon, primaryTarget, riseStrat == OffensiveStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.Buffs); + QueueOGCD(AID.RiseOfTheDragon, primaryTarget, riseStrat == OffensiveStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.Buffs); //Execute Starcross if available var crossStrat = strategy.Option(Track.Starcross).As(); if (!hold && ShouldUseStarcross(crossStrat, primaryTarget)) - QueueOGCD(DRG.AID.Starcross, primaryTarget, crossStrat == OffensiveStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.Starcross); + QueueOGCD(AID.Starcross, primaryTarget, crossStrat == OffensiveStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.Starcross); //Execute Piercing Talon if available var ptStrat = strategy.Option(Track.PiercingTalon).As(); if (ShouldUsePiercingTalon(primaryTarget, ptStrat)) - QueueGCD(DRG.AID.PiercingTalon, primaryTarget, ptStrat == PiercingTalonStrategy.Force ? GCDPriority.ForcedGCD : GCDPriority.NormalGCD); + QueueGCD(AID.PiercingTalon, primaryTarget, ptStrat == PiercingTalonStrategy.Force ? GCDPriority.ForcedGCD : GCDPriority.NormalGCD); //Execute Potion if available if (ShouldUsePotion(strategy.Option(Track.Potion).As())) @@ -611,7 +613,7 @@ public override void Execute(StrategyValues strategy, Actor? primaryTarget, floa //Execute True North if available if (!hold && ShouldUseTrueNorth(strategy.Option(Track.TrueNorth).As(), primaryTarget)) - QueueOGCD(DRG.AID.TrueNorth, Player, OGCDPriority.TrueNorth); + QueueOGCD(AID.TrueNorth, Player, OGCDPriority.TrueNorth); #endregion @@ -636,39 +638,39 @@ public override void Execute(StrategyValues strategy, Actor? primaryTarget, floa #region Core Execution Helpers //Determine the effect application delay for specific abilities - private float EffectApplicationDelay(DRG.AID aid) => aid switch + private float EffectApplicationDelay(AID aid) => aid switch { - DRG.AID.ChaoticSpring => 0.45f, //Chaotic Spring delay - DRG.AID.HighJump => 0.49f, //High Jump delay - DRG.AID.CoerthanTorment => 0.49f, //Coerthan Torment delay - DRG.AID.BattleLitany => 0.62f, //Battle Litany delay - DRG.AID.LanceBarrage => 0.62f, //Lance Barrage delay - DRG.AID.FangAndClaw => 0.62f, //Fang and Claw delay - DRG.AID.RaidenThrust => 0.62f, //Raiden Thrust delay - DRG.AID.Geirskogul => 0.67f, //Geirskogul delay - DRG.AID.WheelingThrust => 0.67f, //Wheeling Thrust delay - DRG.AID.HeavensThrust => 0.71f, //Heavens Thrust delay - DRG.AID.DraconianFury => 0.76f, //Draconian Fury delay - DRG.AID.Nastrond => 0.76f, //Nastrond delay - DRG.AID.TrueThrust => 0.76f, //True Thrust delay - DRG.AID.DragonfireDive => 0.8f, //Dragonfire Dive delay - DRG.AID.MirageDive => 0.8f, //Mirage Dive delay - DRG.AID.SonicThrust => 0.8f, //Sonic Thrust delay - DRG.AID.PiercingTalon => 0.85f, //Piercing Talon delay - DRG.AID.Starcross => 0.98f, //Starcross delay - DRG.AID.VorpalThrust => 1.02f, //Vorpal Thrust delay - DRG.AID.RiseOfTheDragon => 1.16f, //Rise of the Dragon delay - DRG.AID.WyrmwindThrust => 1.2f, //Wyrmwind Thrust delay - DRG.AID.DoomSpike => 1.29f, //Doom Spike delay - DRG.AID.Stardiver => 1.29f, //Stardiver delay - DRG.AID.SpiralBlow => 1.38f, //Spiral Blow delay - DRG.AID.Disembowel => 1.65f, //Disembowel delay - DRG.AID.DragonsongDive => 2.23f, //Dragonsong Dive delay + AID.ChaoticSpring => 0.45f, //Chaotic Spring delay + AID.HighJump => 0.49f, //High Jump delay + AID.CoerthanTorment => 0.49f, //Coerthan Torment delay + AID.BattleLitany => 0.62f, //Battle Litany delay + AID.LanceBarrage => 0.62f, //Lance Barrage delay + AID.FangAndClaw => 0.62f, //Fang and Claw delay + AID.RaidenThrust => 0.62f, //Raiden Thrust delay + AID.Geirskogul => 0.67f, //Geirskogul delay + AID.WheelingThrust => 0.67f, //Wheeling Thrust delay + AID.HeavensThrust => 0.71f, //Heavens Thrust delay + AID.DraconianFury => 0.76f, //Draconian Fury delay + AID.Nastrond => 0.76f, //Nastrond delay + AID.TrueThrust => 0.76f, //True Thrust delay + AID.DragonfireDive => 0.8f, //Dragonfire Dive delay + AID.MirageDive => 0.8f, //Mirage Dive delay + AID.SonicThrust => 0.8f, //Sonic Thrust delay + AID.PiercingTalon => 0.85f, //Piercing Talon delay + AID.Starcross => 0.98f, //Starcross delay + AID.VorpalThrust => 1.02f, //Vorpal Thrust delay + AID.RiseOfTheDragon => 1.16f, //Rise of the Dragon delay + AID.WyrmwindThrust => 1.2f, //Wyrmwind Thrust delay + AID.DoomSpike => 1.29f, //Doom Spike delay + AID.Stardiver => 1.29f, //Stardiver delay + AID.SpiralBlow => 1.38f, //Spiral Blow delay + AID.Disembowel => 1.65f, //Disembowel delay + AID.DragonsongDive => 2.23f, //Dragonsong Dive delay _ => 0 //Default case for unknown abilities }; //Queue a global cooldown (GCD) action - private void QueueGCD(DRG.AID aid, Actor? target, GCDPriority prio) + private void QueueGCD(AID aid, Actor? target, GCDPriority prio) { if (prio != GCDPriority.None) //Check if priority is not None { @@ -691,7 +693,7 @@ private void QueueGCD(DRG.AID aid, Actor? target, GCDPriority prio) } //Queue an off-global cooldown (oGCD) action - private void QueueOGCD(DRG.AID aid, Actor? target, OGCDPriority prio, float basePrio = ActionQueue.Priority.Low) + private void QueueOGCD(AID aid, Actor? target, OGCDPriority prio, float basePrio = ActionQueue.Priority.Low) { if (prio != OGCDPriority.None) //Check if priority is not None { @@ -711,105 +713,105 @@ private void QueueOGCD(DRG.AID aid, Actor? target, OGCDPriority prio, float base #region Single-Target Helpers //Determines the next skill in the single-target (ST) combo chain based on the last used action. - private DRG.AID NextFullST() => ComboLastMove switch + private AID NextFullST() => ComboLastMove switch { //Starting combo with TrueThrust or RaidenThrust - DRG.AID.TrueThrust or DRG.AID.RaidenThrust => + AID.TrueThrust or AID.RaidenThrust => //If Disembowel is Unlocked and power is low or Chaotic Spring is 0, use Disembowel or SpiralBlow, else VorpalThrust or LanceBarrage - Unlocked(DRG.AID.Disembowel) && (powerLeft <= GCDLength * 6 || chaosLeft <= GCDLength * 4) - ? Unlocked(DRG.AID.SpiralBlow) ? DRG.AID.SpiralBlow : DRG.AID.Disembowel - : Unlocked(DRG.AID.LanceBarrage) ? DRG.AID.LanceBarrage : DRG.AID.VorpalThrust, + Unlocked(AID.Disembowel) && (powerLeft <= GCDLength * 6 || chaosLeft <= GCDLength * 4) + ? Unlocked(AID.SpiralBlow) ? AID.SpiralBlow : AID.Disembowel + : Unlocked(AID.LanceBarrage) ? AID.LanceBarrage : AID.VorpalThrust, //Follow-up after Disembowel or SpiralBlow - DRG.AID.Disembowel or DRG.AID.SpiralBlow => - Unlocked(DRG.AID.ChaoticSpring) ? DRG.AID.ChaoticSpring //Use ChaoticSpring if Unlocked - : Unlocked(DRG.AID.ChaosThrust) ? DRG.AID.ChaosThrust //Use ChaosThrust if Unlocked - : DRG.AID.TrueThrust, //Return to TrueThrust otherwise + AID.Disembowel or AID.SpiralBlow => + Unlocked(AID.ChaoticSpring) ? AID.ChaoticSpring //Use ChaoticSpring if Unlocked + : Unlocked(AID.ChaosThrust) ? AID.ChaosThrust //Use ChaosThrust if Unlocked + : AID.TrueThrust, //Return to TrueThrust otherwise //Follow-up after VorpalThrust or LanceBarrage - DRG.AID.VorpalThrust or DRG.AID.LanceBarrage => - Unlocked(DRG.AID.HeavensThrust) ? DRG.AID.HeavensThrust //Use HeavensThrust if Unlocked - : Unlocked(DRG.AID.FullThrust) ? DRG.AID.FullThrust //Use FullThrust if Unlocked - : DRG.AID.TrueThrust, //Return to TrueThrust otherwise + AID.VorpalThrust or AID.LanceBarrage => + Unlocked(AID.HeavensThrust) ? AID.HeavensThrust //Use HeavensThrust if Unlocked + : Unlocked(AID.FullThrust) ? AID.FullThrust //Use FullThrust if Unlocked + : AID.TrueThrust, //Return to TrueThrust otherwise //After FullThrust or HeavensThrust in the combo - DRG.AID.FullThrust or DRG.AID.HeavensThrust => - Unlocked(DRG.AID.FangAndClaw) ? DRG.AID.FangAndClaw //Use FangAndClaw if Unlocked - : DRG.AID.TrueThrust, //Return to TrueThrust otherwise + AID.FullThrust or AID.HeavensThrust => + Unlocked(AID.FangAndClaw) ? AID.FangAndClaw //Use FangAndClaw if Unlocked + : AID.TrueThrust, //Return to TrueThrust otherwise //After ChaosThrust or ChaoticSpring in the combo - DRG.AID.ChaosThrust or DRG.AID.ChaoticSpring => - Unlocked(DRG.AID.WheelingThrust) ? DRG.AID.WheelingThrust //Use WheelingThrust if Unlocked - : DRG.AID.TrueThrust, //Return to TrueThrust otherwise + AID.ChaosThrust or AID.ChaoticSpring => + Unlocked(AID.WheelingThrust) ? AID.WheelingThrust //Use WheelingThrust if Unlocked + : AID.TrueThrust, //Return to TrueThrust otherwise //After WheelingThrust or FangAndClaw in the combo - DRG.AID.WheelingThrust or DRG.AID.FangAndClaw => - Unlocked(DRG.AID.Drakesbane) ? DRG.AID.Drakesbane //Use Drakesbane if Unlocked - : DRG.AID.TrueThrust, //Return to TrueThrust otherwise + AID.WheelingThrust or AID.FangAndClaw => + Unlocked(AID.Drakesbane) ? AID.Drakesbane //Use Drakesbane if Unlocked + : AID.TrueThrust, //Return to TrueThrust otherwise //If no combo active and Draconian Fire buff is up, use RaidenThrust - _ => HasEffect(DRG.SID.DraconianFire) ? DRG.AID.RaidenThrust //RaidenThrust if DraconianFire is active - : DRG.AID.TrueThrust, //No combo, start with TrueThrust + _ => HasEffect(SID.DraconianFire) ? AID.RaidenThrust //RaidenThrust if DraconianFire is active + : AID.TrueThrust, //No combo, start with TrueThrust }; //Limits the combo sequence to just 1-2-3 ST skills, ignoring other Unlocked actions. - private DRG.AID UseOnly123ST() => ComboLastMove switch + private AID UseOnly123ST() => ComboLastMove switch { //Start combo with TrueThrust - DRG.AID.TrueThrust or DRG.AID.RaidenThrust => - Unlocked(DRG.AID.LanceBarrage) ? DRG.AID.LanceBarrage //LanceBarrage if Unlocked - : Unlocked(DRG.AID.VorpalThrust) ? DRG.AID.VorpalThrust //VorpalThrust otherwise - : DRG.AID.TrueThrust, //Else return to TrueThrust + AID.TrueThrust or AID.RaidenThrust => + Unlocked(AID.LanceBarrage) ? AID.LanceBarrage //LanceBarrage if Unlocked + : Unlocked(AID.VorpalThrust) ? AID.VorpalThrust //VorpalThrust otherwise + : AID.TrueThrust, //Else return to TrueThrust //After VorpalThrust or LanceBarrage - DRG.AID.VorpalThrust or DRG.AID.LanceBarrage => - Unlocked(DRG.AID.HeavensThrust) ? DRG.AID.HeavensThrust //HeavensThrust if Unlocked - : Unlocked(DRG.AID.FullThrust) ? DRG.AID.FullThrust //FullThrust to end combo - : DRG.AID.TrueThrust, //Else return to TrueThrust + AID.VorpalThrust or AID.LanceBarrage => + Unlocked(AID.HeavensThrust) ? AID.HeavensThrust //HeavensThrust if Unlocked + : Unlocked(AID.FullThrust) ? AID.FullThrust //FullThrust to end combo + : AID.TrueThrust, //Else return to TrueThrust //After FullThrust or HeavensThrust - DRG.AID.FullThrust or DRG.AID.HeavensThrust => - Unlocked(DRG.AID.FangAndClaw) ? DRG.AID.FangAndClaw //FangAndClaw if Unlocked - : DRG.AID.TrueThrust, //Else return to TrueThrust + AID.FullThrust or AID.HeavensThrust => + Unlocked(AID.FangAndClaw) ? AID.FangAndClaw //FangAndClaw if Unlocked + : AID.TrueThrust, //Else return to TrueThrust //After WheelingThrust or FangAndClaw - DRG.AID.WheelingThrust or DRG.AID.FangAndClaw => - Unlocked(DRG.AID.Drakesbane) ? DRG.AID.Drakesbane //Drakesbane if Unlocked - : DRG.AID.TrueThrust, //Else return to TrueThrust + AID.WheelingThrust or AID.FangAndClaw => + Unlocked(AID.Drakesbane) ? AID.Drakesbane //Drakesbane if Unlocked + : AID.TrueThrust, //Else return to TrueThrust //If Draconian Fire buff is up, use RaidenThrust - _ => HasEffect(DRG.SID.DraconianFire) ? DRG.AID.RaidenThrust //RaidenThrust if DraconianFire is active - : DRG.AID.TrueThrust, //No combo, start with TrueThrust + _ => HasEffect(SID.DraconianFire) ? AID.RaidenThrust //RaidenThrust if DraconianFire is active + : AID.TrueThrust, //No combo, start with TrueThrust }; //Limits the combo sequence to 1-4-5 ST skills, focusing on Disembowel and Chaos/ChaoticSpring. - private DRG.AID UseOnly145ST() => ComboLastMove switch + private AID UseOnly145ST() => ComboLastMove switch { //Start combo with TrueThrust - DRG.AID.TrueThrust or DRG.AID.RaidenThrust => - Unlocked(DRG.AID.Disembowel) - ? Unlocked(DRG.AID.SpiralBlow) ? DRG.AID.SpiralBlow : DRG.AID.Disembowel //Disembowel/SpiralBlow if Unlocked - : DRG.AID.TrueThrust, //Else return to TrueThrust + AID.TrueThrust or AID.RaidenThrust => + Unlocked(AID.Disembowel) + ? Unlocked(AID.SpiralBlow) ? AID.SpiralBlow : AID.Disembowel //Disembowel/SpiralBlow if Unlocked + : AID.TrueThrust, //Else return to TrueThrust //After Disembowel or SpiralBlow - DRG.AID.Disembowel or DRG.AID.SpiralBlow => - Unlocked(DRG.AID.ChaoticSpring) ? DRG.AID.ChaoticSpring //ChaoticSpring if Unlocked - : Unlocked(DRG.AID.ChaosThrust) ? DRG.AID.ChaosThrust //ChaosThrust if Unlocked - : DRG.AID.TrueThrust, //Else return to TrueThrust + AID.Disembowel or AID.SpiralBlow => + Unlocked(AID.ChaoticSpring) ? AID.ChaoticSpring //ChaoticSpring if Unlocked + : Unlocked(AID.ChaosThrust) ? AID.ChaosThrust //ChaosThrust if Unlocked + : AID.TrueThrust, //Else return to TrueThrust //After ChaosThrust or ChaoticSpring - DRG.AID.ChaosThrust or DRG.AID.ChaoticSpring => - Unlocked(DRG.AID.WheelingThrust) ? DRG.AID.WheelingThrust //WheelingThrust if Unlocked - : DRG.AID.TrueThrust, //Else return to TrueThrust + AID.ChaosThrust or AID.ChaoticSpring => + Unlocked(AID.WheelingThrust) ? AID.WheelingThrust //WheelingThrust if Unlocked + : AID.TrueThrust, //Else return to TrueThrust //After WheelingThrust or FangAndClaw - DRG.AID.WheelingThrust or DRG.AID.FangAndClaw => - Unlocked(DRG.AID.Drakesbane) ? DRG.AID.Drakesbane //Drakesbane if Unlocked - : DRG.AID.TrueThrust, //Else return to TrueThrust + AID.WheelingThrust or AID.FangAndClaw => + Unlocked(AID.Drakesbane) ? AID.Drakesbane //Drakesbane if Unlocked + : AID.TrueThrust, //Else return to TrueThrust //If Draconian Fire buff is up, use RaidenThrust - _ => HasEffect(DRG.SID.DraconianFire) ? DRG.AID.RaidenThrust //RaidenThrust if DraconianFire is active - : DRG.AID.TrueThrust, //No combo, start with TrueThrust + _ => HasEffect(SID.DraconianFire) ? AID.RaidenThrust //RaidenThrust if DraconianFire is active + : AID.TrueThrust, //No combo, start with TrueThrust }; #endregion @@ -817,20 +819,20 @@ private void QueueOGCD(DRG.AID aid, Actor? target, OGCDPriority prio, float base #region AOE Helpers //Determines the next action in the AOE combo based on the last action used. - private DRG.AID NextFullAOE() => ComboLastMove switch + private AID NextFullAOE() => ComboLastMove switch { //Start AOE combo with DoomSpike - DRG.AID.DoomSpike => - Unlocked(DRG.AID.SonicThrust) ? DRG.AID.SonicThrust : DRG.AID.DoomSpike, //SonicThrust if Unlocked, else DoomSpike + AID.DoomSpike => + Unlocked(AID.SonicThrust) ? AID.SonicThrust : AID.DoomSpike, //SonicThrust if Unlocked, else DoomSpike //Continue AOE combo with SonicThrust - DRG.AID.SonicThrust => - Unlocked(DRG.AID.CoerthanTorment) ? DRG.AID.CoerthanTorment : DRG.AID.DoomSpike, //CoerthanTorment if Unlocked, else DoomSpike + AID.SonicThrust => + Unlocked(AID.CoerthanTorment) ? AID.CoerthanTorment : AID.DoomSpike, //CoerthanTorment if Unlocked, else DoomSpike //If Draconian Fire buff is up, use DraconianFury - _ => HasEffect(DRG.SID.DraconianFire) - ? Unlocked(DRG.AID.DraconianFury) ? DRG.AID.DraconianFury : DRG.AID.DoomSpike //DraconianFury if Unlocked, else DoomSpike - : DRG.AID.DoomSpike, //No DraconianFire active, default to DoomSpike + _ => HasEffect(SID.DraconianFire) + ? Unlocked(AID.DraconianFury) ? AID.DraconianFury : AID.DoomSpike //DraconianFury if Unlocked, else DoomSpike + : AID.DoomSpike, //No DraconianFire active, default to DoomSpike }; #endregion @@ -863,10 +865,10 @@ private void QueueOGCD(DRG.AID aid, Actor? target, OGCDPriority prio, float base private bool ShouldUseLifeSurge(SurgeStrategy strategy, Actor? target) => strategy switch { SurgeStrategy.Automatic => Player.InCombat && target != null && canLS && hasLC && - !HasEffect(DRG.SID.LifeSurge) && - (CD(DRG.AID.LifeSurge) < 40 || CD(DRG.AID.BattleLitany) > 50) && - (ComboLastMove is DRG.AID.WheelingThrust or DRG.AID.FangAndClaw && Unlocked(DRG.AID.Drakesbane) || - ComboLastMove is DRG.AID.VorpalThrust or DRG.AID.LanceBarrage && Unlocked(DRG.AID.FullThrust)), + !HasEffect(SID.LifeSurge) && + (CD(AID.LifeSurge) < 40 || CD(AID.BattleLitany) > 50) && + (ComboLastMove is AID.WheelingThrust or AID.FangAndClaw && Unlocked(AID.Drakesbane) || + ComboLastMove is AID.VorpalThrust or AID.LanceBarrage && Unlocked(AID.FullThrust)), SurgeStrategy.Force => canLS, SurgeStrategy.Delay => false, _ => false @@ -1004,33 +1006,33 @@ private void QueueOGCD(DRG.AID aid, Actor? target, OGCDPriority prio, float base target != null && Player.InCombat && !HasEffect(ClassShared.SID.TrueNorth) && GCD < 1.25f && - ((!IsOnRear(target) && //Side - (ComboLastMove is DRG.AID.Disembowel or DRG.AID.SpiralBlow - or DRG.AID.ChaosThrust or DRG.AID.ChaoticSpring)) || - (!IsOnFlank(target) && //Back - (ComboLastMove is DRG.AID.HeavensThrust or DRG.AID.FullThrust))), + (!IsOnRear(target) && //Side + ComboLastMove is AID.Disembowel or AID.SpiralBlow + or AID.ChaosThrust or AID.ChaoticSpring || + !IsOnFlank(target) && //Back + ComboLastMove is AID.HeavensThrust or AID.FullThrust), TrueNorthStrategy.ASAP => target != null && Player.InCombat && !HasEffect(ClassShared.SID.TrueNorth) && - ((!IsOnRear(target) && //Side - (ComboLastMove is DRG.AID.Disembowel or DRG.AID.SpiralBlow - or DRG.AID.ChaosThrust or DRG.AID.ChaoticSpring)) || - (!IsOnFlank(target) && //Back - (ComboLastMove is DRG.AID.HeavensThrust or DRG.AID.FullThrust))), + (!IsOnRear(target) && //Side + ComboLastMove is AID.Disembowel or AID.SpiralBlow + or AID.ChaosThrust or AID.ChaoticSpring || + !IsOnFlank(target) && //Back + ComboLastMove is AID.HeavensThrust or AID.FullThrust), TrueNorthStrategy.Flank => target != null && Player.InCombat && !HasEffect(ClassShared.SID.TrueNorth) && GCD < 1.25f && - (!IsOnFlank(target) && //Back - (ComboLastMove is DRG.AID.HeavensThrust or DRG.AID.FullThrust)), + !IsOnFlank(target) && //Back + ComboLastMove is AID.HeavensThrust or AID.FullThrust, TrueNorthStrategy.Rear => target != null && Player.InCombat && !HasEffect(ClassShared.SID.TrueNorth) && GCD < 1.25f && - (!IsOnRear(target) && //Side - (ComboLastMove is DRG.AID.Disembowel or DRG.AID.SpiralBlow - or DRG.AID.ChaosThrust or DRG.AID.ChaoticSpring)), - TrueNorthStrategy.Force => !HasEffect(DRG.SID.TrueNorth), + !IsOnRear(target) && //Side + ComboLastMove is AID.Disembowel or AID.SpiralBlow + or AID.ChaosThrust or AID.ChaoticSpring, + TrueNorthStrategy.Force => !HasEffect(SID.TrueNorth), TrueNorthStrategy.Delay => false, _ => false }; diff --git a/BossMod/Autorotation/akechi/PvE/GNB.cs b/BossMod/Autorotation/akechi/PvE/GNB.cs new file mode 100644 index 0000000000..ac05f9d7d4 --- /dev/null +++ b/BossMod/Autorotation/akechi/PvE/GNB.cs @@ -0,0 +1,825 @@ +using FFXIVClientStructs.FFXIV.Client.Game.Gauge; +using AID = BossMod.GNB.AID; +using SID = BossMod.GNB.SID; +using TraitID = BossMod.GNB.TraitID; + +namespace BossMod.Autorotation.akechi.PvE; +//Contribution by Akechi +//Discord @akechdz or 'Akechi' on Puni.sh for maintenance +//This module supports <=2.47 SkS rotation as default (or 'Automatic') +//With user adjustment, 'SlowGNB' or 'FastGNB' usage is achievable + +public sealed class GNB(RotationModuleManager manager, Actor player) : RotationModule(manager, player) +{ + #region Enums: Abilities / Strategies + //Actions tracked for Cooldown Planner execution + public enum Track + { + AoE, //ST&AoE actions + Burst, //Burst damage actions + Potion, //Potion usage tracking + LightningShot, //Ranged attack tracking + GnashingFang, //Gnashing Fang action tracking + NoMercy, //No Mercy ability tracking + SonicBreak, //Sonic Break ability tracking + Bloodfest, //Bloodfest ability tracking + DoubleDown, //Double Down ability tracking + BurstStrike, //Burst Strike ability tracking + FatedCircle, //Fated Circle ability tracking + Zone, //Blasting Zone or Danger Zone tracking + BowShock, //Bow Shock ability tracking + } + + //Defines the strategy for using ST/AoE actions based on the current target selection and conditions + public enum AOEStrategy + { + SingleTarget, //Force single-target actions without exceeding cartridge cap + FocusSingleTarget, //Force single-target actions, regardless of cartridges + ForceAoE, //Force AoE actions without exceeding cartridge cap + FocusAoE, //Force AoE actions, regardless of cartridges + Auto, //Decide action based on target count; may break combo if needed + AutoFinishCombo, //Decide action based on target count but finish current combo if possible + GenerateDowntime, //Generate cartridges before downtime + } + + //Defines different strategies for executing burst damage actions based on cooldown and resource availability + public enum BurstStrategy + { + Automatic, //Automatically execute based on conditions + ConserveCarts, //Conserve cartridges for future use + UnderRaidBuffs, //Execute during raid buffs for maximum effect + UnderPotion //Execute while under the effects of a potion + } + + //Defines strategies for potion usage in combat, determining when and how to consume potions based on the situation + public enum PotionStrategy + { + Manual, //Manual potion usage + AlignWithRaidBuffs, //Align potion usage with raid buffs + Immediate //Use potions immediately when available + } + + //Defines strategies for using Lightning Shot during combat based on various conditions + public enum LightningShotStrategy + { + OpenerRanged, //Use Lightning Shot as part of the opener + Opener, //Use Lightning Shot at the start of combat + Force, //Always use Lightning Shot regardless of conditions + Ranged, //Use Lightning Shot when ranged attacks are necessary + Forbid //Prohibit the use of Lightning Shot + } + + //Defines the strategy for using Gnashing Fang in combos, allowing for different behaviors based on combat scenarios + public enum GnashingStrategy + { + Automatic, //Automatically decide when to use Gnashing Fang + ForceGnash, //Force the use of Gnashing Fang regardless of conditions + ForceClaw, //Force the use of Savage Claw action when in combo + ForceTalon, //Force the use of Wicked Talon action when in combo + Delay //Delay the use of Gnashing Fang for strategic reasons + } + + //Defines the strategy for using No Mercy, allowing for different behaviors based on combat scenarios + public enum NoMercyStrategy + { + Automatic, //Automatically decide when to use No Mercy + Force, //Force the use of No Mercy regardless of conditions + ForceLW, //Force the use of No Mercy in next possible 2nd oGCD slot + Force2, //Force the use of No Mercy when 2 cartridges are available + Force2LW, //Force the use of No Mercy in next possible 2nd oGCD slot & 2 cartridges are available + Force3, //Force the use of No Mercy when 3 cartridges are available + Force3LW, //Force the use of No Mercy in next possible 2nd oGCD slot & 3 cartridges are available + Delay //Delay the use of No Mercy for strategic reasons + } + + //Defines the strategy for using Sonic Break, allowing for different behaviors based on combat scenarios + public enum SonicBreakStrategy + { + Automatic, //Automatically decide when to use Sonic Break + Force, //Force the use of Sonic Break regardless of conditions + EarlySB, //Force the use of Sonic Break on the first GCD slot inside No Mercy window + LateSB, //Force the use of Sonic Break on the last GCD slot inside No Mercy window + Delay //Delay the use of Sonic Break for strategic reasons + } + + //Defines the strategy for using Bloodfest, allowing for different behaviors based on combat scenarios + public enum BloodfestStrategy + { + Automatic, //Automatically decide when to use Bloodfest + Force, //Force the use of Bloodfest regardless of conditions + Force0, //Force the use of Bloodfest only when ammo is empty + Delay //Delay the use of Sonic Break for strategic reasons + } + + //Defines different offensive strategies that dictate how abilities and resources are used during combat + public enum OffensiveStrategy + { + Automatic, //Automatically decide when to use offensive abilities + Force, //Force the use of offensive abilities regardless of conditions + Delay //Delay the use of offensive abilities for strategic reasons + } + #endregion + + public static RotationModuleDefinition Definition() + { + //Module title & signature + var res = new RotationModuleDefinition("GNB (Akechi)", "Standard Rotation Module", "Standard rotation (Akechi)", "Akechi", RotationModuleQuality.Good, BitMask.Build((int)Class.GNB), 100); + + #region Custom strategies + //Targeting strategy + res.Define(Track.AoE).As("Combo Option", "AoE", uiPriority: 200) + .AddOption(AOEStrategy.SingleTarget, "ST", "Use ST rotation (with overcap protection)") + .AddOption(AOEStrategy.FocusSingleTarget, "ST", "Use ST rotation (without overcap protection)") + .AddOption(AOEStrategy.ForceAoE, "AoE", "Use AoE rotation (with overcap protection)") + .AddOption(AOEStrategy.FocusAoE, "AoE", "Use AoE rotation (without overcap protection)") + .AddOption(AOEStrategy.Auto, "Auto", "Use AoE rotation if 3+ targets would be hit, otherwise use ST rotation; break combo if necessary") + .AddOption(AOEStrategy.AutoFinishCombo, "Auto Finish Combo", "Use AoE rotation if 3+ targets would be hit, otherwise use ST rotation; finish combo before switching") + .AddOption(AOEStrategy.GenerateDowntime, "Generate before Downtime", "Estimates time until disengagement & determines when to use ST or AoE combo to generate carts appropriately before downtime"); + + //Burst strategy + res.Define(Track.Burst).As("Burst", uiPriority: 190) + .AddOption(BurstStrategy.Automatic, "Automatic", "Spend cartridges optimally") + .AddOption(BurstStrategy.ConserveCarts, "Conserve", "Conserve everything (cartridges & GCDs)") + .AddOption(BurstStrategy.UnderRaidBuffs, "Under RaidBuffs", "Spend under raid buffs, otherwise conserve; ignores potion") + .AddOption(BurstStrategy.UnderPotion, "Under Potion", "Spend under potion, otherwise conserve; ignores raid buffs"); + + //Potion strategy + res.Define(Track.Potion).As("Potion", uiPriority: 180) + .AddOption(PotionStrategy.Manual, "Manual", "Do not use automatically") + .AddOption(PotionStrategy.AlignWithRaidBuffs, "AlignWithRaidBuffs", "Align with 2-minute raid buffs (0/6, 2/8, etc)", 270, 30, ActionTargets.Self) + .AddOption(PotionStrategy.Immediate, "Immediate", "Use ASAP, regardless of any buffs", 270, 30, ActionTargets.Self) + .AddAssociatedAction(ActionDefinitions.IDPotionStr); + + //LightningShot strategy + res.Define(Track.LightningShot).As("Lightning Shot", "L.Shot", uiPriority: 20) + .AddOption(LightningShotStrategy.OpenerRanged, "OpenerRanged", "Use as very first GCD and only if outside melee range") + .AddOption(LightningShotStrategy.Opener, "Opener", "Use as very first GCD regardless of range") + .AddOption(LightningShotStrategy.Force, "Force", "Force use ASAP (even in melee range)") + .AddOption(LightningShotStrategy.Ranged, "Ranged", "Use if outside melee range") + .AddOption(LightningShotStrategy.Forbid, "Forbid", "Do not use at all") + .AddAssociatedActions(AID.LightningShot); + + //GnashingFang strategy + res.Define(Track.GnashingFang).As("Gnashing Fang", "G.Fang", uiPriority: 160) + .AddOption(GnashingStrategy.Automatic, "Auto", "Normal use of Gnashing Fang") + .AddOption(GnashingStrategy.ForceGnash, "Force", "Force use of Gnashing Fang (Step 1)", 30, 0, ActionTargets.Hostile, 60) + .AddOption(GnashingStrategy.ForceClaw, "Force", "Force use of Savage Claw (Step 2)", 0, 0, ActionTargets.Hostile, 60) + .AddOption(GnashingStrategy.ForceTalon, "Force", "Force use of Wicked Talon (Step 3)", 0, 0, ActionTargets.Hostile, 60) + .AddOption(GnashingStrategy.Delay, "Delay", "Delay use of Gnashing Fang", 0, 0, ActionTargets.None, 60) + .AddAssociatedActions(AID.GnashingFang, AID.SavageClaw, AID.WickedTalon); + + //NoMercy strategy + res.Define(Track.NoMercy).As("No Mercy", "N.Mercy", uiPriority: 170) + .AddOption(NoMercyStrategy.Automatic, "Automatic", "Use normally") + .AddOption(NoMercyStrategy.Force, "Force", "Force use ASAP (even during downtime)", 60, 20, ActionTargets.Self, 2) + .AddOption(NoMercyStrategy.ForceLW, "Force Late-weave", "Uses as soon as in next possible late-weave slot", 60, 20, ActionTargets.Self, 2) + .AddOption(NoMercyStrategy.Force2, "Force (2+ carts)", "Use as soon as you have 2 (or more) cartridges (SlowGNB)", 60, 20, ActionTargets.Self, 30) + .AddOption(NoMercyStrategy.Force2LW, "Force Late-weave (2+ carts)", "Use as soon as you have 2+ cartridges & in next possible late-weave slot (FastGNB)", 60, 20, ActionTargets.Self, 30) + .AddOption(NoMercyStrategy.Force3, "Force (3 carts)", "Use as soon as you have 3 cartridges (SlowGNB)", 60, 20, ActionTargets.Self, 88) + .AddOption(NoMercyStrategy.Force3LW, "Force Late-weave (3 carts)", "Use as soon as you have 3 cartridges & in next possible late-weave slot (FastGNB)", 60, 20, ActionTargets.Self, 88) + .AddOption(NoMercyStrategy.Delay, "Delay", "Delay", 0, 0, ActionTargets.None, 2) + .AddAssociatedActions(AID.NoMercy); + + //SonicBreak strategy + res.Define(Track.SonicBreak).As("Sonic Break", "S.Break", uiPriority: 150) + .AddOption(SonicBreakStrategy.Automatic, "Auto", "Normal use of Sonic Break") + .AddOption(SonicBreakStrategy.Force, "Force", "Force use of Sonic Break", 0, 30, ActionTargets.Hostile, 54) + .AddOption(SonicBreakStrategy.EarlySB, "Early Sonic Break", "Uses Sonic Break as the very first GCD when in No Mercy", 0, 30, ActionTargets.Hostile, 54) + .AddOption(SonicBreakStrategy.LateSB, "Late Sonic Break", "Uses Sonic Break as the very last GCD when in No Mercy", 0, 30, ActionTargets.Hostile, 54) + .AddOption(SonicBreakStrategy.Delay, "Delay", "Delay use of Sonic Break", 0, 0, ActionTargets.None, 54) + .AddAssociatedActions(AID.SonicBreak); + + //Bloodfest strategy + res.Define(Track.Bloodfest).As("Bloodfest", "Fest", uiPriority: 170) + .AddOption(BloodfestStrategy.Automatic, "Auto", "Normal use of Bloodfest") + .AddOption(BloodfestStrategy.Force, "Force", "Force use of Bloodfest, regardless of ammo count", 120, 0, ActionTargets.Hostile, 80) + .AddOption(BloodfestStrategy.Force0, "Force (0 cart)", "Force use of Bloodfest as soon as you have 0 carts", 120, 0, ActionTargets.Hostile, 80) + .AddOption(BloodfestStrategy.Delay, "Delay", "Delay use of Bloodfest", 0, 0, ActionTargets.None, 80) + .AddAssociatedActions(AID.Bloodfest); + #endregion + + #region Offensive Strategies + //DoubleDown strategy + res.Define(Track.DoubleDown).As("Double Down", "D.Down", uiPriority: 160) + .AddOption(OffensiveStrategy.Automatic, "Auto", "Normal use of Double Down") + .AddOption(OffensiveStrategy.Force, "Force", "Force use of Double Down", 60, 0, ActionTargets.Hostile, 90) + .AddOption(OffensiveStrategy.Delay, "Delay", "Delay use of Double Down", 0, 0, ActionTargets.None, 90) + .AddAssociatedActions(AID.DoubleDown); + + //BurstStrike strategy + res.Define(Track.BurstStrike).As("Burst Strike", "B.Strike", uiPriority: 140) + .AddOption(OffensiveStrategy.Automatic, "Auto", "Normal use of Burst Strike") + .AddOption(OffensiveStrategy.Force, "Force", "Force use of Burst Strike", 0, 0, ActionTargets.Hostile, 30) + .AddOption(OffensiveStrategy.Delay, "Delay", "Delay use of Burst Strike", 0, 0, ActionTargets.None, 30) + .AddAssociatedActions(AID.BurstStrike); + + //FatedCircle strategy + res.Define(Track.FatedCircle).As("Fated Circle", "F.Circle", uiPriority: 140) + .AddOption(OffensiveStrategy.Automatic, "Auto", "Normal use of Fated Circle") + .AddOption(OffensiveStrategy.Force, "Force", "Force use of Fated Circle", 0, 0, ActionTargets.Hostile, 72) + .AddOption(OffensiveStrategy.Delay, "Delay", "Delay use of Fated Circle", 0, 0, ActionTargets.None, 72) + .AddAssociatedActions(AID.FatedCircle); + + //Zone strategy + res.Define(Track.Zone).As("Blasting Zone", "Zone", uiPriority: 150) + .AddOption(OffensiveStrategy.Automatic, "Automatic", "Use normally") + .AddOption(OffensiveStrategy.Force, "Force", "Force use ASAP", 30, 0, ActionTargets.Hostile, 18) + .AddOption(OffensiveStrategy.Delay, "Delay", "Delay", 0, 0, ActionTargets.None, 18) + .AddAssociatedActions(AID.BlastingZone, AID.DangerZone); + + //BowShock strategy + res.Define(Track.BowShock).As("Bow Shock", "B.Shock", uiPriority: 150) + .AddOption(OffensiveStrategy.Automatic, "Auto", "Normal use of Bow Shock") + .AddOption(OffensiveStrategy.Force, "Force", "Force use of Bow Shock", 60, 15, ActionTargets.Self, 62) + .AddOption(OffensiveStrategy.Delay, "Delay", "Delay use of Bow Shock", 0, 0, ActionTargets.None, 62) + .AddAssociatedActions(AID.BowShock); + #endregion + + return res; + + } + + #region Priorities + //Priority for GCDs used + public enum GCDPriority + { + None = 0, + Combo123 = 350, + FatedCircle = 400, + BurstStrike = 500, + Reign = 525, + comboNeed = 550, + GF23 = 575, + SonicBreak = 600, + DoubleDown = 675, + GF1 = 700, + ForcedGCD = 900, + } + //Priority for oGCDs used + public enum OGCDPriority + { + None = 0, + Continuation = 500, + Zone = 550, + BowShock = 600, + Continuation1 = 650, + Bloodfest = 700, + ContinuationNeed = 800, + NoMercy = 875, + Potion = 900, + ForcedOGCD = 900, + } + #endregion + + #region Placeholders for Variables + //Gauge + public byte Ammo; //Range: 0-2 or 0-3 max; this counts current ammo count + public byte GunComboStep; //0 = Gnashing Fang & Reign of Beasts, 1 = Savage Claw, 2 = Wicked Talon, 4 = NobleBlood, 5 = LionHeart. + public int MaxCartridges; //Maximum number of cartridges based on player level + //Cooldown Related + private float bfCD; //Time left on Bloodfest cooldown (120s base) + private float nmLeft; //Time left on No Mercy buff (20s base) + private float nmCD; //Time left on No Mercy cooldown (60s base) + private bool hasNM; //Checks self for No Mercy buff + private bool hasBreak; //Checks self for Ready To Break buff + private bool hasReign; //Checks self for Ready To Reign buff + private bool hasBlast; //Checks self for Ready To Blast buff + private bool hasRaze; //Checks self for Ready To Raze buff + private bool hasRip; //Checks self for Ready To Rip buff + private bool hasTear; //Checks self for Ready To Tear buff + private bool hasGouge; //Checks self for Ready To Gouge buff + private bool canBS; + private bool canGF; + private bool canFC; + private bool canDD; + private bool canBF; + private bool canZone; + private bool canBreak; + private bool canBow; + private bool canContinue; + private bool canReign; + + //Misc + public float PotionLeft; //Time left on potion buff (30s base) + public float RaidBuffsLeft; //Time left on raid-wide buffs (typically 20s-22s) + public float RaidBuffsIn; //Time until raid-wide buffs are applied again (typically 20s-22s) + public float GCDLength; //Current GCD length, adjusted by skill speed/haste (2.5s baseline) + public float BurstWindowLeft; //Time left in current burst window (typically 20s-22s) + public float BurstWindowIn; //Time until next burst window (typically 20s-22s) + public AID NextGCD; //Next global cooldown action to be used (needed for cartridge management) + private GCDPriority NextGCDPrio; //Priority of the next GCD, used for decision making on cooldowns + #endregion + + #region Module Helpers + private bool Unlocked(AID aid) => ActionUnlocked(ActionID.MakeSpell(aid)); //Check if the desired ability is unlocked + private bool Unlocked(TraitID tid) => TraitUnlocked((uint)tid); //Check if the desired trait is unlocked + private float CD(AID aid) => World.Client.Cooldowns[ActionDefinitions.Instance.Spell(aid)!.MainCooldownGroup].Remaining; //Get remaining cooldown time for the specified action + private bool CanFitGCD(float deadline, int extraGCDs = 0) => GCD + GCDLength * extraGCDs < deadline; //Check if we can fit an additional GCD within the provided deadline + private AID ComboLastMove => (AID)World.Client.ComboState.Action; //Get the last action used in the combo sequence + private bool In3y(Actor? target) => Player.DistanceToHitbox(target) <= 3; //Check if the target is within melee range (3 yalms) + private bool In5y(Actor? target) => Player.DistanceToHitbox(target) <= 4.75; //Check if the target is within 5 yalms + private bool ActionReady(AID aid) => World.Client.Cooldowns[ActionDefinitions.Instance.Spell(aid)!.MainCooldownGroup].Remaining < 0.6f; //Check if the desired action is ready (cooldown less than 0.6 seconds) + private bool IsFirstGCD() => !Player.InCombat || (World.CurrentTime - Manager.CombatStart).TotalSeconds < 0.1f; //Check if this is the first GCD in combat + private int NumTargetsHitByAoE() => Hints.NumPriorityTargetsInAOECircle(Player.Position, 5); //Returns the number of targets hit by AoE within a 5-yalm radius around the player + private bool IsPotionBeforeRaidbuffs() => RaidBuffsLeft == 0 && PotionLeft > RaidBuffsIn + 17.5f; //Checks if the potion should be used before raid buffs expire + public bool HasEffect(SID sid) where SID : Enum => Player.FindStatus((uint)(object)sid, Player.InstanceID) != null; //Checks if Status effect is on self + #endregion + + public override void Execute(StrategyValues strategy, Actor? primaryTarget, float estimatedAnimLockDelay, bool isMoving) //Executes our actions + { + #region Variables + //Gauge + var gauge = World.Client.GetGauge(); //Retrieve Gunbreaker gauge + Ammo = gauge.Ammo; //Current cartridges + GunComboStep = gauge.AmmoComboStep; //Combo step for Gnashing Fang or Reign of Beasts + MaxCartridges = Unlocked(TraitID.CartridgeChargeII) ? 3 : 2; //Max cartridges based on level + //Cooldown Related + bfCD = CD(AID.Bloodfest); //Bloodfest cooldown (120s) + nmCD = CD(AID.NoMercy); //No Mercy cooldown (60s) + nmLeft = SelfStatusLeft(SID.NoMercy, 20); //Remaining time for No Mercy buff (20s) + hasBreak = HasEffect(SID.ReadyToBreak); //Checks for Ready To Break buff + hasReign = HasEffect(SID.ReadyToReign); //Checks for Ready To Reign buff + hasNM = nmCD is >= 40 and <= 60; //Checks if No Mercy is active + hasBlast = HasEffect(SID.ReadyToBlast); //Checks for Ready To Blast buff + hasRaze = HasEffect(SID.ReadyToRaze); //Checks for Ready To Raze buff + hasRip = HasEffect(SID.ReadyToRip); //Checks for Ready To Rip buff + hasTear = HasEffect(SID.ReadyToTear); //Checks for Ready To Tear buff + hasGouge = HasEffect(SID.ReadyToGouge); //Checks for Ready To Gouge buff + //Misc + (RaidBuffsLeft, RaidBuffsIn) = EstimateRaidBuffTimings(primaryTarget); + PotionLeft = PotionStatusLeft(); //Remaining time for potion buff (30s) + GCDLength = ActionSpeed.GCDRounded(World.Client.PlayerStats.SkillSpeed, World.Client.PlayerStats.Haste, Player.Level); //GCD based on skill speed and haste + NextGCD = AID.None; //Next global cooldown action to be used + NextGCDPrio = GCDPriority.None; //Priority of the next GCD, used for decision making on cooldowns + + #region Minimal Requirements + //Ammo-relative + canBS = Unlocked(AID.BurstStrike) && Ammo > 0; //BurstStrike conditions; -1 Ammo ST + canGF = Unlocked(AID.GnashingFang) && ActionReady(AID.GnashingFang) && Ammo > 0; //GnashingFang conditions; -1 Ammo ST + canFC = Unlocked(AID.FatedCircle) && Ammo > 0; //FatedCircle conditions; -1 Ammo AOE + canDD = Unlocked(AID.DoubleDown) && ActionReady(AID.DoubleDown) && Ammo > 0; //DoubleDown conditions; -1 Ammo AOE + canBF = Unlocked(AID.Bloodfest) && ActionReady(AID.Bloodfest); //Bloodfest conditions; +all Ammo (must have target) + //Cooldown-relative + canZone = Unlocked(AID.DangerZone) && ActionReady(AID.DangerZone); //Zone conditions + canBreak = hasBreak && Unlocked(AID.SonicBreak); //SonicBreak conditions + canBow = Unlocked(AID.BowShock) && ActionReady(AID.BowShock); //BowShock conditions + canContinue = Unlocked(AID.Continuation); //Continuation conditions + canReign = Unlocked(AID.ReignOfBeasts) && hasReign; //ReignOfBeasts conditions + #endregion + + #endregion + + #region Burst + //Burst (raid buff) windows typically last 20s every 120s + var burst = strategy.Option(Track.Burst); + var burstStrategy = burst.As(); + var hold = burstStrategy == BurstStrategy.ConserveCarts; //Determine if conserving cartridges + + //Calculate the burst window based on the current strategy + (BurstWindowIn, BurstWindowLeft) = burstStrategy switch + { + BurstStrategy.Automatic => (RaidBuffsIn, IsPotionBeforeRaidbuffs() ? 0 : Math.Max(PotionLeft, RaidBuffsLeft)), + BurstStrategy.UnderRaidBuffs => (RaidBuffsIn, RaidBuffsLeft), + BurstStrategy.UnderPotion => (PotionCD, PotionLeft), + _ => (0, 0) + }; + #endregion + + #region Targeting + //Define ST/AoE strategy and determine number of targets + var AOEStrategy = strategy.Option(Track.AoE).As(); + var AoETargets = AOEStrategy switch + { + AOEStrategy.SingleTarget => NumTargetsHitByAoE() > 0 ? 1 : 0, + AOEStrategy.FocusSingleTarget => NumTargetsHitByAoE() > 0 ? 1 : 0, + AOEStrategy.ForceAoE => NumTargetsHitByAoE() > 0 ? 100 : 0, + AOEStrategy.FocusAoE => NumTargetsHitByAoE() > 0 ? 100 : 0, + AOEStrategy.GenerateDowntime => NumTargetsHitByAoE() > 0 ? 100 : 0, + _ => NumTargetsHitByAoE() + }; + #endregion + + #region Rotation Strategies + //Force Options + if (AOEStrategy == AOEStrategy.FocusSingleTarget) //ST (without overcap protection) + QueueGCD(NextForceSingleTarget(), primaryTarget, GCDPriority.ForcedGCD); + if (AOEStrategy == AOEStrategy.FocusAoE) //AoE (without overcap protection) + QueueGCD(NextForceAoE(), primaryTarget, GCDPriority.ForcedGCD); + + #region Logic for Cart Generation before Downtime + //Estimate time to next downtime + var downtimeIn = Manager.Planner?.EstimateTimeToNextDowntime().Item2 ?? float.MaxValue; + var comboStepsRemaining = ComboLastMove switch + { + AID.KeenEdge => Unlocked(AID.SolidBarrel) ? 2 : Unlocked(AID.BrutalShell) ? 1 : 0, + AID.DemonSlice => Unlocked(AID.DemonSlaughter) ? 1 : 0, + _ => 0 + }; + + if (AOEStrategy == AOEStrategy.GenerateDowntime) + { + if (comboStepsRemaining == 0) //Not in any combo + { + if (downtimeIn == GCD * 2 && Ammo == 2 || + downtimeIn == GCD * 4 && Ammo == 1 || + downtimeIn == GCD * 6 && Ammo == 0) + QueueGCD(AID.DemonSlice, Player, GCDPriority.ForcedGCD); + + if (downtimeIn == GCD * 3 && Ammo == 2 || + downtimeIn == GCD * 5 && Ammo == 1 || + downtimeIn == GCD * 8 && Ammo == 0 || + downtimeIn == GCD * 9 && Ammo == 0) + QueueGCD(AID.KeenEdge, primaryTarget, GCDPriority.ForcedGCD); + } + + if (comboStepsRemaining == 1) //Combo initiated + { + if ((downtimeIn == GCD && Ammo == 2 || + downtimeIn == GCD * 3 && Ammo == 1 || + downtimeIn == GCD * 5 && Ammo == 0) && + ComboLastMove == AID.DemonSlice) + QueueGCD(AID.DemonSlaughter, Player, GCDPriority.ForcedGCD); + + if ((downtimeIn == GCD * 2 && Ammo == 2 || + downtimeIn == GCD * 4 && Ammo == 1 || + downtimeIn == GCD * 7 && Ammo == 2 || + downtimeIn == GCD * 8 && Ammo == 2) && + ComboLastMove == AID.KeenEdge) + QueueGCD(AID.BrutalShell, primaryTarget, GCDPriority.ForcedGCD); + } + + if (comboStepsRemaining == 2) + { + if ((downtimeIn == GCD && (Ammo == 2 || Ammo == 3) || + downtimeIn == GCD * 4 && Ammo == 1 || + downtimeIn == GCD * 7 && Ammo == 0) && + ComboLastMove == AID.BrutalShell) + QueueGCD(AID.SolidBarrel, primaryTarget, GCDPriority.ForcedGCD); + } + + if (Ammo == MaxCartridges) + QueueGCD(NextForceSingleTarget(), primaryTarget, GCDPriority.ForcedGCD); + } + #endregion + + #endregion + + #region Rotation Execution + //Determine and queue combo actions + var (comboAction, comboPrio) = ComboActionPriority(AOEStrategy, AoETargets, burstStrategy, burst.Value.ExpireIn); + QueueGCD(comboAction, comboAction is AID.DemonSlice or AID.DemonSlaughter ? Player : primaryTarget, Ammo == 0 ? GCDPriority.comboNeed : comboPrio); + + #region OGCDs + //No Mercy execution + var nmStrat = strategy.Option(Track.NoMercy).As(); + if (!hold && ShouldUseNoMercy(nmStrat, primaryTarget)) + QueueOGCD(AID.NoMercy, Player, nmStrat is NoMercyStrategy.Force or NoMercyStrategy.ForceLW or NoMercyStrategy.Force2 or NoMercyStrategy.Force2LW or NoMercyStrategy.Force3 or NoMercyStrategy.Force3LW ? OGCDPriority.ForcedOGCD : OGCDPriority.NoMercy); + + //Zone execution (Blasting Zone / Danger Zone) + var zoneAction = Unlocked(AID.BlastingZone) ? AID.BlastingZone : AID.DangerZone; + var zoneStrat = strategy.Option(Track.Zone).As(); + if (!hold && ShouldUseZone(zoneStrat, primaryTarget)) + QueueOGCD(zoneAction, primaryTarget, zoneStrat == OffensiveStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.Zone); + + //Bow Shock execution + var bowStrat = strategy.Option(Track.BowShock).As(); + if (!hold && ShouldUseBowShock(bowStrat, primaryTarget)) + QueueOGCD(AID.BowShock, Player, bowStrat == OffensiveStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.BowShock); + + //Bloodfest execution + var bfStrat = strategy.Option(Track.Bloodfest).As(); + if (!hold && ShouldUseBloodfest(bfStrat, primaryTarget)) + QueueOGCD(AID.Bloodfest, primaryTarget, bfStrat is BloodfestStrategy.Force or BloodfestStrategy.Force0 ? OGCDPriority.ForcedOGCD : OGCDPriority.Bloodfest); + + //Continuation execution + if (canContinue) + { + if (hasRip) + QueueOGCD(AID.JugularRip, primaryTarget, OGCDPriority.ContinuationNeed); + if (hasTear) + QueueOGCD(AID.AbdomenTear, primaryTarget, OGCDPriority.ContinuationNeed); + if (hasGouge) + QueueOGCD(AID.EyeGouge, primaryTarget, OGCDPriority.ContinuationNeed); + if (hasBlast) + QueueOGCD(AID.Hypervelocity, primaryTarget, OGCDPriority.ContinuationNeed); + if (hasRaze) + QueueOGCD(AID.FatedBrand, primaryTarget, OGCDPriority.ContinuationNeed); + } + #endregion + + #region GCDs + //Gnashing Fang execution + var gfStrat = strategy.Option(Track.GnashingFang).As(); + if (!hold && ShouldUseGnashingFang(gfStrat, primaryTarget)) + QueueGCD(AID.GnashingFang, primaryTarget, gfStrat == GnashingStrategy.ForceGnash ? GCDPriority.ForcedGCD : GCDPriority.GF1); + + //Double Down execution + var ddStrat = strategy.Option(Track.DoubleDown).As(); + if (ShouldUseDoubleDown(ddStrat, primaryTarget)) + QueueGCD(AID.DoubleDown, primaryTarget, ddStrat == OffensiveStrategy.Force || Ammo == 1 ? GCDPriority.ForcedGCD : GCDPriority.DoubleDown); + + //Gnashing Fang Combo execution + if (GunComboStep == 1) + QueueGCD(AID.SavageClaw, primaryTarget, gfStrat == GnashingStrategy.ForceClaw ? GCDPriority.ForcedGCD : GCDPriority.GF23); + if (GunComboStep == 2) + QueueGCD(AID.WickedTalon, primaryTarget, gfStrat == GnashingStrategy.ForceTalon ? GCDPriority.ForcedGCD : GCDPriority.GF23); + + //Reign full combo execution + if (canReign && hasNM && GunComboStep == 0) + QueueGCD(AID.ReignOfBeasts, primaryTarget, GCDPriority.Reign); + if (GunComboStep == 3) + QueueGCD(AID.NobleBlood, primaryTarget, GCDPriority.Reign); + if (GunComboStep == 4) + QueueGCD(AID.LionHeart, primaryTarget, GCDPriority.Reign); + + //Sonic Break execution + var sbStrat = strategy.Option(Track.SonicBreak).As(); + if (ShouldUseSonicBreak(sbStrat, primaryTarget)) + QueueGCD(AID.SonicBreak, primaryTarget, sbStrat is SonicBreakStrategy.Force or SonicBreakStrategy.EarlySB ? GCDPriority.ForcedGCD : GCDPriority.SonicBreak); + + //Burst Strike execution + var strikeStrat = strategy.Option(Track.BurstStrike).As(); + if (Unlocked(AID.BurstStrike) && Unlocked(AID.Bloodfest) && ShouldUseBurstStrike(strikeStrat, primaryTarget)) + QueueGCD(AID.BurstStrike, primaryTarget, strikeStrat == OffensiveStrategy.Force ? GCDPriority.ForcedGCD : nmCD < 1 ? GCDPriority.ForcedGCD : GCDPriority.BurstStrike); + + //Fated Circle execution + var fcStrat = strategy.Option(Track.FatedCircle).As(); + if (ShouldUseFatedCircle(fcStrat, primaryTarget)) + QueueGCD(AID.FatedCircle, primaryTarget, fcStrat == OffensiveStrategy.Force ? GCDPriority.ForcedGCD : GCDPriority.FatedCircle); + if (!canFC && canBS) + { + if (Ammo > 0 && ShouldUseBurstStrike(strikeStrat, primaryTarget)) + QueueGCD(AID.BurstStrike, primaryTarget, GCDPriority.BurstStrike); + } + + //Lightning Shot execution + var lsStrat = strategy.Option(Track.LightningShot).As(); + if (ShouldUseLightningShot(primaryTarget, strategy.Option(Track.LightningShot).As())) + QueueGCD(AID.LightningShot, primaryTarget, lsStrat is LightningShotStrategy.Force or LightningShotStrategy.Ranged ? GCDPriority.ForcedGCD : GCDPriority.Combo123); + #endregion + + #endregion + + //Potion execution + if (ShouldUsePotion(strategy.Option(Track.Potion).As())) + Hints.ActionsToExecute.Push(ActionDefinitions.IDPotionStr, Player, ActionQueue.Priority.VeryHigh + (int)OGCDPriority.Potion, 0, GCD - 0.9f); + } + + #region Core Execution Helpers + //QueueGCD execution + private void QueueGCD(AID aid, Actor? target, GCDPriority prio) + { + if (prio != GCDPriority.None) + { + Hints.ActionsToExecute.Push(ActionID.MakeSpell(aid), target, ActionQueue.Priority.High + (int)prio); + if (prio > NextGCDPrio) + { + NextGCD = aid; + NextGCDPrio = prio; + } + } + } + + //QueueOGCD execution + private void QueueOGCD(AID aid, Actor? target, OGCDPriority prio, float basePrio = ActionQueue.Priority.Medium) + { + if (prio != OGCDPriority.None) + { + Hints.ActionsToExecute.Push(ActionID.MakeSpell(aid), target, basePrio + (int)prio); + } + } + + //Returns the amount of ammo gained from specific actions + private int AmmoGainedFromAction(AID action) => action switch + { + AID.SolidBarrel => 1, + AID.DemonSlaughter => 1, + AID.Bloodfest => 3, + _ => 0 + }; + + private (AID, GCDPriority) ComboActionPriority(AOEStrategy aoeStrategy, int AoETargets, BurstStrategy burstStrategy, float burstStrategyExpire) + { + //Determine how many combo steps are remaining based on the last action + var comboStepsRemaining = ComboLastMove switch + { + AID.KeenEdge => Unlocked(AID.SolidBarrel) ? 2 : Unlocked(AID.BrutalShell) ? 1 : 0, + AID.DemonSlice => Unlocked(AID.DemonSlaughter) ? 1 : 0, + _ => 0 + }; + + //Check if we can fit the GCD based on remaining time + if (comboStepsRemaining > 0 && !CanFitGCD(World.Client.ComboState.Remaining)) + comboStepsRemaining = 0; + + var doingAOECombo = ComboLastMove == AID.DemonSlice; + + //Determine if an AoE action is desirable based on target count and strategy + var wantAOEAction = Unlocked(AID.DemonSlice) && aoeStrategy switch + { + AOEStrategy.SingleTarget => false, + AOEStrategy.FocusSingleTarget => false, + AOEStrategy.ForceAoE => true, + AOEStrategy.FocusAoE => false, + AOEStrategy.Auto => AoETargets >= 3, + AOEStrategy.AutoFinishCombo => comboStepsRemaining > 0 + ? doingAOECombo + : Unlocked(AID.Continuation) ? AoETargets >= 3 : AoETargets >= 2, + AOEStrategy.GenerateDowntime => false, + _ => false + }; + + //Reset combo steps if the desired action does not match the current combo type + if (comboStepsRemaining > 0 && wantAOEAction != doingAOECombo) + comboStepsRemaining = 0; + + var nextAction = wantAOEAction ? NextComboAoE() : NextComboSingleTarget(); + var riskingAmmo = Ammo + AmmoGainedFromAction(nextAction) > 3; + + //Return combo priority based on the ability to fit GCDs and remaining combo steps + if (comboStepsRemaining > 0 && !CanFitGCD(World.Client.ComboState.Remaining, 1)) + return (nextAction, GCDPriority.Combo123); + + //Return normal combo action priority based on ammo risks + return (nextAction, riskingAmmo ? GCDPriority.BurstStrike : GCDPriority.Combo123); + } + + #endregion + + #region Single-Target Helpers + private AID NextComboSingleTarget() => ComboLastMove switch //Determines the next single-target action based on the last action used + { + AID.BrutalShell => Ammo == MaxCartridges ? AID.BurstStrike : AID.SolidBarrel, + AID.KeenEdge => AID.BrutalShell, + _ => AID.KeenEdge, + }; + private AID NextForceSingleTarget() => ComboLastMove switch + { + AID.BrutalShell => AID.SolidBarrel, + AID.KeenEdge => AID.BrutalShell, + _ => AID.KeenEdge, + }; + #endregion + + #region AoE Helpers + private AID NextComboAoE() => ComboLastMove switch //Determines the next AoE action based on the last action used + { + AID.DemonSlice => Ammo == MaxCartridges + ? Unlocked(AID.FatedCircle) ? AID.FatedCircle : AID.BurstStrike + : AID.DemonSlaughter, + _ => AID.DemonSlice, + }; + private AID NextForceAoE() => ComboLastMove switch + { + AID.DemonSlice => AID.DemonSlaughter, + _ => AID.DemonSlice, + }; + #endregion + + #region Cooldown Helpers + + //Determines when to use Lightning Shot + private bool ShouldUseLightningShot(Actor? target, LightningShotStrategy strategy) => strategy switch + { + LightningShotStrategy.OpenerRanged => IsFirstGCD() && !In3y(target), + LightningShotStrategy.Opener => IsFirstGCD(), + LightningShotStrategy.Force => true, + LightningShotStrategy.Ranged => !In3y(target), + LightningShotStrategy.Forbid => false, + _ => false + }; + + //Determines when to use No Mercy + private bool ShouldUseNoMercy(NoMercyStrategy strategy, Actor? target) => strategy switch + { + NoMercyStrategy.Automatic => + Player.InCombat && target != null && ActionReady(AID.NoMercy) && GCD < 0.9f && + (Ammo < 3 || //Lv90+ Opener + Ammo >= 1 && bfCD == 0 && Unlocked(AID.Bloodfest) && !Unlocked(AID.DoubleDown) || //Lv80+ Opener + !Unlocked(AID.Bloodfest) && Ammo >= 1 && ActionReady(AID.GnashingFang)), //Lv70 & below + NoMercyStrategy.Force => true, + NoMercyStrategy.ForceLW => Player.InCombat && GCD < 0.9f, + NoMercyStrategy.Force2 => Ammo >= 2, + NoMercyStrategy.Force2LW => Player.InCombat && GCD < 0.9f && Ammo >= 2, + NoMercyStrategy.Force3 => Ammo == 3, + NoMercyStrategy.Force3LW => Player.InCombat && GCD < 0.9f && Ammo == 3, + NoMercyStrategy.Delay => false, + _ => false + }; + + //Determines when to use Bloodfest + private bool ShouldUseBloodfest(BloodfestStrategy strategy, Actor? target) => strategy switch + { + BloodfestStrategy.Automatic => + Player.InCombat && target != null && + canBF && Ammo == 0, + BloodfestStrategy.Force => canBF, + BloodfestStrategy.Force0 => canBF && Ammo == 0, + BloodfestStrategy.Delay => false, + _ => false + }; + + //Determines when to use Zone + private bool ShouldUseZone(OffensiveStrategy strategy, Actor? target) => strategy switch + { + OffensiveStrategy.Automatic => + Player.InCombat && In3y(target) && nmCD is < 57.55f and > 17 && + ActionReady(Unlocked(AID.BlastingZone) ? AID.BlastingZone : AID.DangerZone), + OffensiveStrategy.Force => canZone, + OffensiveStrategy.Delay => false, + _ => false + }; + + //Determines when to use BowShock + private bool ShouldUseBowShock(OffensiveStrategy strategy, Actor? target) => strategy switch + { + OffensiveStrategy.Automatic => + Player.InCombat && ActionReady(AID.BowShock) && In5y(target) && nmCD is < 57.55f and > 17, + OffensiveStrategy.Force => canBow, + OffensiveStrategy.Delay => false, + _ => false + }; + + //Determines when to use Sonic Break + private bool ShouldUseSonicBreak(SonicBreakStrategy strategy, Actor? target) => strategy switch + { + SonicBreakStrategy.Automatic => + Player.InCombat && In3y(target) && canBreak, + SonicBreakStrategy.Force => canBreak, + SonicBreakStrategy.EarlySB => nmCD is >= 57.5f || hasBreak, + SonicBreakStrategy.LateSB => nmLeft <= GCDLength, + SonicBreakStrategy.Delay => false, + _ => false + }; + + //Determines when to use Double Down + private bool ShouldUseDoubleDown(OffensiveStrategy strategy, Actor? target) => strategy switch + { + OffensiveStrategy.Automatic => + Player.InCombat && target != null && + In5y(target) && canDD && hasNM, + OffensiveStrategy.Force => canDD, + OffensiveStrategy.Delay => false, + _ => false + }; + + //Determines when to use Gnashing Fang + private bool ShouldUseGnashingFang(GnashingStrategy strategy, Actor? target) => strategy switch + { + GnashingStrategy.Automatic => + Player.InCombat && target != null && In3y(target) && canGF && + (nmLeft > 0 || hasNM || nmCD is < 35 and > 17), + GnashingStrategy.ForceGnash => canGF, + GnashingStrategy.ForceClaw => Player.InCombat && GunComboStep == 1, + GnashingStrategy.ForceTalon => Player.InCombat && GunComboStep == 2, + GnashingStrategy.Delay => false, + _ => false + }; + + //Determines when to use Burst Strike + private bool ShouldUseBurstStrike(OffensiveStrategy strategy, Actor? target) => strategy switch + { + OffensiveStrategy.Automatic => + Player.InCombat && target != null && In3y(target) && canBS && + (nmCD < 1.5f || + Unlocked(AID.DoubleDown) && hasNM && !ActionReady(AID.DoubleDown) && GunComboStep == 0 && !hasReign || //Lv90+ + !Unlocked(AID.DoubleDown) && !ActionReady(AID.GnashingFang) && hasNM && GunComboStep == 0 || //Lv80 & Below + ComboLastMove == AID.BrutalShell && Ammo == MaxCartridges), //Overcap protection + OffensiveStrategy.Force => canBS, + OffensiveStrategy.Delay => false, + _ => false + }; + + //Determines when to use Fated Circle + private bool ShouldUseFatedCircle(OffensiveStrategy strategy, Actor? AoETargets) => strategy switch + { + OffensiveStrategy.Automatic => + Player.InCombat && AoETargets != null && In3y(AoETargets) && canFC && + (hasNM && !ActionReady(AID.DoubleDown) || + ComboLastMove == AID.DemonSlice && Ammo == MaxCartridges), + OffensiveStrategy.Force => canFC, + OffensiveStrategy.Delay => false, + _ => false + }; + + //Determines if potions are aligned with No Mercy + private bool IsPotionAlignedWithNM() + { + //Use potion before Solid Barrel in opener + //Use for 6m window + return Ammo == 1 && ActionReady(AID.GnashingFang) && + ActionReady(AID.DoubleDown) && + ActionReady(AID.Bloodfest) || //Opener + (bfCD < 15 || ActionReady(AID.Bloodfest)) && Ammo == 3; + } + + //Determines when to use a potion based on strategy + private bool ShouldUsePotion(PotionStrategy strategy) => strategy switch + { + PotionStrategy.AlignWithRaidBuffs => + IsPotionAlignedWithNM() || nmCD < 5 && bfCD < 15, + PotionStrategy.Immediate => true, + _ => false + }; + #endregion +} diff --git a/BossMod/Autorotation/akechi/AkechiPLD.cs b/BossMod/Autorotation/akechi/PvE/PLD.cs similarity index 77% rename from BossMod/Autorotation/akechi/AkechiPLD.cs rename to BossMod/Autorotation/akechi/PvE/PLD.cs index 83feeb837c..df6accf1b8 100644 --- a/BossMod/Autorotation/akechi/AkechiPLD.cs +++ b/BossMod/Autorotation/akechi/PvE/PLD.cs @@ -1,10 +1,12 @@ using FFXIVClientStructs.FFXIV.Client.Game.Gauge; +using AID = BossMod.PLD.AID; +using SID = BossMod.PLD.SID; -namespace BossMod.Autorotation.akechi; +namespace BossMod.Autorotation.akechi.PvE; //Contribution by Akechi //Discord: @akechdz or 'Akechi' on Puni.sh for maintenance -public sealed class AkechiPLD(RotationModuleManager manager, Actor player) : RotationModule(manager, player) +public sealed class PLD(RotationModuleManager manager, Actor player) : RotationModule(manager, player) { //Actions tracked for Cooldown Planner execution public enum Track @@ -142,7 +144,7 @@ public static RotationModuleDefinition Definition() .AddOption(AtonementStrategy.ForceSupplication, "Force Supplication", "Force use of Supplication", 0, 0, ActionTargets.Hostile, 76) .AddOption(AtonementStrategy.ForceSepulchre, "Force Sepulchre", "Force use of Sepulchre", 0, 0, ActionTargets.Hostile, 76) .AddOption(AtonementStrategy.Delay, "Delay", "Delay use of Atonement", 0, 0, ActionTargets.None, 60) - .AddAssociatedActions(PLD.AID.Atonement, PLD.AID.Supplication, PLD.AID.Sepulchre); + .AddAssociatedActions(AID.Atonement, AID.Supplication, AID.Sepulchre); //Blade Combo Strategy: Manage the Blade Combo actions res.Define(Track.BladeCombo).As("Blade Combo", "Blades", uiPriority: 160) @@ -152,7 +154,7 @@ public static RotationModuleDefinition Definition() .AddOption(BladeComboStrategy.ForceTruth, "Force Truth", "Force use of Blade of Truth", 0, 0, ActionTargets.Hostile, 90) .AddOption(BladeComboStrategy.ForceValor, "Force Valor", "Force use of Blade of Valor", 0, 0, ActionTargets.Hostile, 90) .AddOption(BladeComboStrategy.Delay, "Delay", "Delay use of Confiteor & Blade Combo", 0, 0, ActionTargets.None, 80) - .AddAssociatedActions(PLD.AID.BladeOfFaith, PLD.AID.BladeOfTruth, PLD.AID.BladeOfValor); + .AddAssociatedActions(AID.BladeOfFaith, AID.BladeOfTruth, AID.BladeOfValor); //Dash Strategy: Control the use of the Intervene ability res.Define(Track.Dash).As("Intervene", "Dash", uiPriority: 150) @@ -161,7 +163,7 @@ public static RotationModuleDefinition Definition() .AddOption(DashStrategy.Conserve1, "Conserve 1", "Conserve one use of Intervene for manual usage", 30, 0, ActionTargets.Hostile, 74) .AddOption(DashStrategy.GapClose, "Gap Close", "Use as gap closer if outside melee range", 30, 0, ActionTargets.None, 74) .AddOption(DashStrategy.Delay, "Delay", "Delay use of Intervene", 30, 0, ActionTargets.None, 74) - .AddAssociatedActions(PLD.AID.Intervene); + .AddAssociatedActions(AID.Intervene); //Ranged Strategy: Manage ranged attacks when outside melee range res.Define(Track.Ranged).As("Ranged", uiPriority: 20) @@ -174,63 +176,63 @@ public static RotationModuleDefinition Definition() .AddOption(RangedStrategy.Ranged, "Ranged", "Use Shield Lob when outside melee range") .AddOption(RangedStrategy.RangedCast, "Ranged Cast", "Use Holy Spirit when outside melee range", 0, 0, ActionTargets.Hostile, 64) .AddOption(RangedStrategy.Forbid, "Forbid", "Prohibit use of both ranged attacks") - .AddAssociatedActions(PLD.AID.ShieldLob, PLD.AID.HolySpirit); + .AddAssociatedActions(AID.ShieldLob, AID.HolySpirit); //Fight or Flight Strategy: Manage offensive cooldowns res.Define(Track.FightOrFlight).As("Fight or Flight", "FoF", uiPriority: 170) .AddOption(OffensiveStrategy.Automatic, "Automatic", "Use Fight or Flight normally") .AddOption(OffensiveStrategy.Force, "Force", "Force use of Fight or Flight", 60, 20, ActionTargets.Self, 2) .AddOption(OffensiveStrategy.Delay, "Delay", "Delay use of Fight or Flight", 0, 0, ActionTargets.None, 2) - .AddAssociatedActions(PLD.AID.FightOrFlight); + .AddAssociatedActions(AID.FightOrFlight); //Requiescat Strategy: Control the use of Requiescat ability res.Define(Track.Requiescat).As("Requiescat", "Req", uiPriority: 170) .AddOption(OffensiveStrategy.Automatic, "Automatic", "Use Requiescat normally") .AddOption(OffensiveStrategy.Force, "Force", "Force use of Requiescat / Imperator", 60, 20, ActionTargets.Self, 68) .AddOption(OffensiveStrategy.Delay, "Delay", "Delay use of Requiescat / Imperator", 0, 0, ActionTargets.None, 68) - .AddAssociatedActions(PLD.AID.Requiescat, PLD.AID.Imperator); + .AddAssociatedActions(AID.Requiescat, AID.Imperator); //Spirits Within Strategy: Manage usage of Spirits Within ability res.Define(Track.SpiritsWithin).As("Spirits Within", "S.Within", uiPriority: 150) .AddOption(OffensiveStrategy.Automatic, "Automatic", "Use Spirits Within normally") .AddOption(OffensiveStrategy.Force, "Force", "Force use of Spirits Within / Expiacion", 30, 0, ActionTargets.Hostile, 30) .AddOption(OffensiveStrategy.Delay, "Delay", "Delay use of Spirits Within / Expiacion", 0, 0, ActionTargets.None, 30) - .AddAssociatedActions(PLD.AID.SpiritsWithin, PLD.AID.Expiacion); + .AddAssociatedActions(AID.SpiritsWithin, AID.Expiacion); //Circle of Scorn Strategy: Control the use of Circle of Scorn res.Define(Track.CircleOfScorn).As("Circle of Scorn Strategy", "Circle of Scorn", uiPriority: 150) .AddOption(OffensiveStrategy.Automatic, "Automatic", "Use Circle of Scorn normally") .AddOption(OffensiveStrategy.Force, "Force", "Force use of Circle of Scorn ASAP", 60, 15, ActionTargets.Hostile, 50) .AddOption(OffensiveStrategy.Delay, "Delay", "Delay use of Circle of Scorn", 0, 0, ActionTargets.None, 50) - .AddAssociatedActions(PLD.AID.CircleOfScorn); + .AddAssociatedActions(AID.CircleOfScorn); //Goring Blade Strategy: Manage the Goring Blade action res.Define(Track.GoringBlade).As("Goring Blade Strategy", "Sonic Break", uiPriority: 150) .AddOption(OffensiveStrategy.Automatic, "Automatic", "Use Goring Blade normally") .AddOption(OffensiveStrategy.Force, "Force", "Force use of Goring Blade ASAP", 0, 30, ActionTargets.Hostile, 54) .AddOption(OffensiveStrategy.Delay, "Delay", "Delay use of Goring Blade", 0, 0, ActionTargets.None, 54) - .AddAssociatedActions(PLD.AID.GoringBlade); + .AddAssociatedActions(AID.GoringBlade); //Holy Spirit Strategy: Control usage of Holy Spirit ability res.Define(Track.HolySpirit).As("Holy Spirit Strategy", "Holy Spirit", uiPriority: 140) .AddOption(OffensiveStrategy.Automatic, "Automatic", "Use Holy Spirit normally") .AddOption(OffensiveStrategy.Force, "Force", "Force use of Holy Spirit ASAP", 0, 0, ActionTargets.Hostile, 30) .AddOption(OffensiveStrategy.Delay, "Delay", "Delay use of Holy Spirit", 0, 0, ActionTargets.None, 30) - .AddAssociatedActions(PLD.AID.HolySpirit); + .AddAssociatedActions(AID.HolySpirit); //Holy Circle Strategy: Manage usage of Holy Circle ability res.Define(Track.HolyCircle).As("Holy Circle Strategy", "Holy Circle", uiPriority: 140) .AddOption(OffensiveStrategy.Automatic, "Automatic", "Use Holy Circle normally") .AddOption(OffensiveStrategy.Force, "Force", "Force use of Holy Circle ASAP", 0, 0, ActionTargets.Hostile, 72) .AddOption(OffensiveStrategy.Delay, "Delay", "Delay use of Holy Circle", 0, 0, ActionTargets.None, 72) - .AddAssociatedActions(PLD.AID.HolyCircle); + .AddAssociatedActions(AID.HolyCircle); //Blade of Honor Strategy: Manage usage of Blade of Honor ability res.Define(Track.BladeOfHonor).As("Blade of Honor Strategy", "Blade of Honor", uiPriority: 150) .AddOption(OffensiveStrategy.Automatic, "Automatic", "Use Blade of Honor normally") .AddOption(OffensiveStrategy.Force, "Force", "Force use of Blade of Honor ASAP", 0, 0, ActionTargets.Hostile, 100) .AddOption(OffensiveStrategy.Delay, "Delay", "Delay use of Blade of Honor", 0, 0, ActionTargets.None, 100) - .AddAssociatedActions(PLD.AID.BladeOfHonor); + .AddAssociatedActions(AID.BladeOfHonor); return res; @@ -297,20 +299,20 @@ public static RotationModuleDefinition Definition() public bool isDivineMightExpiring; //Indicates if the Divine Might buff is nearing expiration public bool isAtonementExpiring; //Indicates if any Atonement buffs are about to expire - public PLD.AID NextGCD; //The next action to be executed during the global cooldown (for cartridge management) + public AID NextGCD; //The next action to be executed during the global cooldown (for cartridge management) private GCDPriority NextGCDPrio; //Priority of the next global cooldown action for decision-making on cooldowns //Check if the desired ability is unlocked - private bool Unlocked(PLD.AID aid) => ActionUnlocked(ActionID.MakeSpell(aid)); + private bool Unlocked(AID aid) => ActionUnlocked(ActionID.MakeSpell(aid)); //Get remaining cooldown time for the specified action - private float CD(PLD.AID aid) => World.Client.Cooldowns[ActionDefinitions.Instance.Spell(aid)!.MainCooldownGroup].Remaining; + private float CD(AID aid) => World.Client.Cooldowns[ActionDefinitions.Instance.Spell(aid)!.MainCooldownGroup].Remaining; //Check if we can fit an additional GCD within the provided deadline private bool CanFitGCD(float deadline, int extraGCDs = 0) => GCD + GCDLength * extraGCDs < deadline; //Get the last action used in the combo sequence - private PLD.AID ComboLastMove => (PLD.AID)World.Client.ComboState.Action; + private AID ComboLastMove => (AID)World.Client.ComboState.Action; //Check if the target is within melee range (3 yalms) private bool In3y(Actor? target) => Player.DistanceToHitbox(target) <= 3; @@ -319,7 +321,7 @@ public static RotationModuleDefinition Definition() private bool In5y(Actor? target) => Player.DistanceToHitbox(target) <= 4.75; //Check if the desired action is ready (cooldown less than 0.6 seconds) - private bool ActionReady(PLD.AID aid) => World.Client.Cooldowns[ActionDefinitions.Instance.Spell(aid)!.MainCooldownGroup].Remaining < 0.6f; + private bool ActionReady(AID aid) => World.Client.Cooldowns[ActionDefinitions.Instance.Spell(aid)!.MainCooldownGroup].Remaining < 0.6f; //Check if this is the first GCD in combat private bool IsFirstGCD() => !Player.InCombat || (World.CurrentTime - Manager.CombatStart).TotalSeconds < 0.1f; @@ -344,24 +346,24 @@ public override void Execute(StrategyValues strategy, Actor? primaryTarget, floa BladeComboStep = gauge.ConfiteorComboStep; //Get the current step in the Confiteor/Blades combo //Buff and cooldown management - fofCD = CD(PLD.AID.FightOrFlight); //Remaining cooldown for the Fight or Flight ability - fofLeft = SelfStatusLeft(PLD.SID.FightOrFlight); //Remaining duration of the Fight or Flight buff - reqCD = CD(PLD.AID.Requiescat); //Remaining cooldown for the Requiescat ability + fofCD = CD(AID.FightOrFlight); //Remaining cooldown for the Fight or Flight ability + fofLeft = SelfStatusLeft(SID.FightOrFlight); //Remaining duration of the Fight or Flight buff + reqCD = CD(AID.Requiescat); //Remaining cooldown for the Requiescat ability playerMP = Player.HPMP.CurMP; //Current mana points (MP) of the player //Buff status checks hasFoF = fofCD is >= 40 and <= 60; //Check if the Fight or Flight buff is active (within cooldown range) - hasReq = HasEffect(PLD.AID.Requiescat); //Check if the Requiescat buff is active - hasMight = HasEffect(PLD.SID.DivineMight); //Check if the Divine Might buff is active + hasReq = HasEffect(AID.Requiescat); //Check if the Requiescat buff is active + hasMight = HasEffect(SID.DivineMight); //Check if the Divine Might buff is active hasMPforMight = playerMP >= 1000; //Check if there is enough MP for Holy Spirit while using Divine Might hasMPforReq = playerMP >= 1000 * 3.6; //Check if there is enough MP for Holy Spirit under the Requiescat buff //Phase and condition monitoring - isDivineMightExpiring = SelfStatusLeft(PLD.SID.DivineMight) < 6; //Check if the Divine Might buff is about to expire + isDivineMightExpiring = SelfStatusLeft(SID.DivineMight) < 6; //Check if the Divine Might buff is about to expire isAtonementExpiring = - HasEffect(PLD.SID.AtonementReady) && SelfStatusLeft(PLD.SID.AtonementReady) < 6 || - HasEffect(PLD.SID.SupplicationReady) && SelfStatusLeft(PLD.SID.SupplicationReady) < 6 || - HasEffect(PLD.SID.SepulchreReady) && SelfStatusLeft(PLD.SID.SepulchreReady) < 6; //Check if any Atonement buffs are close to expiring + HasEffect(SID.AtonementReady) && SelfStatusLeft(SID.AtonementReady) < 6 || + HasEffect(SID.SupplicationReady) && SelfStatusLeft(SID.SupplicationReady) < 6 || + HasEffect(SID.SepulchreReady) && SelfStatusLeft(SID.SepulchreReady) < 6; //Check if any Atonement buffs are close to expiring GCDLength = ActionSpeed.GCDRounded(World.Client.PlayerStats.SkillSpeed, World.Client.PlayerStats.Haste, Player.Level); //Calculate GCD based on skill speed and haste @@ -372,7 +374,7 @@ public override void Execute(StrategyValues strategy, Actor? primaryTarget, floa (RaidBuffsLeft, RaidBuffsIn) = EstimateRaidBuffTimings(primaryTarget); //Estimate remaining and incoming raid buff durations //Next global cooldown action - NextGCD = PLD.AID.None; //Initialize next action as none + NextGCD = AID.None; //Initialize next action as none NextGCDPrio = GCDPriority.None; //Set next GCD priority to none //Define the AoE strategy and determine the number of targets hit @@ -399,104 +401,104 @@ public override void Execute(StrategyValues strategy, Actor? primaryTarget, floa }; //Check GCD (Global Cooldown) conditions for various abilities - var canFoF = Unlocked(PLD.AID.FightOrFlight) && ActionReady(PLD.AID.FightOrFlight); //Fight or Flight ability - var canReq = Unlocked(PLD.AID.Requiescat) && ActionReady(PLD.AID.Requiescat); //Requiescat ability - var canScorn = Unlocked(PLD.AID.CircleOfScorn) && ActionReady(PLD.AID.CircleOfScorn); //Circle of Scorn ability - var canSpirit = Unlocked(PLD.AID.SpiritsWithin) && ActionReady(PLD.AID.SpiritsWithin); //Spirits Within ability - var canGB = Unlocked(PLD.AID.GoringBlade) && HasEffect(PLD.SID.GoringBladeReady); //Goring Blade ability readiness - var canHS = Unlocked(PLD.AID.HolySpirit); //Holy Spirit ability - var canHC = Unlocked(PLD.AID.HolyCircle); //Holy Circle ability - var canAtone = Unlocked(PLD.AID.Atonement) && HasEffect(PLD.SID.AtonementReady); //Atonement ability readiness - var canDash = Unlocked(PLD.AID.Intervene) && CD(PLD.AID.Intervene) <= 30; //Intervene ability with cooldown check - var canConfiteor = Unlocked(PLD.AID.Confiteor) && HasEffect(PLD.SID.ConfiteorReady) && BladeComboStep is 0; //Confiteor ability readiness and combo step check - var canBlade = Unlocked(PLD.AID.BladeOfValor) && BladeComboStep is not 0; //Blade abilities, only if combo step is not zero - var canHonor = Unlocked(PLD.AID.BladeOfHonor) && HasEffect(PLD.SID.BladeOfHonorReady); //Blade of Honor ability readiness + var canFoF = Unlocked(AID.FightOrFlight) && ActionReady(AID.FightOrFlight); //Fight or Flight ability + var canReq = Unlocked(AID.Requiescat) && ActionReady(AID.Requiescat); //Requiescat ability + var canScorn = Unlocked(AID.CircleOfScorn) && ActionReady(AID.CircleOfScorn); //Circle of Scorn ability + var canSpirit = Unlocked(AID.SpiritsWithin) && ActionReady(AID.SpiritsWithin); //Spirits Within ability + var canGB = Unlocked(AID.GoringBlade) && HasEffect(SID.GoringBladeReady); //Goring Blade ability readiness + var canHS = Unlocked(AID.HolySpirit); //Holy Spirit ability + var canHC = Unlocked(AID.HolyCircle); //Holy Circle ability + var canAtone = Unlocked(AID.Atonement) && HasEffect(SID.AtonementReady); //Atonement ability readiness + var canDash = Unlocked(AID.Intervene) && CD(AID.Intervene) <= 30; //Intervene ability with cooldown check + var canConfiteor = Unlocked(AID.Confiteor) && HasEffect(SID.ConfiteorReady) && BladeComboStep is 0; //Confiteor ability readiness and combo step check + var canBlade = Unlocked(AID.BladeOfValor) && BladeComboStep is not 0; //Blade abilities, only if combo step is not zero + var canHonor = Unlocked(AID.BladeOfHonor) && HasEffect(SID.BladeOfHonorReady); //Blade of Honor ability readiness //Determine and queue the next combo action based on AoE strategy var (comboAction, comboPrio) = ComboActionPriority(AOEStrategy, AoETargets, burstStrategy, burst.Value.ExpireIn); - QueueGCD(comboAction, comboAction is PLD.AID.TotalEclipse or PLD.AID.Prominence ? Player : primaryTarget, - AOEStrategy is AOEStrategy.ForceST or AOEStrategy.ForceAoE ? GCDPriority.ForcedGCD : comboPrio); + QueueGCD(comboAction, comboAction is AID.TotalEclipse or AID.Prominence ? Player : primaryTarget, + AOEStrategy is PLD.AOEStrategy.ForceST or PLD.AOEStrategy.ForceAoE ? GCDPriority.ForcedGCD : comboPrio); //Execute Fight or Flight if conditions are met var fofStrat = strategy.Option(Track.FightOrFlight).As(); if (!hold && canFoF && ShouldUseFightOrFlight(fofStrat, primaryTarget)) - QueueOGCD(PLD.AID.FightOrFlight, Player, fofStrat is OffensiveStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.FightOrFlight); + QueueOGCD(AID.FightOrFlight, Player, fofStrat is OffensiveStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.FightOrFlight); //Execute Requiescat if conditions are met var reqStrat = strategy.Option(Track.Requiescat).As(); if (!hold && canReq && ShouldUseRequiescat(reqStrat, primaryTarget)) - QueueOGCD(Unlocked(PLD.AID.Imperator) ? PLD.AID.Imperator : PLD.AID.Requiescat, primaryTarget, reqStrat is OffensiveStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.Requiescat); + QueueOGCD(Unlocked(AID.Imperator) ? AID.Imperator : AID.Requiescat, primaryTarget, reqStrat is OffensiveStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.Requiescat); //Execute Confiteor if conditions are met var bladeStrat = strategy.Option(Track.BladeCombo).As(); if (canConfiteor && BladeComboStep is 0 && ShouldUseBladeCombo(bladeStrat, primaryTarget)) - QueueGCD(PLD.AID.Confiteor, primaryTarget, bladeStrat is BladeComboStrategy.ForceConfiteor ? GCDPriority.ForcedGCD : GCDPriority.Confiteor); + QueueGCD(AID.Confiteor, primaryTarget, bladeStrat is BladeComboStrategy.ForceConfiteor ? GCDPriority.ForcedGCD : GCDPriority.Confiteor); //Execute Circle of Scorn if conditions are met var scornStrat = strategy.Option(Track.CircleOfScorn).As(); if (!hold && canScorn && ShouldUseCircleOfScorn(scornStrat, primaryTarget)) - QueueOGCD(PLD.AID.CircleOfScorn, primaryTarget, scornStrat is OffensiveStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.CircleOfScorn); + QueueOGCD(AID.CircleOfScorn, primaryTarget, scornStrat is OffensiveStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.CircleOfScorn); //Execute Spirits Within if conditions are met var spiritsStrat = strategy.Option(Track.SpiritsWithin).As(); if (!hold && canSpirit && ShouldUseSpiritsWithin(spiritsStrat, primaryTarget)) - QueueOGCD(Unlocked(PLD.AID.Expiacion) ? PLD.AID.Expiacion : PLD.AID.SpiritsWithin, primaryTarget, spiritsStrat is OffensiveStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.SpiritsWithin); + QueueOGCD(Unlocked(AID.Expiacion) ? AID.Expiacion : AID.SpiritsWithin, primaryTarget, spiritsStrat is OffensiveStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.SpiritsWithin); //Execute Blade Combo actions based on the current combo step if (canBlade) { if (BladeComboStep is 1) - QueueGCD(PLD.AID.BladeOfFaith, primaryTarget, bladeStrat is BladeComboStrategy.ForceFaith ? GCDPriority.ForcedGCD : GCDPriority.Faith); + QueueGCD(AID.BladeOfFaith, primaryTarget, bladeStrat is BladeComboStrategy.ForceFaith ? GCDPriority.ForcedGCD : GCDPriority.Faith); if (BladeComboStep is 2) - QueueGCD(PLD.AID.BladeOfTruth, primaryTarget, bladeStrat is BladeComboStrategy.ForceTruth ? GCDPriority.ForcedGCD : GCDPriority.Truth); + QueueGCD(AID.BladeOfTruth, primaryTarget, bladeStrat is BladeComboStrategy.ForceTruth ? GCDPriority.ForcedGCD : GCDPriority.Truth); if (BladeComboStep is 3) - QueueGCD(PLD.AID.BladeOfValor, primaryTarget, bladeStrat is BladeComboStrategy.ForceValor ? GCDPriority.ForcedGCD : GCDPriority.Valor); + QueueGCD(AID.BladeOfValor, primaryTarget, bladeStrat is BladeComboStrategy.ForceValor ? GCDPriority.ForcedGCD : GCDPriority.Valor); } //Execute Intervene if conditions are met var dashStrat = strategy.Option(Track.Dash).As(); if (canDash && ShouldUseDash(dashStrat, primaryTarget)) - QueueOGCD(PLD.AID.Intervene, primaryTarget, dashStrat is DashStrategy.Force or DashStrategy.GapClose ? OGCDPriority.ForcedOGCD : OGCDPriority.Intervene); + QueueOGCD(AID.Intervene, primaryTarget, dashStrat is DashStrategy.Force or DashStrategy.GapClose ? OGCDPriority.ForcedOGCD : OGCDPriority.Intervene); //Execute Goring Blade if conditions are met var gbStrat = strategy.Option(Track.GoringBlade).As(); if (!hold && canGB && ShouldUseGoringBlade(gbStrat, primaryTarget)) - QueueGCD(PLD.AID.GoringBlade, primaryTarget, gbStrat is OffensiveStrategy.Force ? GCDPriority.ForcedGCD : GCDPriority.GoringBlade); + QueueGCD(AID.GoringBlade, primaryTarget, gbStrat is OffensiveStrategy.Force ? GCDPriority.ForcedGCD : GCDPriority.GoringBlade); //Execute Blade of Honor if conditions are met var bohStrat = strategy.Option(Track.GoringBlade).As(); if (!hold && canHonor && ShouldUseGoringBlade(bohStrat, primaryTarget)) - QueueOGCD(PLD.AID.BladeOfHonor, primaryTarget, bohStrat is OffensiveStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.BladeOfHonor); + QueueOGCD(AID.BladeOfHonor, primaryTarget, bohStrat is OffensiveStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.BladeOfHonor); //Execute Atonement if conditions are met var atoneStrat = strategy.Option(Track.Atonement).As(); if (!hold && canAtone && ShouldUseAtonement(atoneStrat, primaryTarget)) - QueueGCD(PLD.AID.Atonement, primaryTarget, atoneStrat is AtonementStrategy.ForceAtonement ? GCDPriority.ForcedGCD : GCDPriority.Atonement1); + QueueGCD(AID.Atonement, primaryTarget, atoneStrat is AtonementStrategy.ForceAtonement ? GCDPriority.ForcedGCD : GCDPriority.Atonement1); //Execute Atonement Combo actions based on readiness - if (HasEffect(PLD.SID.SupplicationReady)) - QueueGCD(PLD.AID.Supplication, primaryTarget, atoneStrat is AtonementStrategy.ForceSupplication ? GCDPriority.ForcedGCD : GCDPriority.Atonement2); - if (HasEffect(PLD.SID.SepulchreReady)) - QueueGCD(PLD.AID.Sepulchre, primaryTarget, atoneStrat is AtonementStrategy.ForceSepulchre ? GCDPriority.ForcedGCD : GCDPriority.Atonement3); + if (HasEffect(SID.SupplicationReady)) + QueueGCD(AID.Supplication, primaryTarget, atoneStrat is AtonementStrategy.ForceSupplication ? GCDPriority.ForcedGCD : GCDPriority.Atonement2); + if (HasEffect(SID.SepulchreReady)) + QueueGCD(AID.Sepulchre, primaryTarget, atoneStrat is AtonementStrategy.ForceSepulchre ? GCDPriority.ForcedGCD : GCDPriority.Atonement3); //Execute Holy Spirit if conditions are met var hsStrat = strategy.Option(Track.HolySpirit).As(); var hcStrat = strategy.Option(Track.HolyCircle).As(); if (canHS && ShouldUseHolySpirit(hsStrat, primaryTarget)) - QueueGCD(PLD.AID.HolySpirit, primaryTarget, hsStrat is OffensiveStrategy.Force ? GCDPriority.ForcedGCD : GCDPriority.HolySpirit); + QueueGCD(AID.HolySpirit, primaryTarget, hsStrat is OffensiveStrategy.Force ? GCDPriority.ForcedGCD : GCDPriority.HolySpirit); //Execute Holy Circle if conditions are met if (canHC && ShouldUseHolyCircle(hcStrat, primaryTarget)) - QueueGCD(PLD.AID.HolyCircle, primaryTarget, hcStrat is OffensiveStrategy.Force ? GCDPriority.ForcedGCD : GCDPriority.HolyCircle); + QueueGCD(AID.HolyCircle, primaryTarget, hcStrat is OffensiveStrategy.Force ? GCDPriority.ForcedGCD : GCDPriority.HolyCircle); if (!canHC && canHS) - QueueGCD(PLD.AID.HolySpirit, primaryTarget, hsStrat is OffensiveStrategy.Force ? GCDPriority.ForcedGCD : GCDPriority.HolySpirit); + QueueGCD(AID.HolySpirit, primaryTarget, hsStrat is OffensiveStrategy.Force ? GCDPriority.ForcedGCD : GCDPriority.HolySpirit); //Execute ranged skills based on strategy var rangedStrat = strategy.Option(Track.Ranged).As(); if (ShouldUseRangedLob(primaryTarget, rangedStrat)) - QueueGCD(PLD.AID.ShieldLob, primaryTarget, rangedStrat is RangedStrategy.Force ? GCDPriority.ForcedGCD : GCDPriority.NormalGCD); + QueueGCD(AID.ShieldLob, primaryTarget, rangedStrat is RangedStrategy.Force ? GCDPriority.ForcedGCD : GCDPriority.NormalGCD); if (ShouldUseRangedCast(primaryTarget, rangedStrat)) - QueueGCD(PLD.AID.HolySpirit, primaryTarget, rangedStrat is RangedStrategy.ForceCast ? GCDPriority.ForcedGCD : GCDPriority.NormalGCD); + QueueGCD(AID.HolySpirit, primaryTarget, rangedStrat is RangedStrategy.ForceCast ? GCDPriority.ForcedGCD : GCDPriority.NormalGCD); //Execute potion if conditions are met if (ShouldUsePotion(strategy.Option(Track.Potion).As())) @@ -505,7 +507,7 @@ public override void Execute(StrategyValues strategy, Actor? primaryTarget, floa } //Method to queue a GCD (Global Cooldown) action - private void QueueGCD(PLD.AID aid, Actor? target, GCDPriority prio) + private void QueueGCD(AID aid, Actor? target, GCDPriority prio) { //Check if the priority for this action is valid if (prio != GCDPriority.None) @@ -523,7 +525,7 @@ private void QueueGCD(PLD.AID aid, Actor? target, GCDPriority prio) } //Method to queue an OGCD (Off Global Cooldown) action - private void QueueOGCD(PLD.AID aid, Actor? target, OGCDPriority prio, float basePrio = ActionQueue.Priority.Medium) + private void QueueOGCD(AID aid, Actor? target, OGCDPriority prio, float basePrio = ActionQueue.Priority.Medium) { //Check if the priority for this action is valid if (prio != OGCDPriority.None) @@ -534,28 +536,28 @@ private void QueueOGCD(PLD.AID aid, Actor? target, OGCDPriority prio, float base } //Method to determine the next single-target action based on the last action used - private PLD.AID NextComboSingleTarget() => ComboLastMove switch + private AID NextComboSingleTarget() => ComboLastMove switch { - PLD.AID.RiotBlade => Unlocked(PLD.AID.RoyalAuthority) ? PLD.AID.RoyalAuthority : PLD.AID.RageOfHalone, //If Riot Blade was last used, choose Royal Authority if available, otherwise Rage of Halone - PLD.AID.FastBlade => PLD.AID.RiotBlade, //If Fast Blade was last used, go back to Riot Blade - _ => PLD.AID.FastBlade, //Default to Fast Blade if no recognized last action + AID.RiotBlade => Unlocked(AID.RoyalAuthority) ? AID.RoyalAuthority : AID.RageOfHalone, //If Riot Blade was last used, choose Royal Authority if available, otherwise Rage of Halone + AID.FastBlade => AID.RiotBlade, //If Fast Blade was last used, go back to Riot Blade + _ => AID.FastBlade, //Default to Fast Blade if no recognized last action }; //Method to determine the next AoE action based on the last action used - private PLD.AID NextComboAoE() => ComboLastMove switch + private AID NextComboAoE() => ComboLastMove switch { - PLD.AID.TotalEclipse => PLD.AID.Prominence, //If Total Eclipse was last used, use Prominence next - _ => PLD.AID.TotalEclipse, //Default to Total Eclipse if no recognized last action + AID.TotalEclipse => AID.Prominence, //If Total Eclipse was last used, use Prominence next + _ => AID.TotalEclipse, //Default to Total Eclipse if no recognized last action }; //Method to determine the next combo action and its priority based on AoE strategy, target count, and burst strategy - private (PLD.AID, GCDPriority) ComboActionPriority(AOEStrategy aoeStrategy, int AoETargets, BurstStrategy burstStrategy, float burstStrategyExpire) + private (AID, GCDPriority) ComboActionPriority(AOEStrategy aoeStrategy, int AoETargets, BurstStrategy burstStrategy, float burstStrategyExpire) { //Determine how many combo steps are remaining based on the last action var comboStepsRemaining = ComboLastMove switch { - PLD.AID.FastBlade => Unlocked(PLD.AID.RiotBlade) ? 2 : Unlocked(PLD.AID.RoyalAuthority) ? 1 : 0, //Fast Blade allows up to 2 or 1 additional combo actions based on availability - PLD.AID.TotalEclipse => Unlocked(PLD.AID.Prominence) ? 1 : 0, //Total Eclipse allows 1 more combo action if Prominence is unlocked + AID.FastBlade => Unlocked(AID.RiotBlade) ? 2 : Unlocked(AID.RoyalAuthority) ? 1 : 0, //Fast Blade allows up to 2 or 1 additional combo actions based on availability + AID.TotalEclipse => Unlocked(AID.Prominence) ? 1 : 0, //Total Eclipse allows 1 more combo action if Prominence is unlocked _ => 0 //Default to no combo steps if the last action doesn't match }; @@ -563,10 +565,10 @@ private void QueueOGCD(PLD.AID aid, Actor? target, OGCDPriority prio, float base if (comboStepsRemaining > 0 && !CanFitGCD(World.Client.ComboState.Remaining)) comboStepsRemaining = 0; //Reset combo steps if not enough time to complete them - var doingAOECombo = ComboLastMove == PLD.AID.TotalEclipse; //Track if currently performing an AoE combo + var doingAOECombo = ComboLastMove == AID.TotalEclipse; //Track if currently performing an AoE combo //Determine if an AoE action is desirable based on target count and strategy - var wantAOEAction = Unlocked(PLD.AID.TotalEclipse) && aoeStrategy switch + var wantAOEAction = Unlocked(AID.TotalEclipse) && aoeStrategy switch { AOEStrategy.UseST => false, //Explicitly using single-target strategy, so AoE action is not desired AOEStrategy.ForceST => false, //Forcing single-target, so AoE action is not desired @@ -618,7 +620,7 @@ private void QueueOGCD(PLD.AID aid, Actor? target, OGCDPriority prio, float base private bool ShouldUseFightOrFlight(OffensiveStrategy strategy, Actor? target) => strategy switch { OffensiveStrategy.Automatic => - Player.InCombat && target != null && ActionReady(PLD.AID.FightOrFlight) && CombatTimer >= GCDLength * 2 + 0.5f, //Use if in combat, target is valid, action is ready + Player.InCombat && target != null && ActionReady(AID.FightOrFlight) && CombatTimer >= GCDLength * 2 + 0.5f, //Use if in combat, target is valid, action is ready OffensiveStrategy.Force => true, //Force OffensiveStrategy.Delay => false, //Delay usage _ => false @@ -628,7 +630,7 @@ private void QueueOGCD(PLD.AID aid, Actor? target, OGCDPriority prio, float base private bool ShouldUseRequiescat(OffensiveStrategy strategy, Actor? target) => strategy switch { OffensiveStrategy.Automatic => - Player.InCombat && target != null && ActionReady(PLD.AID.Requiescat) && hasFoF, //Use if in combat, target is valid, action is ready, and has Fight or Flight + Player.InCombat && target != null && ActionReady(AID.Requiescat) && hasFoF, //Use if in combat, target is valid, action is ready, and has Fight or Flight OffensiveStrategy.Force => true, //Force OffensiveStrategy.Delay => false, //Delay usage _ => false @@ -639,7 +641,7 @@ private void QueueOGCD(PLD.AID aid, Actor? target, OGCDPriority prio, float base { OffensiveStrategy.Automatic => Player.InCombat && In3y(target) && fofCD is < 57.55f and > 17 && //Use if in combat, target is in range, and cooldown is within limits - ActionReady(Unlocked(PLD.AID.Expiacion) ? PLD.AID.Expiacion : PLD.AID.SpiritsWithin), //Action is ready, prioritizing Expiacion if unlocked + ActionReady(Unlocked(AID.Expiacion) ? AID.Expiacion : AID.SpiritsWithin), //Action is ready, prioritizing Expiacion if unlocked OffensiveStrategy.Force => true, //Force OffensiveStrategy.Delay => false, //Delay usage _ => false @@ -649,7 +651,7 @@ private void QueueOGCD(PLD.AID aid, Actor? target, OGCDPriority prio, float base private bool ShouldUseCircleOfScorn(OffensiveStrategy strategy, Actor? target) => strategy switch { OffensiveStrategy.Automatic => - Player.InCombat && ActionReady(PLD.AID.CircleOfScorn) && In5y(target) && fofCD is < 57.55f and > 17, //Use if in combat, action is ready, target is within 5y, and cooldown is within limits + Player.InCombat && ActionReady(AID.CircleOfScorn) && In5y(target) && fofCD is < 57.55f and > 17, //Use if in combat, action is ready, target is within 5y, and cooldown is within limits OffensiveStrategy.Force => true, //Force OffensiveStrategy.Delay => false, //Delay usage _ => false @@ -669,8 +671,8 @@ private void QueueOGCD(PLD.AID aid, Actor? target, OGCDPriority prio, float base private bool ShouldUseBladeCombo(BladeComboStrategy strategy, Actor? target) => strategy switch { BladeComboStrategy.Automatic => - Player.InCombat && HasEffect(PLD.SID.ConfiteorReady) && hasFoF && BladeComboStep is 0, //Use if in combat, Confiteor is ready, has Fight or Flight, and it's the first step - BladeComboStrategy.ForceConfiteor => HasEffect(PLD.SID.ConfiteorReady) && BladeComboStep is 0, //Force use of Confiteor if ready and it's the first step + Player.InCombat && HasEffect(SID.ConfiteorReady) && hasFoF && BladeComboStep is 0, //Use if in combat, Confiteor is ready, has Fight or Flight, and it's the first step + BladeComboStrategy.ForceConfiteor => HasEffect(SID.ConfiteorReady) && BladeComboStep is 0, //Force use of Confiteor if ready and it's the first step BladeComboStrategy.ForceFaith => BladeComboStep is 1, //Force use of Faith if it's the second step BladeComboStrategy.ForceTruth => BladeComboStep is 2, //Force use of Truth if it's the third step BladeComboStrategy.ForceValor => BladeComboStep is 3, //Force use of Valor if it's the fourth step @@ -682,10 +684,10 @@ private void QueueOGCD(PLD.AID aid, Actor? target, OGCDPriority prio, float base private bool ShouldUseAtonement(AtonementStrategy strategy, Actor? target) => strategy switch { AtonementStrategy.Automatic => - Player.InCombat && In3y(target) && HasEffect(PLD.SID.AtonementReady), //Use if in combat, target is in range, and Atonement is ready - AtonementStrategy.ForceAtonement => HasEffect(PLD.SID.AtonementReady), //Force use of Atonement if ready - AtonementStrategy.ForceSupplication => HasEffect(PLD.SID.SupplicationReady), //Force use of Supplication if ready - AtonementStrategy.ForceSepulchre => HasEffect(PLD.SID.SepulchreReady), //Force use of Sepulchre if ready + Player.InCombat && In3y(target) && HasEffect(SID.AtonementReady), //Use if in combat, target is in range, and Atonement is ready + AtonementStrategy.ForceAtonement => HasEffect(SID.AtonementReady), //Force use of Atonement if ready + AtonementStrategy.ForceSupplication => HasEffect(SID.SupplicationReady), //Force use of Supplication if ready + AtonementStrategy.ForceSepulchre => HasEffect(SID.SepulchreReady), //Force use of Sepulchre if ready AtonementStrategy.Delay => false, //Delay usage _ => false }; @@ -718,7 +720,7 @@ private void QueueOGCD(PLD.AID aid, Actor? target, OGCDPriority prio, float base DashStrategy.Automatic => Player.InCombat && target != null && hasFoF, //Use in Fight or Flight DashStrategy.Force => true, //Force - DashStrategy.Conserve1 => CD(PLD.AID.Intervene) > 30, //Use if cooldown is greater than 30 seconds + DashStrategy.Conserve1 => CD(AID.Intervene) > 30, //Use if cooldown is greater than 30 seconds DashStrategy.GapClose => !In3y(target), //Use if target is out of range _ => false }; @@ -729,8 +731,8 @@ private bool IsPotionAlignedWithNM() { //Use potion before FoF/Req in opener //Use for 6m window - return ActionReady(PLD.AID.FightOrFlight) && - (ActionReady(PLD.AID.Requiescat) || //Opener + return ActionReady(AID.FightOrFlight) && + (ActionReady(AID.Requiescat) || //Opener reqCD < 15); //Use if Requiescat is on cooldown for less than 15 seconds } diff --git a/BossMod/Autorotation/akechi/PvP/GNB.cs b/BossMod/Autorotation/akechi/PvP/GNB.cs new file mode 100644 index 0000000000..b7494a21d1 --- /dev/null +++ b/BossMod/Autorotation/akechi/PvP/GNB.cs @@ -0,0 +1,411 @@ +using FFXIVClientStructs.FFXIV.Client.Game.Gauge; +using FFXIVClientStructs.FFXIV.Component.GUI; +using AID = BossMod.GNB.AID; +using SID = BossMod.GNB.SID; + +namespace BossMod.Autorotation.akechi.PvP; +//Contribution by Akechi +//Discord @akechdz or 'Akechi' on Puni.sh for maintenance + +public sealed class GNB(RotationModuleManager manager, Actor player) : RotationModule(manager, player) +{ + #region Enums: Abilities / Strategies + //Actions tracked for Cooldown Planner execution + public enum Track + { + Burst, //Burst tracking + Combo, //Solid Barrel combo tracking + LimitBreak, //Limit Break tracking + GnashingFang, //Gnashing Fang action tracking + FatedCircle, //Fated Circle ability tracking + RoughDivide, //Rough Divide ability tracking + Zone, //Blasting Zone ability tracking + Corundum, //Heart of Corundum ability tracking + } + + public enum BurstStrategy + { + Automatic, //Automatically execute based on conditions + Force, //Force burst actions regardless of conditions + Hold //Conserve resources and cooldowns + } + + public enum ComboStrategy + { + Automatic, + Force, + Hold + } + + public enum LimitBreakStrategy + { + Automatic, + Force, + Hold + } + + public enum ElixirStrategy + { + Automatic, + Close, + Mid, + Far, + Force, + Hold + } + + public enum OffensiveStrategy + { + Automatic, //Automatically decide when to use offensive abilities + Force, //Force the use of offensive abilities regardless of conditions + Delay //Delay the use of offensive abilities for strategic reasons + } + + #endregion + + public static RotationModuleDefinition Definition() + { + //Module title & signature + var res = new RotationModuleDefinition("GNB (PvP)", "PvP Rotation Module", "Standard rotation (Akechi)", "Akechi", RotationModuleQuality.Basic, BitMask.Build((int)Class.GNB), 100); + + #region Custom strategies + //Burst strategy + res.Define(Track.Burst).As("Burst", uiPriority: 190) + .AddOption(BurstStrategy.Automatic, "Automatic", "Use everything optimally") + .AddOption(BurstStrategy.Force, "Force", "Force everything") + .AddOption(BurstStrategy.Hold, "Hold", "Hold everything"); + //Combo strategy + res.Define(Track.Combo).As("Combo", uiPriority: 190) + .AddOption(ComboStrategy.Automatic, "Automatic", "Use combo optimally") + .AddOption(ComboStrategy.Force, "Force", "Force combo") + .AddOption(ComboStrategy.Hold, "Hold", "Hold combo"); + //Limit Break strategy + res.Define(Track.LimitBreak).As("Limit Break", uiPriority: 190) + .AddOption(LimitBreakStrategy.Automatic, "Automatic", "Use Limit Break optimally") + .AddOption(LimitBreakStrategy.Force, "Force", "Force Limit Break") + .AddOption(LimitBreakStrategy.Hold, "Hold", "Hold Limit Break"); + #endregion + + #region Offensive Strategies + res.Define(Track.GnashingFang).As("Gnashing Fang", uiPriority: 150) + .AddOption(OffensiveStrategy.Automatic, "Automatic", "Use normally") + .AddOption(OffensiveStrategy.Force, "Force", "Force", 0, 0, ActionTargets.Hostile, 30) + .AddOption(OffensiveStrategy.Delay, "Delay", "Delay", 0, 0, ActionTargets.None, 30) + .AddAssociatedActions(AID.GnashingFangPvP); + res.Define(Track.FatedCircle).As("Fated Circle", uiPriority: 150) + .AddOption(OffensiveStrategy.Automatic, "Automatic", "Use normally") + .AddOption(OffensiveStrategy.Force, "Force", "Force", 0, 0, ActionTargets.Hostile, 30) + .AddOption(OffensiveStrategy.Delay, "Delay", "Delay", 0, 0, ActionTargets.None, 30) + .AddAssociatedActions(AID.FatedCirclePvP); + res.Define(Track.RoughDivide).As("Rough Divide", uiPriority: 150) + .AddOption(OffensiveStrategy.Automatic, "Automatic", "Use normally") + .AddOption(OffensiveStrategy.Force, "Force", "Force", 0, 0, ActionTargets.Hostile, 30) + .AddOption(OffensiveStrategy.Delay, "Delay", "Delay", 0, 0, ActionTargets.None, 30) + .AddAssociatedActions(AID.RoughDividePvP); + res.Define(Track.Zone).As("Zone", uiPriority: 150) + .AddOption(OffensiveStrategy.Automatic, "Automatic", "Use normally") + .AddOption(OffensiveStrategy.Force, "Force", "Force", 0, 0, ActionTargets.Hostile, 30) + .AddOption(OffensiveStrategy.Delay, "Delay", "Delay", 0, 0, ActionTargets.None, 30) + .AddAssociatedActions(AID.BlastingZonePvP); + res.Define(Track.Corundum).As("Corundum", uiPriority: 150) + .AddOption(OffensiveStrategy.Automatic, "Automatic", "Use normally") + .AddOption(OffensiveStrategy.Force, "Force", "Force", 0, 0, ActionTargets.Hostile, 30) + .AddOption(OffensiveStrategy.Delay, "Delay", "Delay", 0, 0, ActionTargets.None, 30) + .AddAssociatedActions(AID.HeartOfCorundumPvP); + #endregion + + return res; + + } + + #region Priorities + //Priority for GCDs used + public enum GCDPriority + { + None = 0, + KeenEdge = 325, //1 + BrutalShell = 320, //2 + SolidBarrel = 315, //3 + BurstStrike = 340, //4 + Combo = 350, + GnashingFang = 400, + FatedCircle = 450, + ForcedGCD = 900, + } + //Priority for oGCDs used + public enum OGCDPriority + { + None = 0, + RoughDivide = 400, + Zone = 420, + Corundum = 440, + Continuation = 500, + LB = 600, + ForcedOGCD = 900, + } + #endregion + + #region Placeholders for Variables + //Cooldown Related + private float nmLeft; //Time left on No Mercy buff (20s base) + private float rdCD; //Time left on No Mercy cooldown (60s base) + private bool hasNM; //Checks self for No Mercy buff + private bool hasBlast; //Checks self for Ready To Blast buff + private bool hasRaze; //Checks self for Ready To Raze buff + private bool hasRip; //Checks self for Ready To Rip buff + private bool hasTear; //Checks self for Ready To Tear buff + private bool hasGouge; //Checks self for Ready To Gouge buff + private bool can1; //can Keen Edge + private bool can2; //can Brutal Shell + private bool can3; //can Solid Barrel + private bool can4; //can Burst Strike + private bool canGF; //can Gnashing Fang + private bool canFC; //can Fated Circle + private bool canZone; //can Blasting Zone + private bool canHyper; //can Hypervelocity + private bool canBrand; //can Fated Brand + private bool canRip; //can Jugular Rip + private bool canTear; //can Abdomen Tear + private bool canGouge; //can Eye Gouge + + //Misc + public bool LBready; //Checks if Limit Break is ready + public float GFcomboStep; //Current Gnashing Fang combo step (0-3) + public float comboStep; //Current combo step (0-4) + public bool inCombo; //Checks if in combo + public bool inGF; //Checks if in Gnashing Fang combo + public float GCDLength; //Current GCD length, adjusted by skill speed/haste (2.5s baseline) + public AID NextGCD; //Next global cooldown action to be used (needed for cartridge management) + private GCDPriority NextGCDPrio; //Priority of the next GCD, used for decision making on cooldowns + #endregion + + #region Module Helpers + private float CD(AID aid) => World.Client.Cooldowns[ActionDefinitions.Instance.Spell(aid)!.MainCooldownGroup].Remaining; //Get remaining cooldown time for the specified action + private bool CanFitGCD(float deadline, int extraGCDs = 0) => GCD + GCDLength * extraGCDs < deadline; //Check if we can fit an additional GCD within the provided deadline + private AID ComboLastMove => (AID)World.Client.ComboState.Action; //Get the last action used in the combo sequence + private bool In3y(Actor? target) => Player.DistanceToHitbox(target) <= 2.9; //Check if the target is within melee range (3 yalms) + private bool In5y(Actor? target) => Player.DistanceToHitbox(target) <= 4.9; //Check if the target is within 5 yalms + private bool In20y(Actor? target) => Player.DistanceToHitbox(target) <= 19.9; //Check if the target is within 20 yalms + private bool In30y(Actor? target) => Player.DistanceToHitbox(target) <= 29.9; //Check if the target is within 30 yalms + private bool IsOffCooldown(AID aid) => World.Client.Cooldowns[ActionDefinitions.Instance.Spell(aid)!.MainCooldownGroup].Remaining < 0.6f; //Check if the desired action is ready (cooldown less than 0.6 seconds) + private int NumTargetsHitByAoE() => Hints.NumPriorityTargetsInAOECircle(Player.Position, 5); //Returns the number of targets hit by AoE within a 5-yalm radius around the player + public bool HasEffect(SID sid) => SelfStatusLeft(sid) > 0; //Check if the player has the specified status effect + public bool TargetHasEffect(SID sid, Actor? target) => StatusDetails(target, sid, Player.InstanceID, 1000).Left > 0; //Check if the target has the specified status effect + public AID LimitBreak => HasEffect(SID.RelentlessRushPvP) ? AID.TerminalTriggerPvP : AID.RelentlessRushPvP; + #endregion + + public override void Execute(StrategyValues strategy, Actor? primaryTarget, float estimatedAnimLockDelay, bool isMoving) //Executes our actions + { + #region Variables + var gauge = World.Client.GetGauge(); //Retrieve Gunbreaker gauge + var GunStep = gauge.AmmoComboStep; + + rdCD = CD(AID.RoughDividePvP); //Rough Divide cooldown (14s) + nmLeft = SelfStatusLeft(SID.NoMercyPvP, 7); //Remaining time for No Mercy buff (7s) + hasNM = nmLeft > 0; //Checks if No Mercy is active + hasBlast = HasEffect(SID.ReadyToBlastPvP); //Checks for Ready To Blast buff + hasRaze = HasEffect(SID.ReadyToRazePvP); //Checks for Ready To Raze buff + hasRip = HasEffect(SID.ReadyToRipPvP) || GunStep == 1; //Checks for Ready To Rip buff + hasTear = HasEffect(SID.ReadyToTearPvP) || GunStep == 2; //Checks for Ready To Tear buff + hasGouge = HasEffect(SID.ReadyToGougePvP); //Checks for Ready To Gouge buff + LBready = World.Party.LimitBreakLevel >= 1; //Checks if Limit Break is ready + //Misc + GFcomboStep = ComboLastMove switch + { + AID.WickedTalonPvP => 3, + AID.SavageClawPvP => 2, + AID.GnashingFangPvP => 1, + _ => 0 + }; + comboStep = ComboLastMove switch + { + AID.BurstStrikePvP => 0, + AID.SolidBarrelPvP => 3, + AID.BrutalShellPvP => 2, + AID.KeenEdgePvP => 1, + _ => 0 + }; + inCombo = comboStep > 0; + inGF = GFcomboStep > 0; + GCDLength = ActionSpeed.GCDRounded(World.Client.PlayerStats.SkillSpeed, World.Client.PlayerStats.Haste, Player.Level); //GCD based on skill speed and haste + NextGCD = AID.None; //Next global cooldown action to be used + NextGCDPrio = GCDPriority.None; //Priority of the next GCD, used for decision making on cooldowns + + #region Minimal Requirements + can1 = ComboLastMove is AID.BurstStrikePvP; //KeenEdge conditions + can2 = ComboLastMove is AID.KeenEdgePvP; //BrutalShell conditions + can3 = ComboLastMove is AID.BrutalShellPvP; //SolidBarrel conditions + can4 = ComboLastMove is AID.SolidBarrelPvP; //BurstStrike conditions + canGF = IsOffCooldown(AID.GnashingFangPvP); //GnashingFang conditions + canFC = IsOffCooldown(AID.GnashingFangPvP); //FatedCircle conditions + canZone = IsOffCooldown(AID.BlastingZonePvP); //Zone conditions + canHyper = hasBlast && In5y(primaryTarget); //Hypervelocity conditions + canBrand = hasRaze && In5y(primaryTarget); //Fated Brand conditions + canRip = hasRip && In5y(primaryTarget); //Jugular Rip conditions + canTear = hasTear && In5y(primaryTarget); //Abdomen conditions + canGouge = hasGouge && In5y(primaryTarget); //Eye Gouge conditions + #endregion + #endregion + + var burst = strategy.Option(Track.Burst); + var burstStrategy = burst.As(); + var hold = burstStrategy == BurstStrategy.Hold; + + if (strategy.Option(Track.Combo).As() == ComboStrategy.Force) //ST (without overcap protection) + QueueGCD(NextCombo(), primaryTarget, GCDPriority.ForcedGCD); + + #region Rotation Execution + //Determine and queue combo actions + if (!hold && !inGF) + QueueGCD(NextCombo(), primaryTarget, GCDPriority.Combo); + + #region OGCDs + var rdStrat = strategy.Option(Track.RoughDivide).As(); + if (!hold && + ShouldUseRoughDivide(rdStrat, primaryTarget)) + QueueOGCD(AID.RoughDividePvP, primaryTarget, rdStrat is OffensiveStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.RoughDivide); + + var zoneStrat = strategy.Option(Track.Zone).As(); + if (!hold && + ShouldUseZone(zoneStrat, primaryTarget)) + QueueOGCD(AID.BlastingZonePvP, primaryTarget, zoneStrat == OffensiveStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.Zone); + + if (canRip || GunStep == 1) + QueueOGCD(AID.JugularRipPvP, primaryTarget, OGCDPriority.Continuation); + if (canTear || GunStep == 1) + QueueOGCD(AID.AbdomenTearPvP, primaryTarget, OGCDPriority.Continuation); + if (canGouge) + QueueOGCD(AID.EyeGougePvP, primaryTarget, OGCDPriority.Continuation); + if (canHyper) + QueueOGCD(AID.HypervelocityPvP, primaryTarget, OGCDPriority.Continuation); + if (canBrand) + QueueOGCD(AID.FatedBrandPvP, primaryTarget, OGCDPriority.Continuation); + #endregion + + #region GCDs + var gfStrat = strategy.Option(Track.GnashingFang).As(); + if (!hold && + ShouldUseGnashingFang(gfStrat, primaryTarget)) + QueueGCD(AID.GnashingFangPvP, primaryTarget, GCDPriority.GnashingFang); + if (GunStep == 1 && In5y(primaryTarget)) + QueueGCD(AID.SavageClawPvP, primaryTarget, GCDPriority.GnashingFang); + if (GunStep == 2 && In5y(primaryTarget)) + QueueGCD(AID.WickedTalonPvP, primaryTarget, GCDPriority.GnashingFang); + + var fcStrat = strategy.Option(Track.FatedCircle).As(); + if (ShouldUseFatedCircle(fcStrat, primaryTarget)) + QueueGCD(AID.FatedCirclePvP, primaryTarget, fcStrat == OffensiveStrategy.Force ? GCDPriority.ForcedGCD : GCDPriority.FatedCircle); + #endregion + + #endregion + + //Limit Break execution + var lbStrat = strategy.Option(Track.LimitBreak).As(); + if (ShouldUseLimitBreak(lbStrat, primaryTarget)) + QueueOGCD(LimitBreak, primaryTarget, lbStrat == LimitBreakStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.LB); + } + + #region Core Execution Helpers + //QueueGCD execution + private void QueueGCD(AID aid, Actor? target, GCDPriority prio) + { + if (prio != GCDPriority.None) + { + Hints.ActionsToExecute.Push(ActionID.MakeSpell(aid), target, ActionQueue.Priority.High + (int)prio); + if (prio > NextGCDPrio) + { + NextGCD = aid; + NextGCDPrio = prio; + } + } + } + + //QueueOGCD execution + private void QueueOGCD(AID aid, Actor? target, OGCDPriority prio, float basePrio = ActionQueue.Priority.Medium) + { + if (prio != OGCDPriority.None) + { + Hints.ActionsToExecute.Push(ActionID.MakeSpell(aid), target, basePrio + (int)prio); + } + } + + #endregion + + #region Single-Target Helpers + private AID NextCombo() => ComboLastMove switch //Determines the next single-target action based on the last action used + { + AID.SolidBarrelPvP => AID.BurstStrikePvP, //4, defaults back to 1 + AID.BrutalShellPvP => AID.SolidBarrelPvP, //3 + AID.KeenEdgePvP => AID.BrutalShellPvP, //2 + _ => AID.KeenEdgePvP, //1 + }; + #endregion + + #region Cooldown Helpers + + //Determines when to use No Mercy + private bool ShouldUseRoughDivide(OffensiveStrategy strategy, Actor? target) => strategy switch + { + OffensiveStrategy.Automatic => + target != null && + !hasNM || rdCD >= 7 || IsOffCooldown(AID.RoughDividePvP), + OffensiveStrategy.Force => true, + OffensiveStrategy.Delay => false, + _ => false + }; + + //Determines when to use Zone + private bool ShouldUseZone(OffensiveStrategy strategy, Actor? target) => strategy switch + { + OffensiveStrategy.Automatic => + Player.InCombat && + target != null && + canZone && + hasNM && + In5y(target), + OffensiveStrategy.Force => canZone, + OffensiveStrategy.Delay => false, + _ => false + }; + + //Determines when to use Gnashing Fang + private bool ShouldUseGnashingFang(OffensiveStrategy strategy, Actor? target) => strategy switch + { + OffensiveStrategy.Automatic => + Player.InCombat && + target != null && + In5y(target) && + hasNM && + canGF, + OffensiveStrategy.Force => canGF, + OffensiveStrategy.Delay => false, + _ => false + }; + + //Determines when to use Fated Circle + private bool ShouldUseFatedCircle(OffensiveStrategy strategy, Actor? target) => strategy switch + { + OffensiveStrategy.Automatic => + Player.InCombat && + target != null && + In5y(target) && + hasNM && + canFC, + OffensiveStrategy.Force => canFC, + OffensiveStrategy.Delay => false, + _ => false + }; + + private bool ShouldUseLimitBreak(LimitBreakStrategy strategy, Actor? target) => strategy switch + { + LimitBreakStrategy.Automatic => + target != null && + In5y(target) && + hasNM && + LBready, + LimitBreakStrategy.Force => true, + LimitBreakStrategy.Hold => false, + _ => false + }; + #endregion +} From bfc6a01c24714cfd65af1e0dd69a4014ba35734d Mon Sep 17 00:00:00 2001 From: Akechi-kun <167795370+Akechi-kun@users.noreply.github.com> Date: Sun, 1 Dec 2024 04:54:35 -0800 Subject: [PATCH 04/12] x --- BossMod/Autorotation/akechi/AkechiGNB.cs | 822 ----------------------- 1 file changed, 822 deletions(-) delete mode 100644 BossMod/Autorotation/akechi/AkechiGNB.cs diff --git a/BossMod/Autorotation/akechi/AkechiGNB.cs b/BossMod/Autorotation/akechi/AkechiGNB.cs deleted file mode 100644 index 10a066ecc7..0000000000 --- a/BossMod/Autorotation/akechi/AkechiGNB.cs +++ /dev/null @@ -1,822 +0,0 @@ -using FFXIVClientStructs.FFXIV.Client.Game.Gauge; - -namespace BossMod.Autorotation.akechi; -//Contribution by Akechi -//Discord @akechdz or 'Akechi' on Puni.sh for maintenance -//This module supports <=2.47 SkS rotation as default (or 'Automatic') -//With user adjustment, 'SlowGNB' or 'FastGNB' usage is achievable - -public sealed class AkechiGNB(RotationModuleManager manager, Actor player) : RotationModule(manager, player) -{ - #region Enums: Abilities / Strategies - //Actions tracked for Cooldown Planner execution - public enum Track - { - AoE, //ST&AoE actions - Burst, //Burst damage actions - Potion, //Potion usage tracking - LightningShot, //Ranged attack tracking - GnashingFang, //Gnashing Fang action tracking - NoMercy, //No Mercy ability tracking - SonicBreak, //Sonic Break ability tracking - Bloodfest, //Bloodfest ability tracking - DoubleDown, //Double Down ability tracking - BurstStrike, //Burst Strike ability tracking - FatedCircle, //Fated Circle ability tracking - Zone, //Blasting Zone or Danger Zone tracking - BowShock, //Bow Shock ability tracking - } - - //Defines the strategy for using ST/AoE actions based on the current target selection and conditions - public enum AOEStrategy - { - SingleTarget, //Force single-target actions without exceeding cartridge cap - FocusSingleTarget, //Force single-target actions, regardless of cartridges - ForceAoE, //Force AoE actions without exceeding cartridge cap - FocusAoE, //Force AoE actions, regardless of cartridges - Auto, //Decide action based on target count; may break combo if needed - AutoFinishCombo, //Decide action based on target count but finish current combo if possible - GenerateDowntime, //Generate cartridges before downtime - } - - //Defines different strategies for executing burst damage actions based on cooldown and resource availability - public enum BurstStrategy - { - Automatic, //Automatically execute based on conditions - ConserveCarts, //Conserve cartridges for future use - UnderRaidBuffs, //Execute during raid buffs for maximum effect - UnderPotion //Execute while under the effects of a potion - } - - //Defines strategies for potion usage in combat, determining when and how to consume potions based on the situation - public enum PotionStrategy - { - Manual, //Manual potion usage - AlignWithRaidBuffs, //Align potion usage with raid buffs - Immediate //Use potions immediately when available - } - - //Defines strategies for using Lightning Shot during combat based on various conditions - public enum LightningShotStrategy - { - OpenerRanged, //Use Lightning Shot as part of the opener - Opener, //Use Lightning Shot at the start of combat - Force, //Always use Lightning Shot regardless of conditions - Ranged, //Use Lightning Shot when ranged attacks are necessary - Forbid //Prohibit the use of Lightning Shot - } - - //Defines the strategy for using Gnashing Fang in combos, allowing for different behaviors based on combat scenarios - public enum GnashingStrategy - { - Automatic, //Automatically decide when to use Gnashing Fang - ForceGnash, //Force the use of Gnashing Fang regardless of conditions - ForceClaw, //Force the use of Savage Claw action when in combo - ForceTalon, //Force the use of Wicked Talon action when in combo - Delay //Delay the use of Gnashing Fang for strategic reasons - } - - //Defines the strategy for using No Mercy, allowing for different behaviors based on combat scenarios - public enum NoMercyStrategy - { - Automatic, //Automatically decide when to use No Mercy - Force, //Force the use of No Mercy regardless of conditions - ForceLW, //Force the use of No Mercy in next possible 2nd oGCD slot - Force2, //Force the use of No Mercy when 2 cartridges are available - Force2LW, //Force the use of No Mercy in next possible 2nd oGCD slot & 2 cartridges are available - Force3, //Force the use of No Mercy when 3 cartridges are available - Force3LW, //Force the use of No Mercy in next possible 2nd oGCD slot & 3 cartridges are available - Delay //Delay the use of No Mercy for strategic reasons - } - - //Defines the strategy for using Sonic Break, allowing for different behaviors based on combat scenarios - public enum SonicBreakStrategy - { - Automatic, //Automatically decide when to use Sonic Break - Force, //Force the use of Sonic Break regardless of conditions - EarlySB, //Force the use of Sonic Break on the first GCD slot inside No Mercy window - LateSB, //Force the use of Sonic Break on the last GCD slot inside No Mercy window - Delay //Delay the use of Sonic Break for strategic reasons - } - - //Defines the strategy for using Bloodfest, allowing for different behaviors based on combat scenarios - public enum BloodfestStrategy - { - Automatic, //Automatically decide when to use Bloodfest - Force, //Force the use of Bloodfest regardless of conditions - Force0, //Force the use of Bloodfest only when ammo is empty - Delay //Delay the use of Sonic Break for strategic reasons - } - - //Defines different offensive strategies that dictate how abilities and resources are used during combat - public enum OffensiveStrategy - { - Automatic, //Automatically decide when to use offensive abilities - Force, //Force the use of offensive abilities regardless of conditions - Delay //Delay the use of offensive abilities for strategic reasons - } - #endregion - - public static RotationModuleDefinition Definition() - { - //Module title & signature - var res = new RotationModuleDefinition("GNB (Akechi)", "Standard Rotation Module", "Standard rotation (Akechi)", "Akechi", RotationModuleQuality.Good, BitMask.Build((int)Class.GNB), 100); - - #region Custom strategies - //Targeting strategy - res.Define(Track.AoE).As("Combo Option", "AoE", uiPriority: 200) - .AddOption(AOEStrategy.SingleTarget, "ST", "Use ST rotation (with overcap protection)") - .AddOption(AOEStrategy.FocusSingleTarget, "ST", "Use ST rotation (without overcap protection)") - .AddOption(AOEStrategy.ForceAoE, "AoE", "Use AoE rotation (with overcap protection)") - .AddOption(AOEStrategy.FocusAoE, "AoE", "Use AoE rotation (without overcap protection)") - .AddOption(AOEStrategy.Auto, "Auto", "Use AoE rotation if 3+ targets would be hit, otherwise use ST rotation; break combo if necessary") - .AddOption(AOEStrategy.AutoFinishCombo, "Auto Finish Combo", "Use AoE rotation if 3+ targets would be hit, otherwise use ST rotation; finish combo before switching") - .AddOption(AOEStrategy.GenerateDowntime, "Generate before Downtime", "Estimates time until disengagement & determines when to use ST or AoE combo to generate carts appropriately before downtime"); - - //Burst strategy - res.Define(Track.Burst).As("Burst", uiPriority: 190) - .AddOption(BurstStrategy.Automatic, "Automatic", "Spend cartridges optimally") - .AddOption(BurstStrategy.ConserveCarts, "Conserve", "Conserve everything (cartridges & GCDs)") - .AddOption(BurstStrategy.UnderRaidBuffs, "Under RaidBuffs", "Spend under raid buffs, otherwise conserve; ignores potion") - .AddOption(BurstStrategy.UnderPotion, "Under Potion", "Spend under potion, otherwise conserve; ignores raid buffs"); - - //Potion strategy - res.Define(Track.Potion).As("Potion", uiPriority: 180) - .AddOption(PotionStrategy.Manual, "Manual", "Do not use automatically") - .AddOption(PotionStrategy.AlignWithRaidBuffs, "AlignWithRaidBuffs", "Align with 2-minute raid buffs (0/6, 2/8, etc)", 270, 30, ActionTargets.Self) - .AddOption(PotionStrategy.Immediate, "Immediate", "Use ASAP, regardless of any buffs", 270, 30, ActionTargets.Self) - .AddAssociatedAction(ActionDefinitions.IDPotionStr); - - //LightningShot strategy - res.Define(Track.LightningShot).As("Lightning Shot", "L.Shot", uiPriority: 20) - .AddOption(LightningShotStrategy.OpenerRanged, "OpenerRanged", "Use as very first GCD and only if outside melee range") - .AddOption(LightningShotStrategy.Opener, "Opener", "Use as very first GCD regardless of range") - .AddOption(LightningShotStrategy.Force, "Force", "Force use ASAP (even in melee range)") - .AddOption(LightningShotStrategy.Ranged, "Ranged", "Use if outside melee range") - .AddOption(LightningShotStrategy.Forbid, "Forbid", "Do not use at all") - .AddAssociatedActions(GNB.AID.LightningShot); - - //GnashingFang strategy - res.Define(Track.GnashingFang).As("Gnashing Fang", "G.Fang", uiPriority: 160) - .AddOption(GnashingStrategy.Automatic, "Auto", "Normal use of Gnashing Fang") - .AddOption(GnashingStrategy.ForceGnash, "Force", "Force use of Gnashing Fang (Step 1)", 30, 0, ActionTargets.Hostile, 60) - .AddOption(GnashingStrategy.ForceClaw, "Force", "Force use of Savage Claw (Step 2)", 0, 0, ActionTargets.Hostile, 60) - .AddOption(GnashingStrategy.ForceTalon, "Force", "Force use of Wicked Talon (Step 3)", 0, 0, ActionTargets.Hostile, 60) - .AddOption(GnashingStrategy.Delay, "Delay", "Delay use of Gnashing Fang", 0, 0, ActionTargets.None, 60) - .AddAssociatedActions(GNB.AID.GnashingFang, GNB.AID.SavageClaw, GNB.AID.WickedTalon); - - //NoMercy strategy - res.Define(Track.NoMercy).As("No Mercy", "N.Mercy", uiPriority: 170) - .AddOption(NoMercyStrategy.Automatic, "Automatic", "Use normally") - .AddOption(NoMercyStrategy.Force, "Force", "Force use ASAP (even during downtime)", 60, 20, ActionTargets.Self, 2) - .AddOption(NoMercyStrategy.ForceLW, "Force Late-weave", "Uses as soon as in next possible late-weave slot", 60, 20, ActionTargets.Self, 2) - .AddOption(NoMercyStrategy.Force2, "Force (2+ carts)", "Use as soon as you have 2 (or more) cartridges (SlowGNB)", 60, 20, ActionTargets.Self, 30) - .AddOption(NoMercyStrategy.Force2LW, "Force Late-weave (2+ carts)", "Use as soon as you have 2+ cartridges & in next possible late-weave slot (FastGNB)", 60, 20, ActionTargets.Self, 30) - .AddOption(NoMercyStrategy.Force3, "Force (3 carts)", "Use as soon as you have 3 cartridges (SlowGNB)", 60, 20, ActionTargets.Self, 88) - .AddOption(NoMercyStrategy.Force3LW, "Force Late-weave (3 carts)", "Use as soon as you have 3 cartridges & in next possible late-weave slot (FastGNB)", 60, 20, ActionTargets.Self, 88) - .AddOption(NoMercyStrategy.Delay, "Delay", "Delay", 0, 0, ActionTargets.None, 2) - .AddAssociatedActions(GNB.AID.NoMercy); - - //SonicBreak strategy - res.Define(Track.SonicBreak).As("Sonic Break", "S.Break", uiPriority: 150) - .AddOption(SonicBreakStrategy.Automatic, "Auto", "Normal use of Sonic Break") - .AddOption(SonicBreakStrategy.Force, "Force", "Force use of Sonic Break", 0, 30, ActionTargets.Hostile, 54) - .AddOption(SonicBreakStrategy.EarlySB, "Early Sonic Break", "Uses Sonic Break as the very first GCD when in No Mercy", 0, 30, ActionTargets.Hostile, 54) - .AddOption(SonicBreakStrategy.LateSB, "Late Sonic Break", "Uses Sonic Break as the very last GCD when in No Mercy", 0, 30, ActionTargets.Hostile, 54) - .AddOption(SonicBreakStrategy.Delay, "Delay", "Delay use of Sonic Break", 0, 0, ActionTargets.None, 54) - .AddAssociatedActions(GNB.AID.SonicBreak); - - //Bloodfest strategy - res.Define(Track.Bloodfest).As("Bloodfest", "Fest", uiPriority: 170) - .AddOption(BloodfestStrategy.Automatic, "Auto", "Normal use of Bloodfest") - .AddOption(BloodfestStrategy.Force, "Force", "Force use of Bloodfest, regardless of ammo count", 120, 0, ActionTargets.Hostile, 80) - .AddOption(BloodfestStrategy.Force0, "Force (0 cart)", "Force use of Bloodfest as soon as you have 0 carts", 120, 0, ActionTargets.Hostile, 80) - .AddOption(BloodfestStrategy.Delay, "Delay", "Delay use of Bloodfest", 0, 0, ActionTargets.None, 80) - .AddAssociatedActions(GNB.AID.Bloodfest); - #endregion - - #region Offensive Strategies - //DoubleDown strategy - res.Define(Track.DoubleDown).As("Double Down", "D.Down", uiPriority: 160) - .AddOption(OffensiveStrategy.Automatic, "Auto", "Normal use of Double Down") - .AddOption(OffensiveStrategy.Force, "Force", "Force use of Double Down", 60, 0, ActionTargets.Hostile, 90) - .AddOption(OffensiveStrategy.Delay, "Delay", "Delay use of Double Down", 0, 0, ActionTargets.None, 90) - .AddAssociatedActions(GNB.AID.DoubleDown); - - //BurstStrike strategy - res.Define(Track.BurstStrike).As("Burst Strike", "B.Strike", uiPriority: 140) - .AddOption(OffensiveStrategy.Automatic, "Auto", "Normal use of Burst Strike") - .AddOption(OffensiveStrategy.Force, "Force", "Force use of Burst Strike", 0, 0, ActionTargets.Hostile, 30) - .AddOption(OffensiveStrategy.Delay, "Delay", "Delay use of Burst Strike", 0, 0, ActionTargets.None, 30) - .AddAssociatedActions(GNB.AID.BurstStrike); - - //FatedCircle strategy - res.Define(Track.FatedCircle).As("Fated Circle", "F.Circle", uiPriority: 140) - .AddOption(OffensiveStrategy.Automatic, "Auto", "Normal use of Fated Circle") - .AddOption(OffensiveStrategy.Force, "Force", "Force use of Fated Circle", 0, 0, ActionTargets.Hostile, 72) - .AddOption(OffensiveStrategy.Delay, "Delay", "Delay use of Fated Circle", 0, 0, ActionTargets.None, 72) - .AddAssociatedActions(GNB.AID.FatedCircle); - - //Zone strategy - res.Define(Track.Zone).As("Blasting Zone", "Zone", uiPriority: 150) - .AddOption(OffensiveStrategy.Automatic, "Automatic", "Use normally") - .AddOption(OffensiveStrategy.Force, "Force", "Force use ASAP", 30, 0, ActionTargets.Hostile, 18) - .AddOption(OffensiveStrategy.Delay, "Delay", "Delay", 0, 0, ActionTargets.None, 18) - .AddAssociatedActions(GNB.AID.BlastingZone, GNB.AID.DangerZone); - - //BowShock strategy - res.Define(Track.BowShock).As("Bow Shock", "B.Shock", uiPriority: 150) - .AddOption(OffensiveStrategy.Automatic, "Auto", "Normal use of Bow Shock") - .AddOption(OffensiveStrategy.Force, "Force", "Force use of Bow Shock", 60, 15, ActionTargets.Self, 62) - .AddOption(OffensiveStrategy.Delay, "Delay", "Delay use of Bow Shock", 0, 0, ActionTargets.None, 62) - .AddAssociatedActions(GNB.AID.BowShock); - #endregion - - return res; - - } - - #region Priorities - //Priority for GCDs used - public enum GCDPriority - { - None = 0, - Combo123 = 350, - FatedCircle = 400, - BurstStrike = 500, - Reign = 525, - comboNeed = 550, - GF23 = 575, - SonicBreak = 600, - DoubleDown = 675, - GF1 = 700, - ForcedGCD = 900, - } - //Priority for oGCDs used - public enum OGCDPriority - { - None = 0, - Continuation = 500, - Zone = 550, - BowShock = 600, - Continuation1 = 650, - Bloodfest = 700, - ContinuationNeed = 800, - NoMercy = 875, - Potion = 900, - ForcedOGCD = 900, - } - #endregion - - #region Placeholders for Variables - //Gauge - public byte Ammo; //Range: 0-2 or 0-3 max; this counts current ammo count - public byte GunComboStep; //0 = Gnashing Fang & Reign of Beasts, 1 = Savage Claw, 2 = Wicked Talon, 4 = NobleBlood, 5 = LionHeart. - public int MaxCartridges; //Maximum number of cartridges based on player level - //Cooldown Related - private float bfCD; //Time left on Bloodfest cooldown (120s base) - private float nmLeft; //Time left on No Mercy buff (20s base) - private float nmCD; //Time left on No Mercy cooldown (60s base) - private bool hasNM; //Checks self for No Mercy buff - private bool hasBreak; //Checks self for Ready To Break buff - private bool hasReign; //Checks self for Ready To Reign buff - private bool hasBlast; //Checks self for Ready To Blast buff - private bool hasRaze; //Checks self for Ready To Raze buff - private bool hasRip; //Checks self for Ready To Rip buff - private bool hasTear; //Checks self for Ready To Tear buff - private bool hasGouge; //Checks self for Ready To Gouge buff - private bool canBS; - private bool canGF; - private bool canFC; - private bool canDD; - private bool canBF; - private bool canZone; - private bool canBreak; - private bool canBow; - private bool canContinue; - private bool canReign; - - //Misc - public float PotionLeft; //Time left on potion buff (30s base) - public float RaidBuffsLeft; //Time left on raid-wide buffs (typically 20s-22s) - public float RaidBuffsIn; //Time until raid-wide buffs are applied again (typically 20s-22s) - public float GCDLength; //Current GCD length, adjusted by skill speed/haste (2.5s baseline) - public float BurstWindowLeft; //Time left in current burst window (typically 20s-22s) - public float BurstWindowIn; //Time until next burst window (typically 20s-22s) - public GNB.AID NextGCD; //Next global cooldown action to be used (needed for cartridge management) - private GCDPriority NextGCDPrio; //Priority of the next GCD, used for decision making on cooldowns - #endregion - - #region Module Helpers - private bool Unlocked(GNB.AID aid) => ActionUnlocked(ActionID.MakeSpell(aid)); //Check if the desired ability is unlocked - private bool Unlocked(GNB.TraitID tid) => TraitUnlocked((uint)tid); //Check if the desired trait is unlocked - private float CD(GNB.AID aid) => World.Client.Cooldowns[ActionDefinitions.Instance.Spell(aid)!.MainCooldownGroup].Remaining; //Get remaining cooldown time for the specified action - private bool CanFitGCD(float deadline, int extraGCDs = 0) => GCD + GCDLength * extraGCDs < deadline; //Check if we can fit an additional GCD within the provided deadline - private GNB.AID ComboLastMove => (GNB.AID)World.Client.ComboState.Action; //Get the last action used in the combo sequence - private bool In3y(Actor? target) => Player.DistanceToHitbox(target) <= 3; //Check if the target is within melee range (3 yalms) - private bool In5y(Actor? target) => Player.DistanceToHitbox(target) <= 4.75; //Check if the target is within 5 yalms - private bool ActionReady(GNB.AID aid) => World.Client.Cooldowns[ActionDefinitions.Instance.Spell(aid)!.MainCooldownGroup].Remaining < 0.6f; //Check if the desired action is ready (cooldown less than 0.6 seconds) - private bool IsFirstGCD() => !Player.InCombat || (World.CurrentTime - Manager.CombatStart).TotalSeconds < 0.1f; //Check if this is the first GCD in combat - private int NumTargetsHitByAoE() => Hints.NumPriorityTargetsInAOECircle(Player.Position, 5); //Returns the number of targets hit by AoE within a 5-yalm radius around the player - private bool IsPotionBeforeRaidbuffs() => RaidBuffsLeft == 0 && PotionLeft > RaidBuffsIn + 17.5f; //Checks if the potion should be used before raid buffs expire - public bool HasEffect(SID sid) where SID : Enum => Player.FindStatus((uint)(object)sid, Player.InstanceID) != null; //Checks if Status effect is on self - #endregion - - public override void Execute(StrategyValues strategy, Actor? primaryTarget, float estimatedAnimLockDelay, bool isMoving) //Executes our actions - { - #region Variables - //Gauge - var gauge = World.Client.GetGauge(); //Retrieve Gunbreaker gauge - Ammo = gauge.Ammo; //Current cartridges - GunComboStep = gauge.AmmoComboStep; //Combo step for Gnashing Fang or Reign of Beasts - MaxCartridges = Unlocked(GNB.TraitID.CartridgeChargeII) ? 3 : 2; //Max cartridges based on level - //Cooldown Related - bfCD = CD(GNB.AID.Bloodfest); //Bloodfest cooldown (120s) - nmCD = CD(GNB.AID.NoMercy); //No Mercy cooldown (60s) - nmLeft = SelfStatusLeft(GNB.SID.NoMercy, 20); //Remaining time for No Mercy buff (20s) - hasBreak = HasEffect(GNB.SID.ReadyToBreak); //Checks for Ready To Break buff - hasReign = HasEffect(GNB.SID.ReadyToReign); //Checks for Ready To Reign buff - hasNM = nmCD is >= 40 and <= 60; //Checks if No Mercy is active - hasBlast = HasEffect(GNB.SID.ReadyToBlast); //Checks for Ready To Blast buff - hasRaze = HasEffect(GNB.SID.ReadyToRaze); //Checks for Ready To Raze buff - hasRip = HasEffect(GNB.SID.ReadyToRip); //Checks for Ready To Rip buff - hasTear = HasEffect(GNB.SID.ReadyToTear); //Checks for Ready To Tear buff - hasGouge = HasEffect(GNB.SID.ReadyToGouge); //Checks for Ready To Gouge buff - //Misc - (RaidBuffsLeft, RaidBuffsIn) = EstimateRaidBuffTimings(primaryTarget); - PotionLeft = PotionStatusLeft(); //Remaining time for potion buff (30s) - GCDLength = ActionSpeed.GCDRounded(World.Client.PlayerStats.SkillSpeed, World.Client.PlayerStats.Haste, Player.Level); //GCD based on skill speed and haste - NextGCD = GNB.AID.None; //Next global cooldown action to be used - NextGCDPrio = GCDPriority.None; //Priority of the next GCD, used for decision making on cooldowns - - #region Minimal Requirements - //Ammo-relative - canBS = Unlocked(GNB.AID.BurstStrike) && Ammo > 0; //BurstStrike conditions; -1 Ammo ST - canGF = Unlocked(GNB.AID.GnashingFang) && ActionReady(GNB.AID.GnashingFang) && Ammo > 0; //GnashingFang conditions; -1 Ammo ST - canFC = Unlocked(GNB.AID.FatedCircle) && Ammo > 0; //FatedCircle conditions; -1 Ammo AOE - canDD = Unlocked(GNB.AID.DoubleDown) && ActionReady(GNB.AID.DoubleDown) && Ammo > 0; //DoubleDown conditions; -2 Ammo AOE - canBF = Unlocked(GNB.AID.Bloodfest) && ActionReady(GNB.AID.Bloodfest); //Bloodfest conditions; +all Ammo (must have target) - //Cooldown-relative - canZone = Unlocked(GNB.AID.DangerZone) && ActionReady(GNB.AID.DangerZone); //Zone conditions - canBreak = hasBreak && Unlocked(GNB.AID.SonicBreak); //SonicBreak conditions - canBow = Unlocked(GNB.AID.BowShock) && ActionReady(GNB.AID.BowShock); //BowShock conditions - canContinue = Unlocked(GNB.AID.Continuation); //Continuation conditions - canReign = Unlocked(GNB.AID.ReignOfBeasts) && hasReign; //ReignOfBeasts conditions - #endregion - - #endregion - - #region Burst - //Burst (raid buff) windows typically last 20s every 120s - var burst = strategy.Option(Track.Burst); - var burstStrategy = burst.As(); - var hold = burstStrategy == BurstStrategy.ConserveCarts; //Determine if conserving cartridges - - //Calculate the burst window based on the current strategy - (BurstWindowIn, BurstWindowLeft) = burstStrategy switch - { - BurstStrategy.Automatic => (RaidBuffsIn, IsPotionBeforeRaidbuffs() ? 0 : Math.Max(PotionLeft, RaidBuffsLeft)), - BurstStrategy.UnderRaidBuffs => (RaidBuffsIn, RaidBuffsLeft), - BurstStrategy.UnderPotion => (PotionCD, PotionLeft), - _ => (0, 0) - }; - #endregion - - #region Targeting - //Define ST/AoE strategy and determine number of targets - var AOEStrategy = strategy.Option(Track.AoE).As(); - var AoETargets = AOEStrategy switch - { - AOEStrategy.SingleTarget => NumTargetsHitByAoE() > 0 ? 1 : 0, - AOEStrategy.FocusSingleTarget => NumTargetsHitByAoE() > 0 ? 1 : 0, - AOEStrategy.ForceAoE => NumTargetsHitByAoE() > 0 ? 100 : 0, - AOEStrategy.FocusAoE => NumTargetsHitByAoE() > 0 ? 100 : 0, - AOEStrategy.GenerateDowntime => NumTargetsHitByAoE() > 0 ? 100 : 0, - _ => NumTargetsHitByAoE() - }; - #endregion - - #region Rotation Strategies - //Force Options - if (AOEStrategy == AOEStrategy.FocusSingleTarget) //ST (without overcap protection) - QueueGCD(NextForceSingleTarget(), primaryTarget, GCDPriority.ForcedGCD); - if (AOEStrategy == AOEStrategy.FocusAoE) //AoE (without overcap protection) - QueueGCD(NextForceAoE(), primaryTarget, GCDPriority.ForcedGCD); - - #region Logic for Cart Generation before Downtime - //Estimate time to next downtime - var downtimeIn = Manager.Planner?.EstimateTimeToNextDowntime().Item2 ?? float.MaxValue; - var comboStepsRemaining = ComboLastMove switch - { - GNB.AID.KeenEdge => Unlocked(GNB.AID.SolidBarrel) ? 2 : Unlocked(GNB.AID.BrutalShell) ? 1 : 0, - GNB.AID.DemonSlice => Unlocked(GNB.AID.DemonSlaughter) ? 1 : 0, - _ => 0 - }; - - if (AOEStrategy == AOEStrategy.GenerateDowntime) - { - if (comboStepsRemaining == 0) //Not in any combo - { - if ((downtimeIn == GCD * 2 && Ammo == 2) || - (downtimeIn == GCD * 4 && Ammo == 1) || - (downtimeIn == GCD * 6 && Ammo == 0)) - QueueGCD(GNB.AID.DemonSlice, Player, GCDPriority.ForcedGCD); - - if ((downtimeIn == GCD * 3 && Ammo == 2) || - (downtimeIn == GCD * 5 && Ammo == 1) || - (downtimeIn == GCD * 8 && Ammo == 0) || - (downtimeIn == GCD * 9 && Ammo == 0)) - QueueGCD(GNB.AID.KeenEdge, primaryTarget, GCDPriority.ForcedGCD); - } - - if (comboStepsRemaining == 1) //Combo initiated - { - if (((downtimeIn == GCD && Ammo == 2) || - (downtimeIn == GCD * 3 && Ammo == 1) || - (downtimeIn == GCD * 5 && Ammo == 0)) && - ComboLastMove == GNB.AID.DemonSlice) - QueueGCD(GNB.AID.DemonSlaughter, Player, GCDPriority.ForcedGCD); - - if (((downtimeIn == GCD * 2 && Ammo == 2) || - (downtimeIn == GCD * 4 && Ammo == 1) || - (downtimeIn == GCD * 7 && Ammo == 2) || - (downtimeIn == GCD * 8 && Ammo == 2)) && - ComboLastMove == GNB.AID.KeenEdge) - QueueGCD(GNB.AID.BrutalShell, primaryTarget, GCDPriority.ForcedGCD); - } - - if (comboStepsRemaining == 2) - { - if (((downtimeIn == GCD && (Ammo == 2 || Ammo == 3)) || - (downtimeIn == GCD * 4 && Ammo == 1) || - (downtimeIn == GCD * 7 && Ammo == 0)) && - ComboLastMove == GNB.AID.BrutalShell) - QueueGCD(GNB.AID.SolidBarrel, primaryTarget, GCDPriority.ForcedGCD); - } - - if (Ammo == MaxCartridges) - QueueGCD(NextForceSingleTarget(), primaryTarget, GCDPriority.ForcedGCD); - } - #endregion - - #endregion - - #region Rotation Execution - //Determine and queue combo actions - var (comboAction, comboPrio) = ComboActionPriority(AOEStrategy, AoETargets, burstStrategy, burst.Value.ExpireIn); - QueueGCD(comboAction, comboAction is GNB.AID.DemonSlice or GNB.AID.DemonSlaughter ? Player : primaryTarget, Ammo == 0 ? GCDPriority.comboNeed : comboPrio); - - #region OGCDs - //No Mercy execution - var nmStrat = strategy.Option(Track.NoMercy).As(); - if (!hold && ShouldUseNoMercy(nmStrat, primaryTarget)) - QueueOGCD(GNB.AID.NoMercy, Player, nmStrat is NoMercyStrategy.Force or NoMercyStrategy.ForceLW or NoMercyStrategy.Force2 or NoMercyStrategy.Force2LW or NoMercyStrategy.Force3 or NoMercyStrategy.Force3LW ? OGCDPriority.ForcedOGCD : OGCDPriority.NoMercy); - - //Zone execution (Blasting Zone / Danger Zone) - var zoneAction = Unlocked(GNB.AID.BlastingZone) ? GNB.AID.BlastingZone : GNB.AID.DangerZone; - var zoneStrat = strategy.Option(Track.Zone).As(); - if (!hold && ShouldUseZone(zoneStrat, primaryTarget)) - QueueOGCD(zoneAction, primaryTarget, zoneStrat == OffensiveStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.Zone); - - //Bow Shock execution - var bowStrat = strategy.Option(Track.BowShock).As(); - if (!hold && ShouldUseBowShock(bowStrat, primaryTarget)) - QueueOGCD(GNB.AID.BowShock, Player, bowStrat == OffensiveStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.BowShock); - - //Bloodfest execution - var bfStrat = strategy.Option(Track.Bloodfest).As(); - if (!hold && ShouldUseBloodfest(bfStrat, primaryTarget)) - QueueOGCD(GNB.AID.Bloodfest, primaryTarget, bfStrat is BloodfestStrategy.Force or BloodfestStrategy.Force0 ? OGCDPriority.ForcedOGCD : OGCDPriority.Bloodfest); - - //Continuation execution - if (canContinue) - { - if (hasRip) - QueueOGCD(GNB.AID.JugularRip, primaryTarget, OGCDPriority.ContinuationNeed); - if (hasTear) - QueueOGCD(GNB.AID.AbdomenTear, primaryTarget, OGCDPriority.ContinuationNeed); - if (hasGouge) - QueueOGCD(GNB.AID.EyeGouge, primaryTarget, OGCDPriority.ContinuationNeed); - if (hasBlast) - QueueOGCD(GNB.AID.Hypervelocity, primaryTarget, OGCDPriority.ContinuationNeed); - if (hasRaze) - QueueOGCD(GNB.AID.FatedBrand, primaryTarget, OGCDPriority.ContinuationNeed); - } - #endregion - - #region GCDs - //Gnashing Fang execution - var gfStrat = strategy.Option(Track.GnashingFang).As(); - if (!hold && ShouldUseGnashingFang(gfStrat, primaryTarget)) - QueueGCD(GNB.AID.GnashingFang, primaryTarget, gfStrat == GnashingStrategy.ForceGnash ? GCDPriority.ForcedGCD : GCDPriority.GF1); - - //Double Down execution - var ddStrat = strategy.Option(Track.DoubleDown).As(); - if (ShouldUseDoubleDown(ddStrat, primaryTarget)) - QueueGCD(GNB.AID.DoubleDown, primaryTarget, ((ddStrat == OffensiveStrategy.Force) || Ammo == 1) ? GCDPriority.ForcedGCD : GCDPriority.DoubleDown); - - //Gnashing Fang Combo execution - if (GunComboStep == 1) - QueueGCD(GNB.AID.SavageClaw, primaryTarget, gfStrat == GnashingStrategy.ForceClaw ? GCDPriority.ForcedGCD : GCDPriority.GF23); - if (GunComboStep == 2) - QueueGCD(GNB.AID.WickedTalon, primaryTarget, gfStrat == GnashingStrategy.ForceTalon ? GCDPriority.ForcedGCD : GCDPriority.GF23); - - //Reign full combo execution - if (canReign && hasNM && GunComboStep == 0) - QueueGCD(GNB.AID.ReignOfBeasts, primaryTarget, GCDPriority.Reign); - if (GunComboStep == 3) - QueueGCD(GNB.AID.NobleBlood, primaryTarget, GCDPriority.Reign); - if (GunComboStep == 4) - QueueGCD(GNB.AID.LionHeart, primaryTarget, GCDPriority.Reign); - - //Sonic Break execution - var sbStrat = strategy.Option(Track.SonicBreak).As(); - if (ShouldUseSonicBreak(sbStrat, primaryTarget)) - QueueGCD(GNB.AID.SonicBreak, primaryTarget, sbStrat is SonicBreakStrategy.Force or SonicBreakStrategy.EarlySB ? GCDPriority.ForcedGCD : GCDPriority.SonicBreak); - - //Burst Strike execution - var strikeStrat = strategy.Option(Track.BurstStrike).As(); - if (Unlocked(GNB.AID.BurstStrike) && Unlocked(GNB.AID.Bloodfest) && ShouldUseBurstStrike(strikeStrat, primaryTarget)) - QueueGCD(GNB.AID.BurstStrike, primaryTarget, strikeStrat == OffensiveStrategy.Force ? GCDPriority.ForcedGCD : nmCD < 1 ? GCDPriority.ForcedGCD : GCDPriority.BurstStrike); - - //Fated Circle execution - var fcStrat = strategy.Option(Track.FatedCircle).As(); - if (ShouldUseFatedCircle(fcStrat, primaryTarget)) - QueueGCD(GNB.AID.FatedCircle, primaryTarget, fcStrat == OffensiveStrategy.Force ? GCDPriority.ForcedGCD : GCDPriority.FatedCircle); - if (!canFC && canBS) - { - if (Ammo > 0 && ShouldUseBurstStrike(strikeStrat, primaryTarget)) - QueueGCD(GNB.AID.BurstStrike, primaryTarget, GCDPriority.BurstStrike); - } - - //Lightning Shot execution - var lsStrat = strategy.Option(Track.LightningShot).As(); - if (ShouldUseLightningShot(primaryTarget, strategy.Option(Track.LightningShot).As())) - QueueGCD(GNB.AID.LightningShot, primaryTarget, lsStrat is LightningShotStrategy.Force or LightningShotStrategy.Ranged ? GCDPriority.ForcedGCD : GCDPriority.Combo123); - #endregion - - #endregion - - //Potion execution - if (ShouldUsePotion(strategy.Option(Track.Potion).As())) - Hints.ActionsToExecute.Push(ActionDefinitions.IDPotionStr, Player, ActionQueue.Priority.VeryHigh + (int)OGCDPriority.Potion, 0, GCD - 0.9f); - } - - #region Core Execution Helpers - //QueueGCD execution - private void QueueGCD(GNB.AID aid, Actor? target, GCDPriority prio) - { - if (prio != GCDPriority.None) - { - Hints.ActionsToExecute.Push(ActionID.MakeSpell(aid), target, ActionQueue.Priority.High + (int)prio); - if (prio > NextGCDPrio) - { - NextGCD = aid; - NextGCDPrio = prio; - } - } - } - - //QueueOGCD execution - private void QueueOGCD(GNB.AID aid, Actor? target, OGCDPriority prio, float basePrio = ActionQueue.Priority.Medium) - { - if (prio != OGCDPriority.None) - { - Hints.ActionsToExecute.Push(ActionID.MakeSpell(aid), target, basePrio + (int)prio); - } - } - - //Returns the amount of ammo gained from specific actions - private int AmmoGainedFromAction(GNB.AID action) => action switch - { - GNB.AID.SolidBarrel => 1, - GNB.AID.DemonSlaughter => 1, - GNB.AID.Bloodfest => 3, - _ => 0 - }; - - private (GNB.AID, GCDPriority) ComboActionPriority(AOEStrategy aoeStrategy, int AoETargets, BurstStrategy burstStrategy, float burstStrategyExpire) - { - //Determine how many combo steps are remaining based on the last action - var comboStepsRemaining = ComboLastMove switch - { - GNB.AID.KeenEdge => Unlocked(GNB.AID.SolidBarrel) ? 2 : Unlocked(GNB.AID.BrutalShell) ? 1 : 0, - GNB.AID.DemonSlice => Unlocked(GNB.AID.DemonSlaughter) ? 1 : 0, - _ => 0 - }; - - //Check if we can fit the GCD based on remaining time - if (comboStepsRemaining > 0 && !CanFitGCD(World.Client.ComboState.Remaining)) - comboStepsRemaining = 0; - - var doingAOECombo = ComboLastMove == GNB.AID.DemonSlice; - - //Determine if an AoE action is desirable based on target count and strategy - var wantAOEAction = Unlocked(GNB.AID.DemonSlice) && aoeStrategy switch - { - AOEStrategy.SingleTarget => false, - AOEStrategy.FocusSingleTarget => false, - AOEStrategy.ForceAoE => true, - AOEStrategy.FocusAoE => false, - AOEStrategy.Auto => AoETargets >= 3, - AOEStrategy.AutoFinishCombo => comboStepsRemaining > 0 - ? doingAOECombo - : Unlocked(GNB.AID.Continuation) ? AoETargets >= 3 : AoETargets >= 2, - AOEStrategy.GenerateDowntime => false, - _ => false - }; - - //Reset combo steps if the desired action does not match the current combo type - if (comboStepsRemaining > 0 && wantAOEAction != doingAOECombo) - comboStepsRemaining = 0; - - var nextAction = wantAOEAction ? NextComboAoE() : NextComboSingleTarget(); - var riskingAmmo = Ammo + AmmoGainedFromAction(nextAction) > 3; - - //Return combo priority based on the ability to fit GCDs and remaining combo steps - if (comboStepsRemaining > 0 && !CanFitGCD(World.Client.ComboState.Remaining, 1)) - return (nextAction, GCDPriority.Combo123); - - //Return normal combo action priority based on ammo risks - return (nextAction, riskingAmmo ? GCDPriority.BurstStrike : GCDPriority.Combo123); - } - - #endregion - - #region Single-Target Helpers - private GNB.AID NextComboSingleTarget() => ComboLastMove switch //Determines the next single-target action based on the last action used - { - GNB.AID.BrutalShell => Ammo == MaxCartridges ? GNB.AID.BurstStrike : GNB.AID.SolidBarrel, - GNB.AID.KeenEdge => GNB.AID.BrutalShell, - _ => GNB.AID.KeenEdge, - }; - private GNB.AID NextForceSingleTarget() => ComboLastMove switch - { - GNB.AID.BrutalShell => GNB.AID.SolidBarrel, - GNB.AID.KeenEdge => GNB.AID.BrutalShell, - _ => GNB.AID.KeenEdge, - }; - #endregion - - #region AoE Helpers - private GNB.AID NextComboAoE() => ComboLastMove switch //Determines the next AoE action based on the last action used - { - GNB.AID.DemonSlice => Ammo == MaxCartridges - ? Unlocked(GNB.AID.FatedCircle) ? GNB.AID.FatedCircle : GNB.AID.BurstStrike - : GNB.AID.DemonSlaughter, - _ => GNB.AID.DemonSlice, - }; - private GNB.AID NextForceAoE() => ComboLastMove switch - { - GNB.AID.DemonSlice => GNB.AID.DemonSlaughter, - _ => GNB.AID.DemonSlice, - }; - #endregion - - #region Cooldown Helpers - - //Determines when to use Lightning Shot - private bool ShouldUseLightningShot(Actor? target, LightningShotStrategy strategy) => strategy switch - { - LightningShotStrategy.OpenerRanged => IsFirstGCD() && !In3y(target), - LightningShotStrategy.Opener => IsFirstGCD(), - LightningShotStrategy.Force => true, - LightningShotStrategy.Ranged => !In3y(target), - LightningShotStrategy.Forbid => false, - _ => false - }; - - //Determines when to use No Mercy - private bool ShouldUseNoMercy(NoMercyStrategy strategy, Actor? target) => strategy switch - { - NoMercyStrategy.Automatic => - Player.InCombat && target != null && ActionReady(GNB.AID.NoMercy) && GCD < 0.9f && - ((Ammo < 3) || //Lv90+ Opener - (Ammo >= 1 && bfCD == 0 && Unlocked(GNB.AID.Bloodfest) && !Unlocked(GNB.AID.DoubleDown)) || //Lv80+ Opener - (!Unlocked(GNB.AID.Bloodfest) && Ammo >= 1 && ActionReady(GNB.AID.GnashingFang))), //Lv70 & below - NoMercyStrategy.Force => true, - NoMercyStrategy.ForceLW => Player.InCombat && GCD < 0.9f, - NoMercyStrategy.Force2 => Ammo >= 2, - NoMercyStrategy.Force2LW => Player.InCombat && GCD < 0.9f && Ammo >= 2, - NoMercyStrategy.Force3 => Ammo == 3, - NoMercyStrategy.Force3LW => Player.InCombat && GCD < 0.9f && Ammo == 3, - NoMercyStrategy.Delay => false, - _ => false - }; - - //Determines when to use Bloodfest - private bool ShouldUseBloodfest(BloodfestStrategy strategy, Actor? target) => strategy switch - { - BloodfestStrategy.Automatic => - Player.InCombat && target != null && - canBF && Ammo == 0, - BloodfestStrategy.Force => canBF, - BloodfestStrategy.Force0 => canBF && Ammo == 0, - BloodfestStrategy.Delay => false, - _ => false - }; - - //Determines when to use Zone - private bool ShouldUseZone(OffensiveStrategy strategy, Actor? target) => strategy switch - { - OffensiveStrategy.Automatic => - Player.InCombat && In3y(target) && nmCD is < 57.55f and > 17 && - ActionReady(Unlocked(GNB.AID.BlastingZone) ? GNB.AID.BlastingZone : GNB.AID.DangerZone), - OffensiveStrategy.Force => canZone, - OffensiveStrategy.Delay => false, - _ => false - }; - - //Determines when to use BowShock - private bool ShouldUseBowShock(OffensiveStrategy strategy, Actor? target) => strategy switch - { - OffensiveStrategy.Automatic => - Player.InCombat && ActionReady(GNB.AID.BowShock) && In5y(target) && nmCD is < 57.55f and > 17, - OffensiveStrategy.Force => canBow, - OffensiveStrategy.Delay => false, - _ => false - }; - - //Determines when to use Sonic Break - private bool ShouldUseSonicBreak(SonicBreakStrategy strategy, Actor? target) => strategy switch - { - SonicBreakStrategy.Automatic => - Player.InCombat && In3y(target) && canBreak, - SonicBreakStrategy.Force => canBreak, - SonicBreakStrategy.EarlySB => nmCD is >= 57.5f || hasBreak, - SonicBreakStrategy.LateSB => nmLeft <= GCDLength, - SonicBreakStrategy.Delay => false, - _ => false - }; - - //Determines when to use Double Down - private bool ShouldUseDoubleDown(OffensiveStrategy strategy, Actor? target) => strategy switch - { - OffensiveStrategy.Automatic => - Player.InCombat && target != null && - In5y(target) && canDD && hasNM, - OffensiveStrategy.Force => canDD, - OffensiveStrategy.Delay => false, - _ => false - }; - - //Determines when to use Gnashing Fang - private bool ShouldUseGnashingFang(GnashingStrategy strategy, Actor? target) => strategy switch - { - GnashingStrategy.Automatic => - Player.InCombat && target != null && In3y(target) && canGF && - (nmLeft > 0 || hasNM || nmCD is < 35 and > 17), - GnashingStrategy.ForceGnash => canGF, - GnashingStrategy.ForceClaw => Player.InCombat && GunComboStep == 1, - GnashingStrategy.ForceTalon => Player.InCombat && GunComboStep == 2, - GnashingStrategy.Delay => false, - _ => false - }; - - //Determines when to use Burst Strike - private bool ShouldUseBurstStrike(OffensiveStrategy strategy, Actor? target) => strategy switch - { - OffensiveStrategy.Automatic => - Player.InCombat && target != null && In3y(target) && canBS && - ((nmCD < 1.5f) || - (Unlocked(GNB.AID.DoubleDown) && hasNM && !ActionReady(GNB.AID.DoubleDown) && GunComboStep == 0 && !hasReign) || //Lv90+ - (!Unlocked(GNB.AID.DoubleDown) && !ActionReady(GNB.AID.GnashingFang) && hasNM && GunComboStep == 0) || //Lv80 & Below - (ComboLastMove == GNB.AID.BrutalShell && Ammo == MaxCartridges)), //Overcap protection - OffensiveStrategy.Force => canBS, - OffensiveStrategy.Delay => false, - _ => false - }; - - //Determines when to use Fated Circle - private bool ShouldUseFatedCircle(OffensiveStrategy strategy, Actor? AoETargets) => strategy switch - { - OffensiveStrategy.Automatic => - Player.InCombat && AoETargets != null && In3y(AoETargets) && canFC && - ((hasNM && !ActionReady(GNB.AID.DoubleDown)) || - (ComboLastMove == GNB.AID.DemonSlice && Ammo == MaxCartridges)), - OffensiveStrategy.Force => canFC, - OffensiveStrategy.Delay => false, - _ => false - }; - - //Determines if potions are aligned with No Mercy - private bool IsPotionAlignedWithNM() - { - //Use potion before Solid Barrel in opener - //Use for 6m window - return (Ammo == 1 && ActionReady(GNB.AID.GnashingFang) && - ActionReady(GNB.AID.DoubleDown) && - ActionReady(GNB.AID.Bloodfest)) || //Opener - (bfCD < 15 || ActionReady(GNB.AID.Bloodfest)) && Ammo == 3; - } - - //Determines when to use a potion based on strategy - private bool ShouldUsePotion(PotionStrategy strategy) => strategy switch - { - PotionStrategy.AlignWithRaidBuffs => - IsPotionAlignedWithNM() || (nmCD < 5 && bfCD < 15), - PotionStrategy.Immediate => true, - _ => false - }; - #endregion -} From 83973209444f14f7e6713eb82a663f44d1b90ee5 Mon Sep 17 00:00:00 2001 From: Akechi-kun <167795370+Akechi-kun@users.noreply.github.com> Date: Sun, 1 Dec 2024 05:07:54 -0800 Subject: [PATCH 05/12] cleanup PvP Util --- .../Autorotation/Utility/RolePvPUtility.cs | 145 ++++++++---------- 1 file changed, 66 insertions(+), 79 deletions(-) diff --git a/BossMod/Autorotation/Utility/RolePvPUtility.cs b/BossMod/Autorotation/Utility/RolePvPUtility.cs index a07c13236e..58dc20f137 100644 --- a/BossMod/Autorotation/Utility/RolePvPUtility.cs +++ b/BossMod/Autorotation/Utility/RolePvPUtility.cs @@ -10,11 +10,11 @@ public sealed class RolePvPUtility(RotationModuleManager manager, Actor player) #region Enums: Abilities / Strategies public enum Track { - Elixir, //Solid Barrel combo tracking - Recuperate, //Gnashing Fang action tracking - Guard, //Rough Divide ability tracking - Purify, //Fated Circle ability tracking - Sprint, //Burst tracking + Elixir, + Recuperate, + Guard, + Purify, + Sprint, } public enum ElixirStrategy { @@ -28,90 +28,86 @@ public enum ElixirStrategy public enum RecuperateStrategy { - Automatic, //Automatically execute based on conditions - Seventy, //Execute at 70% HP - Fifty, //Execute at 50% HP - Thirty, //Execute at 30% HP - Force, //Force burst actions regardless of conditions - Hold //Conserve resources and cooldowns + Automatic, + Seventy, + Fifty, + Thirty, + Force, + Hold } public enum GuardStrategy { - Automatic, //Automatically execute based on conditions - Seventy, //Execute at 70% HP - Fifty, //Execute at 50% HP - Thirty, //Execute at 30% HP - Force, //Force burst actions regardless of conditions - Hold //Conserve resources and cooldowns + Automatic, + Seventy, + Fifty, + Thirty, + Force, + Hold } public enum DefensiveStrategy { - Automatic, //Automatically decide when to use Defensive abilities - Force, //Force the use of Defensive abilities regardless of conditions - Delay //Delay the use of Defensive abilities for strategic reasons + Automatic, + Force, + Delay } - #endregion public static RotationModuleDefinition Definition() { var res = new RotationModuleDefinition("PvP: Utility", "PvP Rotation Module", "Utility Actions (PvP)", "Akechi", RotationModuleQuality.Excellent, BitMask.Build( - Class.PLD, Class.WAR, Class.DRK, Class.GNB, //Tank - Class.WHM, Class.SCH, Class.AST, Class.SGE, //Healer - Class.MNK, Class.DRG, Class.NIN, Class.SAM, Class.RPR, Class.VPR, //Melee - Class.BRD, Class.MCH, Class.DNC, //Ranged - Class.BLM, Class.SMN, Class.RDM, Class.PCT), 30); //Caster + Class.PLD, Class.WAR, Class.DRK, Class.GNB, + Class.WHM, Class.SCH, Class.AST, Class.SGE, + Class.MNK, Class.DRG, Class.NIN, Class.SAM, Class.RPR, Class.VPR, + Class.BRD, Class.MCH, Class.DNC, + Class.BLM, Class.SMN, Class.RDM, Class.PCT), 30); res.Define(Track.Elixir).As("Elixir", uiPriority: 150) - .AddOption(ElixirStrategy.Automatic, "Automatic", "Use normally") - .AddOption(ElixirStrategy.Close, "Close", "Use when target is close (within 10y or further)") - .AddOption(ElixirStrategy.Mid, "Mid", "Use when target is mid-range (within 20y or further)") - .AddOption(ElixirStrategy.Far, "Far", "Use when target is far (within 30y or further)") - .AddOption(ElixirStrategy.Force, "Force", "Force") - .AddOption(ElixirStrategy.Hold, "Hold", "Hold") + .AddOption(ElixirStrategy.Automatic, "Automatic") + .AddOption(ElixirStrategy.Close, "Close") + .AddOption(ElixirStrategy.Mid, "Mid") + .AddOption(ElixirStrategy.Far, "Far") + .AddOption(ElixirStrategy.Force, "Force") + .AddOption(ElixirStrategy.Hold, "Hold") .AddAssociatedActions(AID.Elixir); res.Define(Track.Recuperate).As("Recuperate", uiPriority: 150) - .AddOption(RecuperateStrategy.Automatic, "Automatic", "Use normally") - .AddOption(RecuperateStrategy.Seventy, "Seventy", "Use at 70% HP") - .AddOption(RecuperateStrategy.Fifty, "Fifty", "Use at 50% HP") - .AddOption(RecuperateStrategy.Thirty, "Thirty", "Use at 30% HP") - .AddOption(RecuperateStrategy.Force, "Force", "Force") - .AddOption(RecuperateStrategy.Hold, "Hold", "Hold") + .AddOption(RecuperateStrategy.Automatic, "Automatic") + .AddOption(RecuperateStrategy.Seventy, "Seventy") + .AddOption(RecuperateStrategy.Fifty, "Fifty") + .AddOption(RecuperateStrategy.Thirty, "Thirty") + .AddOption(RecuperateStrategy.Force, "Force") + .AddOption(RecuperateStrategy.Hold, "Hold") .AddAssociatedActions(AID.Recuperate); res.Define(Track.Guard).As("Guard", uiPriority: 150) - .AddOption(GuardStrategy.Automatic, "Automatic", "Use normally") - .AddOption(GuardStrategy.Seventy, "Seventy", "Use at 70% HP") - .AddOption(GuardStrategy.Fifty, "Fifty", "Use at 50% HP") - .AddOption(GuardStrategy.Thirty, "Thirty", "Use at 30% HP") - .AddOption(GuardStrategy.Force, "Force", "Force") - .AddOption(GuardStrategy.Hold, "Hold", "Hold") + .AddOption(GuardStrategy.Automatic, "Automatic") + .AddOption(GuardStrategy.Seventy, "Seventy") + .AddOption(GuardStrategy.Fifty, "Fifty") + .AddOption(GuardStrategy.Thirty, "Thirty") + .AddOption(GuardStrategy.Force, "Force") + .AddOption(GuardStrategy.Hold, "Hold") .AddAssociatedActions(AID.Guard); res.Define(Track.Purify).As("Purify", uiPriority: 150) - .AddOption(DefensiveStrategy.Automatic, "Automatic", "Use normally") - .AddOption(DefensiveStrategy.Force, "Force", "Force") - .AddOption(DefensiveStrategy.Delay, "Delay", "Delay") + .AddOption(DefensiveStrategy.Automatic, "Automatic") + .AddOption(DefensiveStrategy.Force, "Force") + .AddOption(DefensiveStrategy.Delay, "Delay") .AddAssociatedActions(AID.Purify); res.Define(Track.Sprint).As("Sprint", uiPriority: 150) - .AddOption(DefensiveStrategy.Automatic, "Automatic", "Use normally") - .AddOption(DefensiveStrategy.Force, "Force", "Force") - .AddOption(DefensiveStrategy.Delay, "Delay", "Delay") + .AddOption(DefensiveStrategy.Automatic, "Automatic") + .AddOption(DefensiveStrategy.Force, "Force") + .AddOption(DefensiveStrategy.Delay, "Delay") .AddAssociatedActions(AID.Sprint); return res; - } #region Priorities - //Priority for GCDs used public enum GCDPriority { None = 0, Elixir = 500, ForcedGCD = 900, } - //Priority for oGCDs used public enum OGCDPriority { None = 0, @@ -124,27 +120,25 @@ public enum OGCDPriority #endregion #region Placeholders for Variables - //Cooldown Related - private bool hasSprint; //Checks if Sprint is active - private bool canElixir; //can Keen Edge - private bool canRecuperate; //can Brutal Shell - private bool canGuard; //can Solid Barrel - private bool canPurify; //can Burst Strike - private bool canSprint; //can Gnashing Fang + private bool hasSprint; + private bool canElixir; + private bool canRecuperate; + private bool canGuard; + private bool canPurify; + private bool canSprint; - //Misc - public float GCDLength; //Current GCD length, adjusted by skill speed/haste (2.5s baseline) - public AID NextGCD; //Next global cooldown action to be used (needed for cartridge management) - private GCDPriority NextGCDPrio; //Priority of the next GCD, used for decision making on cooldowns + public float GCDLength; + public AID NextGCD; + private GCDPriority NextGCDPrio; #endregion #region Module Helpers - private bool In10y(Actor? target) => Player.DistanceToHitbox(target) <= 9.9; //Check if the target is within 10 yalms - private bool In20y(Actor? target) => Player.DistanceToHitbox(target) <= 19.9; //Check if the target is within 20 yalms - private bool In30y(Actor? target) => Player.DistanceToHitbox(target) <= 29.9; //Check if the target is within 30 yalms - private bool IsOffCooldown(AID aid) => World.Client.Cooldowns[ActionDefinitions.Instance.Spell(aid)!.MainCooldownGroup].Remaining < 0.6f; //Check if the desired action is ready (cooldown less than 0.6 seconds) - public bool HasEffect(SID sid) => SelfStatusLeft(sid) > 0; //Check if the player has the specified status effect - public bool TargetHasEffect(SID sid, Actor? target) => StatusDetails(target, sid, Player.InstanceID, 1000).Left > 0; //Check if the target has the specified status effect + private bool In10y(Actor? target) => Player.DistanceToHitbox(target) <= 9.9; + private bool In20y(Actor? target) => Player.DistanceToHitbox(target) <= 19.9; + private bool In30y(Actor? target) => Player.DistanceToHitbox(target) <= 29.9; + private bool IsOffCooldown(AID aid) => World.Client.Cooldowns[ActionDefinitions.Instance.Spell(aid)!.MainCooldownGroup].Remaining < 0.6f; + public bool HasEffect(SID sid) => SelfStatusLeft(sid) > 0; + public bool TargetHasEffect(SID sid, Actor? target) => StatusDetails(target, sid, Player.InstanceID, 1000).Left > 0; #endregion public float DebuffsLeft(Actor? target) { @@ -160,10 +154,10 @@ public float DebuffsLeft(Actor? target) } public bool HasAnyDebuff(Actor? target) => DebuffsLeft(target) > 0; - public override void Execute(StrategyValues strategy, Actor? primaryTarget, float estimatedAnimLockDelay, bool isMoving) //Executes our actions + public override void Execute(StrategyValues strategy, Actor? primaryTarget, float estimatedAnimLockDelay, bool isMoving) { #region Variables - hasSprint = HasEffect(SID.SprintPvP); //Checks if Sprint is active + hasSprint = HasEffect(SID.SprintPvP); #region Minimal Requirements canElixir = IsOffCooldown(AID.Elixir) && strategy.Option(Track.Elixir).As() != ElixirStrategy.Hold; @@ -174,34 +168,28 @@ public override void Execute(StrategyValues strategy, Actor? primaryTarget, floa #endregion #endregion - //Elixir execution var elixirStrat = strategy.Option(Track.Elixir).As(); if (ShouldUseElixir(elixirStrat, primaryTarget)) QueueGCD(AID.Elixir, Player, elixirStrat == ElixirStrategy.Force ? GCDPriority.ForcedGCD : GCDPriority.Elixir); - //Recuperate execution var recuperateStrat = strategy.Option(Track.Recuperate).As(); if (ShouldUseRecuperate(recuperateStrat)) QueueOGCD(AID.Recuperate, Player, recuperateStrat == RecuperateStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.Recuperate); - //Guard execution var guardStrat = strategy.Option(Track.Guard).As(); if (ShouldUseGuard(guardStrat, primaryTarget)) QueueOGCD(AID.Guard, Player, guardStrat == GuardStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.Guard); - //Purify execution var purifyStrat = strategy.Option(Track.Purify).As(); if (ShouldUsePurify(purifyStrat, primaryTarget)) QueueOGCD(AID.Purify, Player, purifyStrat == DefensiveStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.Purify); - //Sprint execution var sprintStrat = strategy.Option(Track.Sprint).As(); if (ShouldUseSprint(sprintStrat, primaryTarget)) QueueOGCD(AID.SprintPvP, Player, sprintStrat == DefensiveStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.Sprint); } #region Core Execution Helpers - //QueueGCD execution private void QueueGCD(AID aid, Actor? target, GCDPriority prio) { if (prio != GCDPriority.None) @@ -214,7 +202,6 @@ private void QueueGCD(AID aid, Actor? target, GCDPriority prio) } } } - //QueueOGCD execution private void QueueOGCD(AID aid, Actor? target, OGCDPriority prio, float basePrio = ActionQueue.Priority.Medium) { if (prio != OGCDPriority.None) From e389a0b25ad8cb692933fa98e049ebc67381d7a9 Mon Sep 17 00:00:00 2001 From: Akechi-kun <167795370+Akechi-kun@users.noreply.github.com> Date: Sun, 1 Dec 2024 05:16:18 -0800 Subject: [PATCH 06/12] cleanup GNB PvP --- BossMod/Autorotation/akechi/PvP/GNB.cs | 198 +++++++++++-------------- 1 file changed, 84 insertions(+), 114 deletions(-) diff --git a/BossMod/Autorotation/akechi/PvP/GNB.cs b/BossMod/Autorotation/akechi/PvP/GNB.cs index b7494a21d1..cc58bb6ddb 100644 --- a/BossMod/Autorotation/akechi/PvP/GNB.cs +++ b/BossMod/Autorotation/akechi/PvP/GNB.cs @@ -1,5 +1,4 @@ using FFXIVClientStructs.FFXIV.Client.Game.Gauge; -using FFXIVClientStructs.FFXIV.Component.GUI; using AID = BossMod.GNB.AID; using SID = BossMod.GNB.SID; @@ -10,24 +9,23 @@ namespace BossMod.Autorotation.akechi.PvP; public sealed class GNB(RotationModuleManager manager, Actor player) : RotationModule(manager, player) { #region Enums: Abilities / Strategies - //Actions tracked for Cooldown Planner execution public enum Track { - Burst, //Burst tracking - Combo, //Solid Barrel combo tracking - LimitBreak, //Limit Break tracking - GnashingFang, //Gnashing Fang action tracking - FatedCircle, //Fated Circle ability tracking - RoughDivide, //Rough Divide ability tracking - Zone, //Blasting Zone ability tracking - Corundum, //Heart of Corundum ability tracking + Burst, + Combo, + LimitBreak, + GnashingFang, + FatedCircle, + RoughDivide, + Zone, + Corundum, } public enum BurstStrategy { - Automatic, //Automatically execute based on conditions - Force, //Force burst actions regardless of conditions - Hold //Conserve resources and cooldowns + Automatic, + Force, + Hold } public enum ComboStrategy @@ -56,30 +54,28 @@ public enum ElixirStrategy public enum OffensiveStrategy { - Automatic, //Automatically decide when to use offensive abilities - Force, //Force the use of offensive abilities regardless of conditions - Delay //Delay the use of offensive abilities for strategic reasons + Automatic, + Force, + Delay } #endregion public static RotationModuleDefinition Definition() { - //Module title & signature var res = new RotationModuleDefinition("GNB (PvP)", "PvP Rotation Module", "Standard rotation (Akechi)", "Akechi", RotationModuleQuality.Basic, BitMask.Build((int)Class.GNB), 100); #region Custom strategies - //Burst strategy res.Define(Track.Burst).As("Burst", uiPriority: 190) .AddOption(BurstStrategy.Automatic, "Automatic", "Use everything optimally") .AddOption(BurstStrategy.Force, "Force", "Force everything") .AddOption(BurstStrategy.Hold, "Hold", "Hold everything"); - //Combo strategy + res.Define(Track.Combo).As("Combo", uiPriority: 190) .AddOption(ComboStrategy.Automatic, "Automatic", "Use combo optimally") .AddOption(ComboStrategy.Force, "Force", "Force combo") .AddOption(ComboStrategy.Hold, "Hold", "Hold combo"); - //Limit Break strategy + res.Define(Track.LimitBreak).As("Limit Break", uiPriority: 190) .AddOption(LimitBreakStrategy.Automatic, "Automatic", "Use Limit Break optimally") .AddOption(LimitBreakStrategy.Force, "Force", "Force Limit Break") @@ -92,21 +88,25 @@ public static RotationModuleDefinition Definition() .AddOption(OffensiveStrategy.Force, "Force", "Force", 0, 0, ActionTargets.Hostile, 30) .AddOption(OffensiveStrategy.Delay, "Delay", "Delay", 0, 0, ActionTargets.None, 30) .AddAssociatedActions(AID.GnashingFangPvP); + res.Define(Track.FatedCircle).As("Fated Circle", uiPriority: 150) .AddOption(OffensiveStrategy.Automatic, "Automatic", "Use normally") .AddOption(OffensiveStrategy.Force, "Force", "Force", 0, 0, ActionTargets.Hostile, 30) .AddOption(OffensiveStrategy.Delay, "Delay", "Delay", 0, 0, ActionTargets.None, 30) .AddAssociatedActions(AID.FatedCirclePvP); + res.Define(Track.RoughDivide).As("Rough Divide", uiPriority: 150) .AddOption(OffensiveStrategy.Automatic, "Automatic", "Use normally") .AddOption(OffensiveStrategy.Force, "Force", "Force", 0, 0, ActionTargets.Hostile, 30) .AddOption(OffensiveStrategy.Delay, "Delay", "Delay", 0, 0, ActionTargets.None, 30) .AddAssociatedActions(AID.RoughDividePvP); + res.Define(Track.Zone).As("Zone", uiPriority: 150) .AddOption(OffensiveStrategy.Automatic, "Automatic", "Use normally") .AddOption(OffensiveStrategy.Force, "Force", "Force", 0, 0, ActionTargets.Hostile, 30) .AddOption(OffensiveStrategy.Delay, "Delay", "Delay", 0, 0, ActionTargets.None, 30) .AddAssociatedActions(AID.BlastingZonePvP); + res.Define(Track.Corundum).As("Corundum", uiPriority: 150) .AddOption(OffensiveStrategy.Automatic, "Automatic", "Use normally") .AddOption(OffensiveStrategy.Force, "Force", "Force", 0, 0, ActionTargets.Hostile, 30) @@ -115,24 +115,22 @@ public static RotationModuleDefinition Definition() #endregion return res; - } #region Priorities - //Priority for GCDs used public enum GCDPriority { None = 0, - KeenEdge = 325, //1 - BrutalShell = 320, //2 - SolidBarrel = 315, //3 - BurstStrike = 340, //4 + KeenEdge = 325, + BrutalShell = 320, + SolidBarrel = 315, + BurstStrike = 340, Combo = 350, GnashingFang = 400, FatedCircle = 450, ForcedGCD = 900, } - //Priority for oGCDs used + public enum OGCDPriority { None = 0, @@ -146,70 +144,57 @@ public enum OGCDPriority #endregion #region Placeholders for Variables - //Cooldown Related - private float nmLeft; //Time left on No Mercy buff (20s base) - private float rdCD; //Time left on No Mercy cooldown (60s base) - private bool hasNM; //Checks self for No Mercy buff - private bool hasBlast; //Checks self for Ready To Blast buff - private bool hasRaze; //Checks self for Ready To Raze buff - private bool hasRip; //Checks self for Ready To Rip buff - private bool hasTear; //Checks self for Ready To Tear buff - private bool hasGouge; //Checks self for Ready To Gouge buff - private bool can1; //can Keen Edge - private bool can2; //can Brutal Shell - private bool can3; //can Solid Barrel - private bool can4; //can Burst Strike - private bool canGF; //can Gnashing Fang - private bool canFC; //can Fated Circle - private bool canZone; //can Blasting Zone - private bool canHyper; //can Hypervelocity - private bool canBrand; //can Fated Brand - private bool canRip; //can Jugular Rip - private bool canTear; //can Abdomen Tear - private bool canGouge; //can Eye Gouge - - //Misc - public bool LBready; //Checks if Limit Break is ready - public float GFcomboStep; //Current Gnashing Fang combo step (0-3) - public float comboStep; //Current combo step (0-4) - public bool inCombo; //Checks if in combo - public bool inGF; //Checks if in Gnashing Fang combo - public float GCDLength; //Current GCD length, adjusted by skill speed/haste (2.5s baseline) - public AID NextGCD; //Next global cooldown action to be used (needed for cartridge management) - private GCDPriority NextGCDPrio; //Priority of the next GCD, used for decision making on cooldowns + private float nmLeft; + private float rdCD; + private bool hasNM; + private bool hasBlast; + private bool hasRaze; + private bool hasRip; + private bool hasTear; + private bool hasGouge; + private bool canGF; + private bool canFC; + private bool canZone; + private bool canHyper; + private bool canBrand; + private bool canRip; + private bool canTear; + private bool canGouge; + + public bool LBready; + public float GFcomboStep; + public float comboStep; + public bool inCombo; + public bool inGF; + public float GCDLength; + public AID NextGCD; + private GCDPriority NextGCDPrio; #endregion #region Module Helpers - private float CD(AID aid) => World.Client.Cooldowns[ActionDefinitions.Instance.Spell(aid)!.MainCooldownGroup].Remaining; //Get remaining cooldown time for the specified action - private bool CanFitGCD(float deadline, int extraGCDs = 0) => GCD + GCDLength * extraGCDs < deadline; //Check if we can fit an additional GCD within the provided deadline - private AID ComboLastMove => (AID)World.Client.ComboState.Action; //Get the last action used in the combo sequence - private bool In3y(Actor? target) => Player.DistanceToHitbox(target) <= 2.9; //Check if the target is within melee range (3 yalms) - private bool In5y(Actor? target) => Player.DistanceToHitbox(target) <= 4.9; //Check if the target is within 5 yalms - private bool In20y(Actor? target) => Player.DistanceToHitbox(target) <= 19.9; //Check if the target is within 20 yalms - private bool In30y(Actor? target) => Player.DistanceToHitbox(target) <= 29.9; //Check if the target is within 30 yalms - private bool IsOffCooldown(AID aid) => World.Client.Cooldowns[ActionDefinitions.Instance.Spell(aid)!.MainCooldownGroup].Remaining < 0.6f; //Check if the desired action is ready (cooldown less than 0.6 seconds) - private int NumTargetsHitByAoE() => Hints.NumPriorityTargetsInAOECircle(Player.Position, 5); //Returns the number of targets hit by AoE within a 5-yalm radius around the player - public bool HasEffect(SID sid) => SelfStatusLeft(sid) > 0; //Check if the player has the specified status effect - public bool TargetHasEffect(SID sid, Actor? target) => StatusDetails(target, sid, Player.InstanceID, 1000).Left > 0; //Check if the target has the specified status effect + private float CD(AID aid) => World.Client.Cooldowns[ActionDefinitions.Instance.Spell(aid)!.MainCooldownGroup].Remaining; + private AID ComboLastMove => (AID)World.Client.ComboState.Action; + private bool In5y(Actor? target) => Player.DistanceToHitbox(target) <= 4.9; + private bool IsOffCooldown(AID aid) => World.Client.Cooldowns[ActionDefinitions.Instance.Spell(aid)!.MainCooldownGroup].Remaining < 0.6f; + public bool HasEffect(SID sid) => SelfStatusLeft(sid) > 0; + public bool TargetHasEffect(SID sid, Actor? target) => StatusDetails(target, sid, Player.InstanceID, 1000).Left > 0; public AID LimitBreak => HasEffect(SID.RelentlessRushPvP) ? AID.TerminalTriggerPvP : AID.RelentlessRushPvP; #endregion - public override void Execute(StrategyValues strategy, Actor? primaryTarget, float estimatedAnimLockDelay, bool isMoving) //Executes our actions + public override void Execute(StrategyValues strategy, Actor? primaryTarget, float estimatedAnimLockDelay, bool isMoving) { #region Variables - var gauge = World.Client.GetGauge(); //Retrieve Gunbreaker gauge + var gauge = World.Client.GetGauge(); var GunStep = gauge.AmmoComboStep; - - rdCD = CD(AID.RoughDividePvP); //Rough Divide cooldown (14s) - nmLeft = SelfStatusLeft(SID.NoMercyPvP, 7); //Remaining time for No Mercy buff (7s) - hasNM = nmLeft > 0; //Checks if No Mercy is active - hasBlast = HasEffect(SID.ReadyToBlastPvP); //Checks for Ready To Blast buff - hasRaze = HasEffect(SID.ReadyToRazePvP); //Checks for Ready To Raze buff - hasRip = HasEffect(SID.ReadyToRipPvP) || GunStep == 1; //Checks for Ready To Rip buff - hasTear = HasEffect(SID.ReadyToTearPvP) || GunStep == 2; //Checks for Ready To Tear buff - hasGouge = HasEffect(SID.ReadyToGougePvP); //Checks for Ready To Gouge buff - LBready = World.Party.LimitBreakLevel >= 1; //Checks if Limit Break is ready - //Misc + rdCD = CD(AID.RoughDividePvP); + nmLeft = SelfStatusLeft(SID.NoMercyPvP, 7); + hasNM = nmLeft > 0; + hasBlast = HasEffect(SID.ReadyToBlastPvP); + hasRaze = HasEffect(SID.ReadyToRazePvP); + hasRip = HasEffect(SID.ReadyToRipPvP) || GunStep == 1; + hasTear = HasEffect(SID.ReadyToTearPvP) || GunStep == 2; + hasGouge = HasEffect(SID.ReadyToGougePvP); + LBready = World.Party.LimitBreakLevel >= 1; GFcomboStep = ComboLastMove switch { AID.WickedTalonPvP => 3, @@ -227,23 +212,19 @@ public override void Execute(StrategyValues strategy, Actor? primaryTarget, floa }; inCombo = comboStep > 0; inGF = GFcomboStep > 0; - GCDLength = ActionSpeed.GCDRounded(World.Client.PlayerStats.SkillSpeed, World.Client.PlayerStats.Haste, Player.Level); //GCD based on skill speed and haste - NextGCD = AID.None; //Next global cooldown action to be used - NextGCDPrio = GCDPriority.None; //Priority of the next GCD, used for decision making on cooldowns + GCDLength = ActionSpeed.GCDRounded(World.Client.PlayerStats.SkillSpeed, World.Client.PlayerStats.Haste, Player.Level); + NextGCD = AID.None; + NextGCDPrio = GCDPriority.None; #region Minimal Requirements - can1 = ComboLastMove is AID.BurstStrikePvP; //KeenEdge conditions - can2 = ComboLastMove is AID.KeenEdgePvP; //BrutalShell conditions - can3 = ComboLastMove is AID.BrutalShellPvP; //SolidBarrel conditions - can4 = ComboLastMove is AID.SolidBarrelPvP; //BurstStrike conditions - canGF = IsOffCooldown(AID.GnashingFangPvP); //GnashingFang conditions - canFC = IsOffCooldown(AID.GnashingFangPvP); //FatedCircle conditions - canZone = IsOffCooldown(AID.BlastingZonePvP); //Zone conditions - canHyper = hasBlast && In5y(primaryTarget); //Hypervelocity conditions - canBrand = hasRaze && In5y(primaryTarget); //Fated Brand conditions - canRip = hasRip && In5y(primaryTarget); //Jugular Rip conditions - canTear = hasTear && In5y(primaryTarget); //Abdomen conditions - canGouge = hasGouge && In5y(primaryTarget); //Eye Gouge conditions + canGF = IsOffCooldown(AID.GnashingFangPvP); + canFC = IsOffCooldown(AID.GnashingFangPvP); + canZone = IsOffCooldown(AID.BlastingZonePvP); + canHyper = hasBlast && In5y(primaryTarget); + canBrand = hasRaze && In5y(primaryTarget); + canRip = hasRip && In5y(primaryTarget); + canTear = hasTear && In5y(primaryTarget); + canGouge = hasGouge && In5y(primaryTarget); #endregion #endregion @@ -251,11 +232,10 @@ public override void Execute(StrategyValues strategy, Actor? primaryTarget, floa var burstStrategy = burst.As(); var hold = burstStrategy == BurstStrategy.Hold; - if (strategy.Option(Track.Combo).As() == ComboStrategy.Force) //ST (without overcap protection) + if (strategy.Option(Track.Combo).As() == ComboStrategy.Force) QueueGCD(NextCombo(), primaryTarget, GCDPriority.ForcedGCD); #region Rotation Execution - //Determine and queue combo actions if (!hold && !inGF) QueueGCD(NextCombo(), primaryTarget, GCDPriority.Combo); @@ -299,14 +279,12 @@ public override void Execute(StrategyValues strategy, Actor? primaryTarget, floa #endregion - //Limit Break execution var lbStrat = strategy.Option(Track.LimitBreak).As(); if (ShouldUseLimitBreak(lbStrat, primaryTarget)) QueueOGCD(LimitBreak, primaryTarget, lbStrat == LimitBreakStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.LB); } #region Core Execution Helpers - //QueueGCD execution private void QueueGCD(AID aid, Actor? target, GCDPriority prio) { if (prio != GCDPriority.None) @@ -319,8 +297,6 @@ private void QueueGCD(AID aid, Actor? target, GCDPriority prio) } } } - - //QueueOGCD execution private void QueueOGCD(AID aid, Actor? target, OGCDPriority prio, float basePrio = ActionQueue.Priority.Medium) { if (prio != OGCDPriority.None) @@ -328,22 +304,19 @@ private void QueueOGCD(AID aid, Actor? target, OGCDPriority prio, float basePrio Hints.ActionsToExecute.Push(ActionID.MakeSpell(aid), target, basePrio + (int)prio); } } - #endregion #region Single-Target Helpers - private AID NextCombo() => ComboLastMove switch //Determines the next single-target action based on the last action used + private AID NextCombo() => ComboLastMove switch { - AID.SolidBarrelPvP => AID.BurstStrikePvP, //4, defaults back to 1 - AID.BrutalShellPvP => AID.SolidBarrelPvP, //3 - AID.KeenEdgePvP => AID.BrutalShellPvP, //2 - _ => AID.KeenEdgePvP, //1 + AID.SolidBarrelPvP => AID.BurstStrikePvP, + AID.BrutalShellPvP => AID.SolidBarrelPvP, + AID.KeenEdgePvP => AID.BrutalShellPvP, + _ => AID.KeenEdgePvP, }; #endregion #region Cooldown Helpers - - //Determines when to use No Mercy private bool ShouldUseRoughDivide(OffensiveStrategy strategy, Actor? target) => strategy switch { OffensiveStrategy.Automatic => @@ -354,7 +327,6 @@ private void QueueOGCD(AID aid, Actor? target, OGCDPriority prio, float basePrio _ => false }; - //Determines when to use Zone private bool ShouldUseZone(OffensiveStrategy strategy, Actor? target) => strategy switch { OffensiveStrategy.Automatic => @@ -368,7 +340,6 @@ private void QueueOGCD(AID aid, Actor? target, OGCDPriority prio, float basePrio _ => false }; - //Determines when to use Gnashing Fang private bool ShouldUseGnashingFang(OffensiveStrategy strategy, Actor? target) => strategy switch { OffensiveStrategy.Automatic => @@ -382,7 +353,6 @@ private void QueueOGCD(AID aid, Actor? target, OGCDPriority prio, float basePrio _ => false }; - //Determines when to use Fated Circle private bool ShouldUseFatedCircle(OffensiveStrategy strategy, Actor? target) => strategy switch { OffensiveStrategy.Automatic => From fdd28964ed4bd5c069e06202c9445cc397c53d82 Mon Sep 17 00:00:00 2001 From: Akechi-kun <167795370+Akechi-kun@users.noreply.github.com> Date: Sun, 1 Dec 2024 05:42:13 -0800 Subject: [PATCH 07/12] last little bit for now --- BossMod/Autorotation/Utility/RolePvPUtility.cs | 2 +- BossMod/Autorotation/akechi/PvE/DRG.cs | 2 +- BossMod/Autorotation/akechi/PvE/GNB.cs | 2 +- BossMod/Autorotation/akechi/PvE/PLD.cs | 2 +- BossMod/Autorotation/akechi/PvP/GNB.cs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/BossMod/Autorotation/Utility/RolePvPUtility.cs b/BossMod/Autorotation/Utility/RolePvPUtility.cs index 58dc20f137..86d1b5218f 100644 --- a/BossMod/Autorotation/Utility/RolePvPUtility.cs +++ b/BossMod/Autorotation/Utility/RolePvPUtility.cs @@ -56,7 +56,7 @@ public enum DefensiveStrategy public static RotationModuleDefinition Definition() { - var res = new RotationModuleDefinition("PvP: Utility", "PvP Rotation Module", "Utility Actions (PvP)", "Akechi", RotationModuleQuality.Excellent, + var res = new RotationModuleDefinition("Utility: PvP", "PvP Rotation Module", "PvP", "Akechi", RotationModuleQuality.Basic, BitMask.Build( Class.PLD, Class.WAR, Class.DRK, Class.GNB, Class.WHM, Class.SCH, Class.AST, Class.SGE, diff --git a/BossMod/Autorotation/akechi/PvE/DRG.cs b/BossMod/Autorotation/akechi/PvE/DRG.cs index aae0321cf7..16a735606a 100644 --- a/BossMod/Autorotation/akechi/PvE/DRG.cs +++ b/BossMod/Autorotation/akechi/PvE/DRG.cs @@ -140,7 +140,7 @@ public enum OffensiveStrategy public static RotationModuleDefinition Definition() { //Module title & signature - var res = new RotationModuleDefinition("DRG (Akechi)", "Standard Rotation Module", "Standard rotation (Akechi)", "Akechi", RotationModuleQuality.Good, BitMask.Build(Class.LNC, Class.DRG), 100); + var res = new RotationModuleDefinition("Akechi DRG", "Standard Rotation Module", "Standard rotation (Akechi)", "Akechi", RotationModuleQuality.Good, BitMask.Build(Class.LNC, Class.DRG), 100); #region Custom Strategies //Targeting strategy diff --git a/BossMod/Autorotation/akechi/PvE/GNB.cs b/BossMod/Autorotation/akechi/PvE/GNB.cs index ac05f9d7d4..505eafc8bf 100644 --- a/BossMod/Autorotation/akechi/PvE/GNB.cs +++ b/BossMod/Autorotation/akechi/PvE/GNB.cs @@ -123,7 +123,7 @@ public enum OffensiveStrategy public static RotationModuleDefinition Definition() { //Module title & signature - var res = new RotationModuleDefinition("GNB (Akechi)", "Standard Rotation Module", "Standard rotation (Akechi)", "Akechi", RotationModuleQuality.Good, BitMask.Build((int)Class.GNB), 100); + var res = new RotationModuleDefinition("Akechi GNB", "Standard Rotation Module", "Standard rotation (Akechi)", "Akechi", RotationModuleQuality.Good, BitMask.Build((int)Class.GNB), 100); #region Custom strategies //Targeting strategy diff --git a/BossMod/Autorotation/akechi/PvE/PLD.cs b/BossMod/Autorotation/akechi/PvE/PLD.cs index df6accf1b8..98dc753589 100644 --- a/BossMod/Autorotation/akechi/PvE/PLD.cs +++ b/BossMod/Autorotation/akechi/PvE/PLD.cs @@ -112,7 +112,7 @@ public enum OffensiveStrategy public static RotationModuleDefinition Definition() { //Define the rotation module - var res = new RotationModuleDefinition("PLD (Akechi)", "Standard Rotation Module", "Standard rotation (Akechi)", "Akechi", RotationModuleQuality.Ok, BitMask.Build((int)Class.GLA, (int)Class.PLD), 100); + var res = new RotationModuleDefinition("Akechi PLD", "Standard Rotation Module", "Standard rotation (Akechi)", "Akechi", RotationModuleQuality.Ok, BitMask.Build((int)Class.GLA, (int)Class.PLD), 100); //AoE Strategy: Manage AoE versus single-target rotations res.Define(Track.AoE).As("AoE", uiPriority: 200) diff --git a/BossMod/Autorotation/akechi/PvP/GNB.cs b/BossMod/Autorotation/akechi/PvP/GNB.cs index cc58bb6ddb..ca551b0e0c 100644 --- a/BossMod/Autorotation/akechi/PvP/GNB.cs +++ b/BossMod/Autorotation/akechi/PvP/GNB.cs @@ -63,7 +63,7 @@ public enum OffensiveStrategy public static RotationModuleDefinition Definition() { - var res = new RotationModuleDefinition("GNB (PvP)", "PvP Rotation Module", "Standard rotation (Akechi)", "Akechi", RotationModuleQuality.Basic, BitMask.Build((int)Class.GNB), 100); + var res = new RotationModuleDefinition("Akechi GNB (PvP)", "PvP Rotation Module", "PvP", "Akechi", RotationModuleQuality.Basic, BitMask.Build((int)Class.GNB), 100); #region Custom strategies res.Define(Track.Burst).As("Burst", uiPriority: 190) From db0de7ddd9bd75a229b82c1e9ace02d324a5f571 Mon Sep 17 00:00:00 2001 From: Akechi-kun <167795370+Akechi-kun@users.noreply.github.com> Date: Sun, 1 Dec 2024 22:35:58 -0800 Subject: [PATCH 08/12] move back --- BossMod/Autorotation/akechi/{PvE/DRG.cs => AkechiDRG.cs} | 4 ++-- BossMod/Autorotation/akechi/{PvE/GNB.cs => AkechiGNB.cs} | 4 ++-- BossMod/Autorotation/akechi/{PvE/PLD.cs => AkechiPLD.cs} | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) rename BossMod/Autorotation/akechi/{PvE/DRG.cs => AkechiDRG.cs} (99%) rename BossMod/Autorotation/akechi/{PvE/GNB.cs => AkechiGNB.cs} (99%) rename BossMod/Autorotation/akechi/{PvE/PLD.cs => AkechiPLD.cs} (99%) diff --git a/BossMod/Autorotation/akechi/PvE/DRG.cs b/BossMod/Autorotation/akechi/AkechiDRG.cs similarity index 99% rename from BossMod/Autorotation/akechi/PvE/DRG.cs rename to BossMod/Autorotation/akechi/AkechiDRG.cs index 16a735606a..a372dd062d 100644 --- a/BossMod/Autorotation/akechi/PvE/DRG.cs +++ b/BossMod/Autorotation/akechi/AkechiDRG.cs @@ -2,11 +2,11 @@ using AID = BossMod.DRG.AID; using SID = BossMod.DRG.SID; -namespace BossMod.Autorotation.akechi.PvE; +namespace BossMod.Autorotation.akechi; //Contribution by Akechi //Discord: @akechdz or 'Akechi' on Puni.sh for maintenance -public sealed class DRG(RotationModuleManager manager, Actor player) : RotationModule(manager, player) +public sealed class AkechiDRG(RotationModuleManager manager, Actor player) : RotationModule(manager, player) { #region Enums: Abilities / Strategies diff --git a/BossMod/Autorotation/akechi/PvE/GNB.cs b/BossMod/Autorotation/akechi/AkechiGNB.cs similarity index 99% rename from BossMod/Autorotation/akechi/PvE/GNB.cs rename to BossMod/Autorotation/akechi/AkechiGNB.cs index 505eafc8bf..b57487832c 100644 --- a/BossMod/Autorotation/akechi/PvE/GNB.cs +++ b/BossMod/Autorotation/akechi/AkechiGNB.cs @@ -3,13 +3,13 @@ using SID = BossMod.GNB.SID; using TraitID = BossMod.GNB.TraitID; -namespace BossMod.Autorotation.akechi.PvE; +namespace BossMod.Autorotation.akechi; //Contribution by Akechi //Discord @akechdz or 'Akechi' on Puni.sh for maintenance //This module supports <=2.47 SkS rotation as default (or 'Automatic') //With user adjustment, 'SlowGNB' or 'FastGNB' usage is achievable -public sealed class GNB(RotationModuleManager manager, Actor player) : RotationModule(manager, player) +public sealed class AkechiGNB(RotationModuleManager manager, Actor player) : RotationModule(manager, player) { #region Enums: Abilities / Strategies //Actions tracked for Cooldown Planner execution diff --git a/BossMod/Autorotation/akechi/PvE/PLD.cs b/BossMod/Autorotation/akechi/AkechiPLD.cs similarity index 99% rename from BossMod/Autorotation/akechi/PvE/PLD.cs rename to BossMod/Autorotation/akechi/AkechiPLD.cs index 98dc753589..0b8c6ecc66 100644 --- a/BossMod/Autorotation/akechi/PvE/PLD.cs +++ b/BossMod/Autorotation/akechi/AkechiPLD.cs @@ -2,11 +2,11 @@ using AID = BossMod.PLD.AID; using SID = BossMod.PLD.SID; -namespace BossMod.Autorotation.akechi.PvE; +namespace BossMod.Autorotation.akechi; //Contribution by Akechi //Discord: @akechdz or 'Akechi' on Puni.sh for maintenance -public sealed class PLD(RotationModuleManager manager, Actor player) : RotationModule(manager, player) +public sealed class AkechiPLD(RotationModuleManager manager, Actor player) : RotationModule(manager, player) { //Actions tracked for Cooldown Planner execution public enum Track @@ -417,7 +417,7 @@ public override void Execute(StrategyValues strategy, Actor? primaryTarget, floa //Determine and queue the next combo action based on AoE strategy var (comboAction, comboPrio) = ComboActionPriority(AOEStrategy, AoETargets, burstStrategy, burst.Value.ExpireIn); QueueGCD(comboAction, comboAction is AID.TotalEclipse or AID.Prominence ? Player : primaryTarget, - AOEStrategy is PLD.AOEStrategy.ForceST or PLD.AOEStrategy.ForceAoE ? GCDPriority.ForcedGCD : comboPrio); + AOEStrategy is AOEStrategy.ForceST or AOEStrategy.ForceAoE ? GCDPriority.ForcedGCD : comboPrio); //Execute Fight or Flight if conditions are met var fofStrat = strategy.Option(Track.FightOrFlight).As(); From fa9f3b39cd870471a2dd0eb821f1b2b68ebb2308 Mon Sep 17 00:00:00 2001 From: Akechi-kun <167795370+Akechi-kun@users.noreply.github.com> Date: Sun, 1 Dec 2024 22:44:20 -0800 Subject: [PATCH 09/12] requested changes --- BossMod/Autorotation/Utility/RolePvPUtility.cs | 2 +- BossMod/Autorotation/akechi/{PvP/GNB.cs => AkechiGNBPvP.cs} | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) rename BossMod/Autorotation/akechi/{PvP/GNB.cs => AkechiGNBPvP.cs} (98%) diff --git a/BossMod/Autorotation/Utility/RolePvPUtility.cs b/BossMod/Autorotation/Utility/RolePvPUtility.cs index 86d1b5218f..fd43ddfb2b 100644 --- a/BossMod/Autorotation/Utility/RolePvPUtility.cs +++ b/BossMod/Autorotation/Utility/RolePvPUtility.cs @@ -62,7 +62,7 @@ public static RotationModuleDefinition Definition() Class.WHM, Class.SCH, Class.AST, Class.SGE, Class.MNK, Class.DRG, Class.NIN, Class.SAM, Class.RPR, Class.VPR, Class.BRD, Class.MCH, Class.DNC, - Class.BLM, Class.SMN, Class.RDM, Class.PCT), 30); + Class.BLM, Class.SMN, Class.RDM, Class.PCT), 100, 30); res.Define(Track.Elixir).As("Elixir", uiPriority: 150) .AddOption(ElixirStrategy.Automatic, "Automatic") diff --git a/BossMod/Autorotation/akechi/PvP/GNB.cs b/BossMod/Autorotation/akechi/AkechiGNBPvP.cs similarity index 98% rename from BossMod/Autorotation/akechi/PvP/GNB.cs rename to BossMod/Autorotation/akechi/AkechiGNBPvP.cs index ca551b0e0c..90d793a961 100644 --- a/BossMod/Autorotation/akechi/PvP/GNB.cs +++ b/BossMod/Autorotation/akechi/AkechiGNBPvP.cs @@ -2,11 +2,11 @@ using AID = BossMod.GNB.AID; using SID = BossMod.GNB.SID; -namespace BossMod.Autorotation.akechi.PvP; +namespace BossMod.Autorotation.akechi; //Contribution by Akechi //Discord @akechdz or 'Akechi' on Puni.sh for maintenance -public sealed class GNB(RotationModuleManager manager, Actor player) : RotationModule(manager, player) +public sealed class AkechiGNBPvP(RotationModuleManager manager, Actor player) : RotationModule(manager, player) { #region Enums: Abilities / Strategies public enum Track @@ -63,7 +63,7 @@ public enum OffensiveStrategy public static RotationModuleDefinition Definition() { - var res = new RotationModuleDefinition("Akechi GNB (PvP)", "PvP Rotation Module", "PvP", "Akechi", RotationModuleQuality.Basic, BitMask.Build((int)Class.GNB), 100); + var res = new RotationModuleDefinition("Akechi GNB (PvP)", "PvP Rotation Module", "PvP", "Akechi", RotationModuleQuality.Basic, BitMask.Build((int)Class.GNB), 100, 30); #region Custom strategies res.Define(Track.Burst).As("Burst", uiPriority: 190) From bd6db75897f56a5cc1fd8c88d4c47fa7b857cdc5 Mon Sep 17 00:00:00 2001 From: Andrew Gilewsky Date: Fri, 6 Dec 2024 20:17:09 +0000 Subject: [PATCH 10/12] Daily quest debugger for liza. --- BossMod/Debug/DebugQuests.cs | 202 +++++++++++++++++++++++++++++++ BossMod/Debug/MainDebugWindow.cs | 5 + FFXIVClientStructs | 2 +- 3 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 BossMod/Debug/DebugQuests.cs diff --git a/BossMod/Debug/DebugQuests.cs b/BossMod/Debug/DebugQuests.cs new file mode 100644 index 0000000000..61238fff61 --- /dev/null +++ b/BossMod/Debug/DebugQuests.cs @@ -0,0 +1,202 @@ +using Dalamud.Interface.Utility.Raii; +using FFXIVClientStructs.FFXIV.Client.Game; +using FFXIVClientStructs.FFXIV.Client.Game.Control; +using FFXIVClientStructs.FFXIV.Client.Game.Event; +using FFXIVClientStructs.Interop; +using ImGuiNET; +using Lumina.Excel.Sheets; + +namespace BossMod; + +unsafe class DebugQuests +{ + private class NpcData + { + public readonly List AllQuests = []; + public List AvailableQuests = []; + public bool OutrankAll; + } + + private record struct Rng(uint S0, uint S1 = 0, uint S2 = 0, uint S3 = 0) + { + public int Next(int range) + { + (S0, S1, S2, S3) = (S3, Transform(S0, S1), S1, S2); + return (int)(S1 % range); + } + + // returns new value for s1 + private static uint Transform(uint s0, uint s1) + { + var temp = s0 ^ (s0 << 11); + return s1 ^ temp ^ ((temp ^ (s1 >> 11)) >> 8); + } + } + + private readonly UITree _tree = new(); + private readonly Dictionary _dailyQuests = []; + + public DebugQuests() + { + foreach (var q in Service.LuminaSheet()!) + { + if (q.RepeatIntervalType == 1 && q.QuestRepeatFlag.RowId == 0) + { + _dailyQuests.GetOrAdd(q.IssuerStart.RowId).AllQuests.Add(q); + } + } + foreach (var quests in _dailyQuests.Values) + { + quests.AllQuests.SortBy(q => (q.Unknown11 == 3, q.RowId)); + var rankMin = (int)quests.AllQuests.Select(q => q.BeastReputationRank.RowId).Min(); + var rankMax = (int)quests.AllQuests.Select(q => q.BeastReputationRank.RowId).Max(); + var rankCur = RankForQuest(quests.AllQuests[0], out var rankedUp); + quests.OutrankAll = quests.AllQuests.All(q => RankForQuest(q, out _) > q.BeastReputationRank.RowId); + quests.AvailableQuests = CalculateAvailable(quests.AllQuests, QuestManager.Instance()->DailyQuestSeed, quests.OutrankAll, rankCur, rankedUp); + } + } + + public void Draw() + { + var qm = QuestManager.Instance(); + foreach (var n in _tree.Node($"Player (seed={qm->DailyQuestSeed})###player")) + { + uint i = 1; + foreach (ref var t in qm->BeastReputation) + { + _tree.LeafNode($"Tribe {i} '{Service.LuminaRow(i)?.Name}': rank={t.Rank}, value={t.Value}"); + ++i; + } + } + + var fwk = EventFramework.Instance(); + foreach (var n in _tree.Node($"Dailies map ({fwk->DailyQuests.Entries.Count})###dailies_map")) + { + foreach (var (baseId, entry) in fwk->DailyQuests.Entries) + { + foreach (var nnpc in _tree.Node($"NPC {baseId:X}: tribe={entry.TribeId} '{Service.LuminaRow(entry.TribeId)?.Name}', ranks={entry.RankRequirementMin}-{entry.RankRequirementMax}, dirty={entry.Dirty}###{baseId}")) + { + int i = 0; + foreach (var e in entry.HandlersNormal) + { + _tree.LeafNode($"[Gx {i++}] {e.Value->QuestId} '{Service.LuminaRow(0x10000u | e.Value->QuestId)?.Name}'"); + } + + i = 0; + foreach (var e in entry.HandlersExclusive) + { + _tree.LeafNode($"[G3 {i++}] {e.Value->QuestId} '{Service.LuminaRow(0x10000u | e.Value->QuestId)?.Name}'"); + } + } + } + } + + foreach (var n in _tree.Node("Full daily map")) + { + foreach (var (baseId, quests) in _dailyQuests) + { + foreach (var nnpc in _tree.Node($"NPC {baseId:X}, outrank={quests.OutrankAll}")) + { + int i = 0; + foreach (var q in quests.AllQuests) + { + _tree.LeafNode($"[{i++}] G{q.Unknown11} {(IsEligible(q) ? "+" : "-")}{(quests.AvailableQuests.Contains(q) ? "+" : "-")} {q.RowId} '{q.Name}'"); + } + } + } + } + + var target = TargetSystem.Instance()->GetTargetObject(); + using (ImRaii.Disabled(target == null)) + if (ImGui.Button("Compare...")) + CompareLogic(target->BaseId); + } + + private int RankForQuest(Quest q, out bool rankedUp) + { + var tribe = q.BeastTribe.RowId; + var curRank = QuestManager.Instance()->BeastReputation[(int)tribe - 1].Rank; + rankedUp = (curRank & 0x80) != 0; + return curRank & 0x7F; + } + + private bool IsEligible(Quest q) + { + var playerRank = RankForQuest(q, out var rankedUp); + return IsEligible(q, playerRank, rankedUp); + } + + private bool IsEligible(Quest q, int playerRank, bool rankedUp) + { + var questRank = q.BeastReputationRank.RowId; + return rankedUp ? questRank == playerRank : questRank <= playerRank; + } + + private List CalculateAvailable(List potential, byte seed, bool outrankAll, int playerRank, bool rankedUp) + { + List eligible = [.. potential.Where(q => IsEligible(q, playerRank, rankedUp))]; + List available = []; + if (eligible.Count == 0) + return available; + + var rng = new Rng(seed); + if (outrankAll) + { + for (int i = 0, cnt = Math.Min(eligible.Count, 3); i < cnt; ++i) + { + var index = rng.Next(eligible.Count); + while (available.Contains(eligible[index])) + index = (index + 1) % eligible.Count; + available.Add(eligible[index]); + } + } + else + { + var firstExclusive = eligible.FindIndex(q => q.Unknown11 == 3); + if (firstExclusive >= 0) + available.Add(eligible[firstExclusive + rng.Next(eligible.Count - firstExclusive)]); + else + firstExclusive = eligible.Count; + + for (int i = available.Count, cnt = Math.Min(firstExclusive, 3); i < cnt; ++i) + { + var index = rng.Next(firstExclusive); + while (available.Contains(eligible[index])) + index = (index + 1) % firstExclusive; + available.Add(eligible[index]); + } + } + return available; + } + + private void CompareLogic(uint npcId) + { + var fwk = EventFramework.Instance(); + var loc = fwk->DailyQuests.Entries.WithOps.Tree.FindLowerBound(npcId); + if (loc.KeyEquals(npcId)) + { + Span bools = [false, true]; + Span availStorage = [0, 0, 0]; + var avail = (QuestEventHandler**)availStorage.GetPointer(0); + Service.Log($"Comparing {npcId:X} r={loc.Bound->_Myval.Item2.RankRequirementMin}-{loc.Bound->_Myval.Item2.RankRequirementMax}"); + for (int rank = loc.Bound->_Myval.Item2.RankRequirementMin; rank <= loc.Bound->_Myval.Item2.RankRequirementMax; ++rank) + { + foreach (var rankInRange in bools) + { + foreach (var exactMatch in bools) + { + for (int seed = 0; seed < 256; ++seed) + { + var numAvail = (int)fwk->DailyQuests.CalculateAvailableQuests(null, loc.Bound, (byte)seed, rankInRange, (byte)rank, exactMatch, avail); + var ourAvail = CalculateAvailable(_dailyQuests[npcId].AllQuests, (byte)seed, !rankInRange, rank, exactMatch); + if (ourAvail.Count != numAvail || Enumerable.Range(0, numAvail).Any(i => ((uint)avail[i]->QuestId | 0x10000) != ourAvail[i].RowId)) + { + Service.Log($"Mismatch: s={seed},rank={rank}/{rankInRange}/{exactMatch}: game={string.Join('/', Enumerable.Range(0, numAvail).Select(i => (uint)avail[i]->QuestId | 0x10000))}, our={string.Join('/', ourAvail.Select(q => q.RowId))}"); + } + } + } + } + } + } + } +} diff --git a/BossMod/Debug/MainDebugWindow.cs b/BossMod/Debug/MainDebugWindow.cs index ceae357355..cb7a11d86e 100644 --- a/BossMod/Debug/MainDebugWindow.cs +++ b/BossMod/Debug/MainDebugWindow.cs @@ -22,6 +22,7 @@ namespace BossMod; private readonly DebugAutorotation _debugAutorot = new(autorot); private readonly DebugAddon _debugAddon = new(); private readonly DebugTiming _debugTiming = new(); + private readonly DebugQuests _debugQuests = new(); //private readonly DebugVfx _debugVfx = new(); protected override void Dispose(bool disposing) @@ -164,6 +165,10 @@ public override unsafe void Draw() { DrawLimitBreak(); } + if (ImGui.CollapsingHeader("Quests")) + { + _debugQuests.Draw(); + } } private unsafe void DrawStatuses() diff --git a/FFXIVClientStructs b/FFXIVClientStructs index 49cbc3273c..320bfce636 160000 --- a/FFXIVClientStructs +++ b/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 49cbc3273c7cc792d80a06edc9837657d9cf2d42 +Subproject commit 320bfce636d0e648ab0ebc8e73e57ac189889e2e From 00b0b0721f61689ed5b77ae2dbd995fe168c0cc9 Mon Sep 17 00:00:00 2001 From: Andrew Gilewsky Date: Fri, 6 Dec 2024 21:24:40 +0000 Subject: [PATCH 11/12] Some tweaks to ai movement delay. --- BossMod/AI/AIBehaviour.cs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/BossMod/AI/AIBehaviour.cs b/BossMod/AI/AIBehaviour.cs index b561dc3ebc..c45c39d995 100644 --- a/BossMod/AI/AIBehaviour.cs +++ b/BossMod/AI/AIBehaviour.cs @@ -18,8 +18,7 @@ sealed class AIBehaviour(AIController ctrl, RotationModuleManager autorot) : IDi private bool _followMaster; // if true, our navigation target is master rather than primary target - this happens e.g. in outdoor or in dungeons during gathering trash private WPos _masterPrevPos; private DateTime _masterLastMoved; - private DateTime? _navDecisionTimeMade; - private TimeSpan NavDecisionTime => _navDecisionTimeMade != null ? WorldState.CurrentTime - _navDecisionTimeMade!.Value : TimeSpan.Zero; + private DateTime _navStartTime; // if current time is < this, navigation won't start public void Dispose() { @@ -40,6 +39,7 @@ public void Execute(Actor player, Actor master) bool pyreticImminent = autorot.Hints.ImminentSpecialMode.mode == AIHints.SpecialMode.Pyretic && autorot.Hints.ImminentSpecialMode.activation <= WorldState.FutureTime(1); bool misdirectionMode = autorot.Hints.ImminentSpecialMode.mode == AIHints.SpecialMode.Misdirection && autorot.Hints.ImminentSpecialMode.activation <= WorldState.CurrentTime; bool forbidTargeting = _config.ForbidActions || _afkMode || gazeImminent || pyreticImminent; + bool hadNavi = _naviDecision.Destination != null; Targeting target = new(); if (!forbidTargeting) @@ -64,11 +64,8 @@ public void Execute(Actor player, Actor master) bool moveWithMaster = masterIsMoving && _followMaster && master != player; ForceMovementIn = moveWithMaster || gazeImminent || pyreticImminent ? 0 : _naviDecision.LeewaySeconds; - if (_naviDecision.Destination == null) - _navDecisionTimeMade = null; - - if (_naviDecision.Destination != null && _navDecisionTimeMade == null) - _navDecisionTimeMade = WorldState.CurrentTime; + if (_config.MoveDelay > 0 && !hadNavi && _naviDecision.Destination != null) + _navStartTime = WorldState.FutureTime(_config.MoveDelay); UpdateMovement(player, master, target, gazeImminent || pyreticImminent, misdirectionMode ? autorot.Hints.MisdirectionThreshold : default, !forbidTargeting ? autorot.Hints.ActionsToExecute : null); } @@ -207,7 +204,7 @@ private void UpdateMovement(Actor player, Actor master, Targeting target, bool g { var toDest = _naviDecision.Destination != null ? _naviDecision.Destination.Value - player.Position : new(); var distSq = toDest.LengthSq(); - ctrl.NaviTargetPos = NavDecisionTime.TotalMilliseconds >= (_config.MoveDelay * 1000) ? _naviDecision.Destination : null; + ctrl.NaviTargetPos = WorldState.CurrentTime >= _navStartTime ? _naviDecision.Destination : null; ctrl.NaviTargetVertical = master != player ? master.PosRot.Y : null; ctrl.AllowInterruptingCastByMovement = player.CastInfo != null && _naviDecision.LeewaySeconds <= player.CastInfo.RemainingTime - 0.5; ctrl.ForceCancelCast = false; From 335cf1a80eddc3ef17b6fc09d6ebe719914819c6 Mon Sep 17 00:00:00 2001 From: Andrew Gilewsky Date: Fri, 6 Dec 2024 23:04:49 +0000 Subject: [PATCH 12/12] Fixed discord invite link. --- BossMod/Config/AboutTab.cs | 2 +- README.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/BossMod/Config/AboutTab.cs b/BossMod/Config/AboutTab.cs index d70a803708..1f36955643 100644 --- a/BossMod/Config/AboutTab.cs +++ b/BossMod/Config/AboutTab.cs @@ -64,7 +64,7 @@ public void Draw() using (ImRaii.PushColor(ImGuiCol.Button, DiscordColor.ABGR)) if (ImGui.Button("Puni.sh Discord", new(180, 0))) - _lastErrorMessage = OpenLink("https://discord.gg/punishxiv"); + _lastErrorMessage = OpenLink("https://discord.gg/Zzrcc8kmvy"); ImGui.SameLine(); if (ImGui.Button("Boss Mod Repository", new(180, 0))) _lastErrorMessage = OpenLink("https://github.com/awgil/ffxiv_bossmod"); diff --git a/README.md b/README.md index 789559d57a..b01364e3c5 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ -Boss Mod (vbm) is a Dalamud plugin for FFXIV that provides boss fight radar, auto-rotation, cooldown planning, and AI. All of the its modules can be toggled individually. Support for it can be found in the [Puni.sh Discord server](https://discord.gg/punishxiv). +Boss Mod (vbm) is a Dalamud plugin for FFXIV that provides boss fight radar, auto-rotation, cooldown planning, and AI. All of the its modules can be toggled individually. Support for it can be found in the [Puni.sh Discord server](https://discord.gg/Zzrcc8kmvy). _Licensed under the terms of the ![BSD 3-Clause License](/LICENSE)_ @@ -42,7 +42,7 @@ VBM's AI module was created to automate movement during boss fights. With the he The AI will move your character based on safe zones determined by a boss's module, which are also displayed on the radar. It also tries to keep you within range of the enemies you are attacking while you're in combat with them. -An example of a plugin that utilizies VBM's AI module is [AutoDuty](https://github.com/ffxivcode/AutoDuty), which is supported in the same [Discord server](https://discord.gg/punishxiv) that VBM is supported in. +An example of a plugin that utilizies VBM's AI module is [AutoDuty](https://github.com/ffxivcode/AutoDuty), which is supported in the same [Discord server](https://discord.gg/Zzrcc8kmvy) that VBM is supported in.

@@ -57,7 +57,7 @@ Settings can be accessed via the Plugin Installer or using the chat command `/vb When you've found a bug or think you have some issue with the plugin, please do the following: -1. Ask in [Discord](https://discord.gg/punishxiv): it might be a known issue or people might be able to help you quickly +1. Ask in [Discord](https://discord.gg/Zzrcc8kmvy): it might be a known issue or people might be able to help you quickly 2. Gather extra information to aid in investigating the issue: 1. Set log level to "Debug" (type `/xldev`, select "Dalamud" -> "Set log level" -> "Debug") 2. Start replay recording (type `/vbm r` and hit "Start Recording")