diff --git a/BossMod/ActionQueue/Tanks/GNB.cs b/BossMod/ActionQueue/Tanks/GNB.cs index 7e8bf67f30..370e59b964 100644 --- a/BossMod/ActionQueue/Tanks/GNB.cs +++ b/BossMod/ActionQueue/Tanks/GNB.cs @@ -152,6 +152,7 @@ public enum SID : uint HeartOfCorundumPvP = 4295, // applied by Heart of Corundum to self CatharsisOfCorundumPvP = 4296, // applied by Heart of Corundum to self RelentlessRushPvP = 3052, + RelentlessShrapnelPvP = 3053, //Shared Elixir = ClassShared.AID.Elixir, diff --git a/BossMod/Autorotation/Standard/akechi/AkechiTools.cs b/BossMod/Autorotation/Standard/akechi/AkechiTools.cs index 326426cb7c..21aafa6744 100644 --- a/BossMod/Autorotation/Standard/akechi/AkechiTools.cs +++ b/BossMod/Autorotation/Standard/akechi/AkechiTools.cs @@ -2,286 +2,100 @@ namespace BossMod.Autorotation.akechi; -#region Shared Enums: Strategies +#region Shared Enums /// -/// The SharedTrack enum used for AOE and Hold strategies, typically for modules featuring damage rotations. -///
This enum defines tracks that can be used for all PvE classes and jobs, such as strategies containing executing standard rotations or explicitly holding abilities.
-/// Example Given: -///
- public enum Track { NoMercy = SharedTrack.Count }
-/// Explanation: -///
- Track is the enum for tracking specific abilities on user's specific rotation module.
-///
- NoMercy is the example enum of a specific ability being tracked on user's specific rotation module.
-///
- SharedTrack.Count is the shared track being used for executing our shared strategies listed above, called using .
+/// SharedTrack enum for AOE and Hold strategies, used in damage rotations for all PvE classes and jobs. ///
-/// - All strategies listed under into user's rotation module public enum SharedTrack { - /// - /// The main strategy for tracking single-target and AOE rotations. - /// - /// - All strategies listed under into user's rotation module + /// Tracks single-target and AOE rotations. AOE, - /// - /// The main strategy used for tracking when to hold any buffs, gauge, or cooldown abilties for optimal usage. - /// - /// - All strategies listed under into user's rotation module + /// Tracks holding buffs, gauge, or cooldown abilities for optimal usage. Hold, - /// - /// Represents the total count of strategies available inside this specific track. We generally never actually use this as a strategy since there isn't any logic really linked to this besides the counting. - /// + /// Represents the total count of strategies in this track. Count } /// -/// The Default Strategy enum used for tracking single-target and AOE strategies. -/// Example Given:
-/// - strategy.Option(SharedTrack.AOE).As<>(); -/// Explanation:
-/// - "strategy" is the parameter for tracking a specific strategy for a specific ability in the user's rotation module.
-/// - "Option" are the relative options for user's specific strategy.
-/// - "(SharedTrack.AOE)" is the enum representing all options relating to this custom strategy being tracked in the user's rotation module.
-/// - ".As<>();" is the relative strategy for user's specific ability being tracked in the user's rotation module. +/// AOEStrategy enum for tracking single-target and AOE strategies. +/// NOTE: For jobs with combos that have no relative combo timer (e.g. BLM), and are essentially the same function. ///
-/// - All strategies listed under into user's rotation module public enum AOEStrategy { - /// - /// The default strategy used for automatically executing the rotation.
- /// This can also be called using , or also as `strategy.Automatic()` in some cases. - /// Example:
- /// - strategy.Option(SharedTrack.AOE).As<>() == - /// Explanation:
- /// - "strategy.Option(SharedTrack.AOE).As<>()" is the full function (or local variable if user desires) for calling .
- /// - "" is the chosen option for this specific strategy.
- ///
- /// - The most optimal rotation automatically executed. - Automatic, + /// Executes the most optimal rotation automatically.
+ /// Finishes any combo if currently inside one.
+ AutoFinish, - /// - /// The main strategy used for force-executing the single-target rotation.
- /// This can also be called using , or also as `strategy.ForceST()` in some cases. - /// Example:
- /// - strategy.Option(SharedTrack.AOE).As<>() == - /// Explanation:
- /// - "strategy.Option(SharedTrack.AOE).As<>()" is the full function (or local variable if user desires) for calling .
- /// - "" is the chosen option for this specific strategy.
- ///
- /// - The single-target rotation force-executed, regardless of any conditions. + /// Executes the most optimal rotation automatically.
+ /// Breaks any combo if currently inside one.
+ AutoBreak, + + /// Forces execution of the single-target rotation. ForceST, - /// - /// The main strategy used for force-executing the AOE rotation.
- /// This can also be called using , or also as `strategy.ForceAOE()` in some cases. - /// Example:
- /// - strategy.Option(SharedTrack.AOE).As<>() == - /// Explanation:
- /// - "strategy.Option(SharedTrack.AOE).As<>()" is the full function (or local variable if user desires) for calling .
- /// - "" is the chosen option for this specific strategy.
- ///
- /// - The AOE rotation force-executed, regardless of any conditions. + /// Forces execution of the AOE rotation. ForceAOE } /// -/// The Default Strategy enum used for tracking when to hold any buffs, gauge, or cooldown abilties for optimal usage. -/// Example Given:
-/// - strategy.Option(SharedTrack.Hold).As<>(); -/// Explanation:
-/// - "strategy" is the parameter for tracking a specific strategy for a specific ability in the user's rotation module.
-/// - "Option" are the relative options for user's specific strategy.
-/// - "(SharedTrack.Hold)" is the enum representing all options relating to this custom strategy being tracked in the user's rotation module.
-/// - ".As<>();" is the relative strategy for user's specific ability being tracked in the user's rotation module. +/// HoldStrategy enum for tracking when to hold buffs, gauge, or cooldown abilities. ///
-/// - All strategies listed under into user's rotation module public enum HoldStrategy { - /// - /// The default strategy used for not holding any buffs, gauge, or cooldown abilties.
- /// This can also be called using , or also as `strategy.DontHold()` in some cases. - /// Example:
- /// - strategy.Option(SharedTrack.Hold).As<>() == - /// Explanation:
- /// - "strategy.Option(SharedTrack.Hold).As<>()" is the full function (or local variable if user desires) for calling .
- /// - "" is the chosen option for this specific strategy.
- ///
- /// - The availability of using all buffs, gauge, or cooldown abilties. + /// Uses all buffs, gauge, and cooldown abilities without restriction. DontHold, - /// - /// The main strategy used for only holding any ability that is cooldown-related.
- /// This can also be called using , or also as `strategy.HoldCDs()` in some cases. - /// Example:
- /// - strategy.Option(SharedTrack.Hold).As<>() == - /// Explanation:
- /// - "strategy.Option(SharedTrack.Hold).As<>()" is the full function (or local variable if user desires) for calling .
- /// - "" is the chosen option for this specific strategy.
- ///
- /// - The availability of using all buffs and gauge abilties, but forbidden from using any cooldowns. + /// Holds all cooldown-related abilities only. HoldCooldowns, - /// - /// The main strategy used for only holding any ability that is gauge-related.
- /// This can also be called using , or also as `strategy.HoldGauge()` in some cases. - /// Example:
- /// - strategy.Option(SharedTrack.Hold).As<>() == - /// Explanation:
- /// - "strategy.Option(SharedTrack.Hold).As<>()" is the full function (or local variable if user desires) for calling .
- /// - "" is the chosen option for this specific strategy.
- ///
- /// - The availability of using all buffs and cooldowns, but forbidden from using any gauge abilties. + /// Holds all gauge-related abilities only. HoldGauge, - /// - /// The main strategy used for only holding any ability that is buff-related.
- /// This can also be called using , or also as `strategy.HoldBuffs()` in some cases. - /// Example:
- /// - strategy.Option(SharedTrack.Hold).As<>() == - /// Explanation:
- /// - "strategy.Option(SharedTrack.Hold).As<>()" is the full function (or local variable if user desires) for calling .
- /// - "" is the chosen option for this specific strategy.
- ///
- /// - The availability of using all cooldowns and gauge abilties, but forbidden from using any buffs. + /// Holds all buff-related abilities only. HoldBuffs, - /// - /// The main strategy used for holding any ability that is buff, cooldown, or gauge-related.
- /// This can also be called using , or also as `strategy.HoldAll()` in some cases. - /// Example:
- /// - strategy.Option(SharedTrack.Hold).As<>() == - /// Explanation:
- /// - "strategy.Option(SharedTrack.Hold).As<>()" is the full function (or local variable if user desires) for calling .
- /// - "" is the chosen option for this specific strategy.
- ///
- /// - The forbiddance from using all buffs, cooldowns and gauge abilties. + /// Holds all buffs, cooldowns, and gauge abilities. HoldEverything } /// -/// The Default Strategy enum used for allowing or forbidding use of module-specific GCD abilities. -/// Example Given:
-/// - strategy.Option(Track.SonicBreak).As<>() -/// Explanation:
-/// - "strategy" is the parameter for tracking a specific strategy for a specific ability in the user's rotation module.
-/// - "Option" are the relative options for user's specific strategy.
-/// - "(Track.SonicBreak)" is the user's module-specific GCD ability enum being tracked.
-/// - ".As<>()" is the relative Default strategy for user's module-specific GCD ability being tracked in the user's rotation module. +/// GCDStrategy enum for managing module-specific GCD abilities. ///
-/// - All strategies listed under . public enum GCDStrategy { - /// - /// The default strategy used for automatically executing user's module-specific GCD ability. - /// Example:
- /// - strategy.Option(Track.SonicBreak).As<>() == - /// Explanation:
- /// - "strategy.Option(Track.SonicBreak).As<>()" is the full function (or local variable if user desires) for calling .
- /// - "" is the chosen option for this specific strategy.
- ///
- /// - Automatic execution of ability based on user's logic. + /// Executes the ability automatically based on user logic. Automatic, - /// - /// The main strategy used for force-executing user's module-specific GCD ability. - /// Example:
- /// - strategy.Option(Track.SonicBreak).As<>() == - /// Explanation:
- /// - "strategy.Option(Track.SonicBreak).As<>()" is the full function (or local variable if user desires) for calling .
- /// - "" is the chosen option for this specific strategy.
- ///
- /// - Forced execution of user's module-specific GCD ability. + /// Forces execution of the ability. Force, - /// - /// The main strategy used for forbidding use of user's module-specific GCD ability. - /// Example:
- /// - strategy.Option(Track.SonicBreak).As<>() == - /// Explanation:
- /// - "strategy.Option(Track.SonicBreak).As<>()" is the full function (or local variable if user desires) for calling .
- /// - "" is the chosen option for this specific strategy.
- ///
- /// - Forbiddance of execution for user's module-specific GCD ability. + /// Forbids execution of the ability. Delay } /// -/// The Default Strategy enum used for allowing or forbidding use of module-specific GCD abilities. -/// Example Given:
-/// - strategy.Option(Track.NoMercy).As<>() -/// Explanation:
-/// - "strategy" is the parameter for tracking a specific strategy for a specific ability in the user's rotation module.
-/// - "Option" are the relative options for user's specific strategy.
-/// - "(Track.NoMercy)" is the user's module-specific OGCD ability enum being tracked.
-/// - ".As<>()" is the relative Default strategy for user's module-specific OGCD ability being tracked in the user's rotation module. +/// OGCDStrategy enum for managing module-specific OGCD abilities. ///
-/// - All strategies listed under . public enum OGCDStrategy { - /// - /// The default strategy used for automatically executing user's module-specific OGCD ability. - /// Example:
- /// - strategy.Option(Track.NoMercy).As<>() == - /// Explanation:
- /// - "strategy.Option(Track.NoMercy).As<>()" is the full function (or local variable if user desires) for calling .
- /// - "" is the chosen option for this specific strategy.
- ///
- /// - Automatic execution of ability based on user's logic. + /// Executes the ability automatically based on user logic. Automatic, - /// - /// The main strategy used for force-executing user's module-specific OGCD ability. - /// Example:
- /// - strategy.Option(Track.NoMercy).As<>() == - /// Explanation:
- /// - "strategy.Option(Track.NoMercy).As<>()" is the full function (or local variable if user desires) for calling .
- /// - "" is the chosen option for this specific strategy.
- ///
- /// - Forced execution of user's module-specific OGCD ability. + /// Forces execution of the ability. Force, - /// - /// The main strategy used for force-executing user's module-specific OGCD ability in the very next weave window. - /// Example:
- /// - strategy.Option(Track.NoMercy).As<>() == - /// Explanation:
- /// - "strategy.Option(Track.NoMercy).As<>()" is the full function (or local variable if user desires) for calling .
- /// - "" is the chosen option for this specific strategy.
- ///
- /// - Forced execution of user's module-specific OGCD ability inside next weave. + /// Forces execution of the ability in the very next weave window. AnyWeave, - /// - /// The main strategy used for force-executing user's module-specific OGCD ability in the first-half of very next weave window. - /// Example:
- /// - strategy.Option(Track.NoMercy).As<>() == - /// Explanation:
- /// - "strategy.Option(Track.NoMercy).As<>()" is the full function (or local variable if user desires) for calling .
- /// - "" is the chosen option for this specific strategy.
- ///
- /// - Forced execution of user's module-specific OGCD ability inside next early-weave. + /// Forces execution of the ability in the very next EARLY-weave window. EarlyWeave, - /// - /// The main strategy used for force-executing user's module-specific OGCD ability in the second-half of very next weave window. - /// Example:
- /// - strategy.Option(Track.NoMercy).As<>() == - /// Explanation:
- /// - "strategy.Option(Track.NoMercy).As<>()" is the full function (or local variable if user desires) for calling .
- /// - "" is the chosen option for this specific strategy.
- ///
- /// - Forced execution of user's module-specific OGCD ability inside next late-weave. + /// Forces execution of the ability in the very next LATE-weave window. LateWeave, - /// - /// The main strategy used for forbidding use of user's module-specific OGCD ability. - /// Example:
- /// - strategy.Option(Track.NoMercy).As<>() == - /// Explanation:
- /// - "strategy.Option(Track.NoMercy).As<>()" is the full function (or local variable if user desires) for calling .
- /// - "" is the chosen option for this specific strategy.
- ///
- /// - Forbiddance of execution for user's module-specific OGCD ability. + /// Forbids execution of the ability. Delay } #endregion @@ -302,10 +116,10 @@ public abstract class AkechiTools(RotationModuleManager manager, A protected int NextGCDPrio; #region Queuing + #region GCD /// - /// The primary helper we use for calling all our GCD abilities onto any actor. - ///
This also handles Ground Target abilities, such as BLM:LeyLines or NIN:Shukuchi
+ /// The primary helper we use for calling all our GCD abilities onto any actor. /// NOTE: For compatibility between Actor? and Enemy? inside one function, use primaryTarget?.Actor as Enemy? definition. ///
/// The user's specified Action ID being checked. @@ -332,8 +146,7 @@ protected void QueueGCD(AID aid, Actor? target, int priority = 2, float delay = #region OGCD /// - /// The primary helper we use for calling all our OGCD abilities onto any actor.
- /// This also handles Ground Target abilities, such as BLM:LeyLines or NIN:Shukuchi + /// The primary helper we use for calling all our OGCD abilities onto any actor. /// NOTE: For compatibility between Actor? and Enemy? inside one function, use primaryTarget?.Actor as Enemy? definition. ///
/// The user's specified Action ID being checked. @@ -390,230 +203,107 @@ protected bool QueueAction(AID aid, Actor? target, float priority, float delay, #endregion #region HP/MP/Shield - /// - /// A quick and easy helper for retrieving the current HP value of the player. - /// Example:
- /// - HP >= 6900 - /// Explanation:
- /// - "HP" represents the current HP value of the player.
- /// - ">= 6900" is the example conditional expression specified by the user.
- ///
- /// - A value representing the Player's current HP + /// Retrieves the current HP value of the player. protected uint HP { get; private set; } - /// - /// A quick and easy helper for retrieving the current MP value of the player. - /// Example:
- /// - MP != 4200 - /// Explanation:
- /// - "MP" represents the current MP value of the player.
- /// - "!= 4200" is the example conditional expression specified by the user.
- ///
- /// - A value representing the Player's current MP + /// Retrieves the current MP value of the player. protected uint MP { get; private set; } - /// - /// A quick and easy helper for retrieving the current Shield value of the player. - /// Example:
- /// - Shield > 0 - /// Explanation:
- /// - "Shield" represents the current Shield value of the player.
- /// - "> 0" is the example conditional expression specified by the user.
- ///
- /// - A value representing the Player's current Shield + /// Retrieves the current Shield value of the player. protected uint Shield { get; private set; } - /// - /// A quick and easy helper for retrieving the Current HP of any specified actor, whether it is the player or any other target user desires. - /// Example:
- /// - TargetCurrentHP(primaryTarget) > 0 - /// Explanation:
- /// - "TargetCurrentHP" represents the current HP value of the specified actor.
- /// - "(primaryTarget)" represents the specified actor being checked.
- /// - "> 0" is the example conditional expression specified by the user.
- ///
- /// Any specified player, ally, or target - /// - A value representing the current HP of user's specified actor + /// Retrieves the current HP value of a specified actor. + /// The specified target actor. protected uint TargetCurrentHP(Actor actor) => actor.HPMP.CurHP; - /// - /// A quick and easy helper for retrieving the Current Shield of any specified actor, whether it is the player or any other target user desires. - /// Example:
- /// - TargetCurrentShield(primaryTarget) > 0 - /// Explanation:
- /// - "TargetCurrentShield" represents the current Shield value of the specified actor.
- /// - "(primaryTarget)" represents the specified actor being checked.
- /// - "> 0" is the example conditional expression specified by the user.
- ///
- /// Any specified player, ally, or target - /// - A value representing the current Shield of user's specified actor. + /// Retrieves the current Shield value of a specified actor. + /// The target actor. protected uint TargetCurrentShield(Actor actor) => actor.HPMP.Shield; - /// - /// A quick and easy helper for checking if specified actor has any current Shield present, whether it is the player or any other target user desires. - /// Example:
- /// - TargetHasShield(primaryTarget) - /// Explanation:
- /// - "TargetHasShield" checks if the specified actor has any current Shield value.
- /// - "(primaryTarget)" represents the specified actor being checked.
- ///
- /// Any specified player, ally, or target + /// Checks if a specified actor has any current active Shield. + /// The target actor. protected bool TargetHasShield(Actor actor) => actor.HPMP.Shield > 0.1f; - /// - /// A quick and easy helper for retrieving the Current HP Percentage of any specified actor, whether it is the player or any other target user desires. - /// Example:
- /// - TargetHPP(primaryTarget) > 50 - /// Explanation:
- /// - "TargetHPP" represents the current HP Percentage value of the specified actor.
- /// - "(primaryTarget)" represents the specified actor being checked.
- /// - "> 50" is the example conditional expression specified by the user.
- ///
- /// Any specified player, ally, or target - /// - A value representing the current HP Percentage (%) of user's specified actor. - public static float TargetHPP(Actor? target = null) + /// Retrieves the player's current HP percentage. + protected float PlayerHPP() => (float)Player.HPMP.CurHP / Player.HPMP.MaxHP * 100; + + /// Retrieves the current HP percentage of a specified actor. + /// The target actor. + protected static float TargetHPP(Actor? actor = null) { - if (target is null || target.IsDead) + if (actor is null || actor.IsDead) return 0f; - if (target is Actor actor) - { - var HPP = (float)actor.HPMP.CurHP / actor.HPMP.MaxHP * 100f; - return Math.Clamp(HPP, 0f, 100f); - } - - return 0f; + var HPP = (float)actor.HPMP.CurHP / actor.HPMP.MaxHP * 100f; + return Math.Clamp(HPP, 0f, 100f); } #endregion #region Actions - /// - /// Checks if specified action is Unlocked based on Level and Job Quest (if required). - /// Example:
- /// - Unlocked(AID.GnashingFang) - /// Explanation:
- /// - "Unlocked" is the function.
- /// - "(AID.GnashingFang)" is the example ability being checked, called using .. - ///
+ /// Checks if specified action is Unlocked based on Level and Job Quest (if required). /// The user's specified Action ID being checked. - /// - TRUE if the specified action is Unlocked.
- /// - FALSE if the specified action is still locked or Job Quest is unfinished.
- protected bool Unlocked(AID aid) => ActionUnlocked(ActionID.MakeSpell(aid)); //Check if the desired ability is unlocked + protected bool Unlocked(AID aid) => ActionUnlocked(ActionID.MakeSpell(aid)); - /// - /// Checks if specified trait is Unlocked based on Level and Job Quest (if required). - /// Example:
- /// - Unlocked(TraitID.EnhancedBloodfest) - /// Explanation:
- /// - "Unlocked" is the function.
- /// - "(TraitID.EnhancedBloodfest)" is the example trait being checked, called using .. - ///
- /// The user's specified Trait ID being checked. - /// - TRUE if the specified action is Unlocked.
- /// - FALSE if the specified action is still locked or Job Quest is unfinished.
+ /// Checks if specified trait is Unlocked based on Level and Job Quest (if required). + /// The Trait ID being checked. protected bool Unlocked(TraitID tid) => TraitUnlocked((uint)(object)tid); - /// - /// Checks if last combo action is what the user is specifying.
- /// NOTE: This does NOT check all actions, only combo actions. - /// Example:
- /// - ComboLastMove == AID.BrutalShell - /// Explanation:
- /// - "ComboLastMove" is the function.
- /// - "AID.BrutalShell" is the example specified combo action being checked, called using .. - ///
- /// - TRUE if the last combo action is what the user is specifying.
- /// - FALSE if otherwise or last action was not a combo action.
+ /// Checks the last combo action is what the user is specifying. protected AID ComboLastMove => (AID)(object)World.Client.ComboState.Action; - /// - /// Checks the time left remaining inside current combo before expiration. - /// NOTE: This does NOT check all actions, only combo actions. - /// + /// Checks the time left remaining inside current combo before expiration. protected float ComboTimer => (float)(object)World.Client.ComboState.Remaining; - /// - /// Retrieves actual cast time of a specified action. - /// Example:
- /// - ActualCastTime(AID.Fire3) > 0 - /// Explanation:
- /// - "ActualCastTime" is the function.
- /// - "AID.Fire3" is the example specified action being checked, called using ..
- /// - "> 0" is the example conditional expression specified by the user.
- ///
+ /// Retrieves actual cast time of a specified action. /// The user's specified Action ID being checked. - /// - A value representing the current real-time Cast Time of user's specified action protected virtual float ActualCastTime(AID aid) => ActionDefinitions.Instance.Spell(aid)!.CastTime; - /// - /// Retrieves effective cast time of a specified action. - /// Example:
- /// - EffectiveCastTime(AID.Fire3) > 0 - /// Explanation:
- /// - "EffectiveCastTime" is the function.
- /// - "AID.Fire3" is the example specified action being checked, called using ..
- /// - "> 0" is the example conditional expression specified by the user.
- ///
+ /// Retrieves effective cast time of a specified action. /// The user's specified Action ID being checked. - /// - A value representing the current effective Cast Time of user's specified action protected virtual float EffectiveCastTime(AID aid) => PlayerHasEffect(ClassShared.SID.Swiftcast, 10) ? 0 : ActualCastTime(aid) * SpSGCDLength / 2.5f; - /// Retrieves player's GCD length based on Skill-Speed. - /// NOTE: This function is only recommended for jobs with Skill-Speed. Spell-Speed users are unaffected by this function. - /// - A value representing the player's current GCD Length + /// Retrieves player's GCD length based on Skill-Speed. protected float SkSGCDLength => ActionSpeed.GCDRounded(World.Client.PlayerStats.SkillSpeed, World.Client.PlayerStats.Haste, Player.Level); /// Retrieves player's current Skill-Speed stat. - /// - A value representing the player's current Skill-Speed protected float SkS => ActionSpeed.Round(World.Client.PlayerStats.SkillSpeed); - /// Retrieves player's GCD length based on Spell-Speed. - /// NOTE: This function is only recommended for jobs with Spell-Speed. Skill-Speed users are unaffected by this function. - /// - A value representing the player's current GCD Length + /// Retrieves player's GCD length based on Spell-Speed. protected float SpSGCDLength => ActionSpeed.GCDRounded(World.Client.PlayerStats.SpellSpeed, World.Client.PlayerStats.Haste, Player.Level); /// Retrieves player's current Spell-Speed stat. - /// - A value representing the player's current Spell-Speed protected float SpS => ActionSpeed.Round(World.Client.PlayerStats.SpellSpeed); /// Checks if we can fit in a skill-speed based GCD. - /// - /// How many extra GCDs the user can fit in. - /// - TRUE if we can fit in a skill-speed based GCD - FALSE if otherwise + /// The duration to check against. + /// How many extra GCDs the user can fit in. protected bool CanFitSkSGCD(float duration, int extraGCDs = 0) => GCD + SkSGCDLength * extraGCDs < duration; /// Checks if we can fit in a spell-speed based GCD. - /// - /// How many extra GCDs the user can fit in. - /// - TRUE if we can fit in a spell-speed based GCD - FALSE if otherwise + /// The duration to check against. + /// How many extra GCDs the user can fit in. protected bool CanFitSpSGCD(float duration, int extraGCDs = 0) => GCD + SpSGCDLength * extraGCDs < duration; - /// Checks if player is available to weave in any abilities. - /// NOTE: This function is only recommended for jobs with Skill-Speed. Spell-Speed users are unaffected by this. - /// The cooldown time of the action specified. - /// The animation lock time of the action specified. - /// How many extra GCDs the user can fit in. - /// How much extra delay the user can add in, in seconds. - /// - TRUE if we can weave in a skill-speed based GCD - FALSE if otherwise + /// Checks if player is available to weave in any abilities. + /// The cooldown time of the action specified. + /// The animation lock time of the action specified. + /// How many extra GCDs the user can fit in. + /// How much extra delay the user can add in, in seconds. protected bool CanWeave(float cooldown, float actionLock, int extraGCDs = 0, float extraFixedDelay = 0) => MathF.Max(cooldown, World.Client.AnimationLock) + actionLock + AnimationLockDelay <= GCD + SkSGCDLength * extraGCDs + extraFixedDelay; - /// Checks if player is available to weave in any spells. - /// NOTE: This function is only recommended for jobs with Spell-Speed. Skill-Speed users are unaffected by this. - /// The cooldown time of the action specified. - /// The animation lock time of the action specified. - /// How many extra GCDs the user can fit in. - /// How much extra delay the user can add in, in seconds. - /// - TRUE if we can weave in a spell-speed based GCD - FALSE if otherwise + /// Checks if player is available to weave in any spells. + /// The cooldown time of the action specified. + /// The animation lock time of the action specified. + /// How many extra GCDs the user can fit in. + /// How much extra delay the user can add in, in seconds. protected bool CanSpellWeave(float cooldown, float actionLock, int extraGCDs = 0, float extraFixedDelay = 0) => MathF.Max(cooldown, World.Client.AnimationLock) + actionLock + AnimationLockDelay <= GCD + SpSGCDLength * extraGCDs + extraFixedDelay; - /// Checks if player is available to weave in any abilities. - /// NOTE: This function is only recommended for jobs with Skill-Speed. Spell-Speed users are unaffected by this. - /// The user's specified Action ID being checked. - /// How many extra GCDs the user can fit in. - /// How much extra delay the user can add in, in seconds. - /// - TRUE if we can weave in any abilities - FALSE if otherwise + /// Checks if player is available to weave in any abilities. + /// The Action ID being checked. + /// How many extra GCDs the user can fit in. + /// How much extra delay the user can add in, in seconds. protected bool CanWeave(AID aid, int extraGCDs = 0, float extraFixedDelay = 0) { if (!Unlocked(aid)) @@ -625,127 +315,103 @@ protected bool CanWeave(AID aid, int extraGCDs = 0, float extraFixedDelay = 0) : SpS > 100 && CanWeave(ChargeCD(aid), res.InstantAnimLock, extraGCDs, extraFixedDelay); } - /// Checks if user is in pre-pull stage; useful for First GCD openings. - /// - TRUE if user is in pre-pull stage or fully not in combat- FALSE if otherwise + /// Checks if user is in pre-pull stage. protected bool IsFirstGCD() => !Player.InCombat || (World.CurrentTime - Manager.CombatStart).TotalSeconds < 0.1f; - /// Checks if user can Weave in any abilities.NOTE: This function is pretty sub-optimal, but gets the job done. CanWeave() is much more intricate if user really wants it. + /// Checks if user can Weave in any abilities. protected bool CanWeaveIn => GCD is <= 2.49f and >= 0.01f; - /// Checks if user can Early Weave in any abilities.NOTE: This function is pretty sub-optimal, but gets the job done. CanWeave() is much more intricate if user really wants it. + /// Checks if user can Early Weave in any abilities. protected bool CanEarlyWeaveIn => GCD is <= 2.49f and >= 1.26f; - /// Checks if user can Late Weave in any abilities.NOTE: This function is pretty sub-optimal, but gets the job done. CanWeave() is much more intricate if user really wants it. + /// Checks if user can Late Weave in any abilities. protected bool CanLateWeaveIn => GCD is <= 1.25f and >= 0.01f; - /// Checks if user can Quarter Weave in any abilities.NOTE: This function is pretty sub-optimal, but gets the job done. CanWeave() is much more intricate if user really wants it. + /// Checks if user can Quarter Weave in any abilities. protected bool CanQuarterWeaveIn => GCD is < 0.9f and >= 0.01f; #endregion #region Cooldown - /// Retrieves the total cooldown time left on the specified action. + /// Retrieves the total cooldown time left on the specified action. /// The user's specified Action ID being checked. - /// - A value representing the current cooldown on user's specified Action ID protected float TotalCD(AID aid) => World.Client.Cooldowns[ActionDefinitions.Instance.Spell(aid)!.MainCooldownGroup].Remaining; - /// Returns the charge cooldown time left on the specified action. + /// Returns the charge cooldown time left on the specified action. /// The user's specified Action ID being checked. - /// - A value representing the current charge cooldown on user's specified Action ID protected float ChargeCD(AID aid) => Unlocked(aid) ? ActionDefinitions.Instance.Spell(aid)!.ReadyIn(World.Client.Cooldowns, World.Client.DutyActions) : float.MaxValue; - /// Checks if action is ready to be used based on if it's Unlocked and its charge cooldown timer. + /// Checks if action is ready to be used based on if it's Unlocked and its total cooldown timer. /// The user's specified Action ID being checked. - /// - TRUE if the specified Action is Unlocked and off cooldown. - FALSE if locked or still on cooldown - protected bool ActionReady(AID aid) => Unlocked(aid) && ChargeCD(aid) < 0.6f; + protected bool ActionReady(AID aid) => Unlocked(aid) && !IsOnCooldown(aid); - /// Checks if action has any charges remaining. + /// Checks if action has any charges remaining. /// The user's specified Action ID being checked. - /// - TRUE if the specified Action has any charges available - FALSE if locked or still on cooldown protected bool HasCharges(AID aid) => ChargeCD(aid) < 0.6f; - /// Checks if action is on cooldown based on its total cooldown timer. + /// Checks if action is on cooldown based on its total cooldown timer. /// The user's specified Action ID being checked. - /// - TRUE if the specified Action is still on cooldown - FALSE if off cooldown protected bool IsOnCooldown(AID aid) => TotalCD(aid) > 0.6f; - /// Checks if action is off cooldown based on its total cooldown timer. + /// Checks if action is off cooldown based on its total cooldown timer. /// The user's specified Action ID being checked. - /// - TRUE if the specified Action is off cooldown - FALSE if still on cooldown protected bool IsOffCooldown(AID aid) => !IsOnCooldown(aid); - /// Checks if action is off cooldown based on its charge cooldown timer. + /// Checks if action is off cooldown based on its charge cooldown timer. /// The user's specified Action ID being checked. - /// - TRUE if the specified Action is off cooldown - FALSE if still on cooldown protected bool OnCooldown(AID aid) => MaxChargesIn(aid) > 0; - /// Checks if last action used is what the user is specifying and within however long. + /// Checks if last action used is what the user is specifying. /// The user's specified Action ID being checked. - /// - TRUE if the specified Action was just used - FALSE if was not just used protected bool LastActionUsed(AID aid) => Manager.LastCast.Data?.IsSpell(aid) == true; - /// Retrieves time remaining until specified action is at max charges. + /// Retrieves time remaining until specified action is at max charges. /// The user's specified Action ID being checked. protected float MaxChargesIn(AID aid) => Unlocked(aid) ? ActionDefinitions.Instance.Spell(aid)!.ChargeCapIn(World.Client.Cooldowns, World.Client.DutyActions, Player.Level) : float.MaxValue; #endregion #region Status - /// Retrieves the amount of specified status effect's stacks remaining on any target. - /// NOTE: The effect MUST be owned by the Player. - /// Example Given: "StacksRemaining(Player, SID.Requiescat, 30) > 0" + /// Retrieves the amount of specified status effect's stacks remaining on any target. + /// NOTE: The effect MUST be owned by the Player. /// The user's specified Target being checked. /// The user's specified Status ID being checked. /// The Total Effect Duration of specified Status ID being checked. - /// - A value indicating how many stacks exist protected int StacksRemaining(Actor? target, SID sid, float duration = 1000f) where SID : Enum => StatusDetails(target, sid, Player.InstanceID, duration).Stacks; - /// Retrieves the amount of specified status effect's time left remaining on any target. - /// NOTE: The effect MUST be owned by the Player. - /// Example Given: "StatusRemaining(Player, SID.Requiescat, 30) > 0f" + /// Retrieves the amount of specified status effect's time left remaining on any target. + /// NOTE: The effect MUST be owned by the Player. /// The user's specified Target being checked. /// The user's specified Status ID being checked. /// The Total Effect Duration of specified Status ID being checked. - /// - A value indicating how much time left on existing effect - protected float StatusRemaining(Actor? target, SID sid, float duration) where SID : Enum => StatusDetails(target, sid, Player.InstanceID, duration).Left; + protected float StatusRemaining(Actor? target, SID sid, float duration = 1000f) where SID : Enum => StatusDetails(target, sid, Player.InstanceID, duration).Left; - /// Checks if a specific status effect on the player exists. - /// NOTE: The effect MUST be owned by the Player. - /// Example Given: "PlayerHasEffect(SID.NoMercy, 20)" + /// Checks if a specific status effect on the Player exists. + /// NOTE: The effect MUST be owned by the Player. /// The user's specified Status ID being checked. /// The Total Effect Duration of specified Status ID being checked. - /// - A value indicating if the effect exists protected bool PlayerHasEffect(SID sid, float duration = 1000f) where SID : Enum => StatusRemaining(Player, sid, duration) > 0.1f; - /// Checks if a specific status effect on the player exists. - /// NOTE: The effect can be owned by anyone; Player, Party, Alliance, NPCs or even enemies - /// Example Given: "PlayerHasEffectAny(SID.Troubadour)" + /// Checks if a specific status effect on the Player exists. + /// NOTE: The effect can be owned by anyone; Player, Party, Alliance, NPCs, or even enemies. /// The user's specified Status ID being checked. - /// The Total Effect Duration of specified Status ID being checked. - /// - A value indicating if the effect exists protected bool PlayerHasAnyEffect(SID sid) where SID : Enum => Player.FindStatus(sid) != null; - /// Checks if a specific status effect on any specified target exists. - /// NOTE: The effect MUST be owned by the Player. - /// Example Given: "TargetHasEffect(primaryTarget, SID.SonicBreak, 30)" + /// Checks if a specific status effect on any specified Target exists. + /// NOTE: The effect MUST be owned by the Player. /// The user's specified Target being checked. /// The user's specified Status ID being checked. /// The Total Effect Duration of specified Status ID being checked. - /// - A value indicating if the effect exists protected bool TargetHasEffect(Actor? target, SID sid, float duration = 1000f) where SID : Enum => StatusRemaining(target, sid, duration) > 0.1f; - /// Checks if a specific status effect on any specified target exists. - /// NOTE: The effect can be owned by anyone; Player, Party, Alliance, NPCs or even enemies - /// Example Given: "TargetHasAnyEffect(primaryTarget, SID.MeditativeBrotherhood)" + /// Checks if a specific status effect on any specified Target exists. + /// NOTE: The effect can be owned by anyone; Player, Party, Alliance, NPCs, or even enemies. /// The user's specified Target being checked. /// The user's specified Status ID being checked. - /// - A value indicating if the effect exists protected bool TargetHasAnyEffect(Actor? target, SID sid) where SID : Enum => target?.FindStatus(sid) != null; - /// Checks if Player has any stacks of specific status effect. - /// NOTE: The effect MUST be owned by the Player. - /// Example Given: "PlayerHasStacks(SID.Requiescat)" + /// Checks if Player has any stacks of a specific status effect. + /// NOTE: The effect MUST be owned by the Player. /// The user's specified Status ID being checked. - /// - A value indicating if the effect exists protected bool PlayerHasStacks(SID sid) where SID : Enum => StacksRemaining(Player, sid) > 0; #endregion @@ -753,162 +419,179 @@ protected bool CanWeave(AID aid, int extraGCDs = 0, float extraFixedDelay = 0) #region Targeting #region Position Checks - /// - /// Checks precise positioning between player target and any other targets. - /// + + #region Core + /// Checks precise positioning between player target and any other targets. protected delegate bool PositionCheck(Actor playerTarget, Actor targetToTest); - /// - /// Calculates the priority of a target based on the total number of targets and the primary target itself. - /// It is generic, so it can return different types based on the implementation. - /// + + /// Calculates the priority of a target based on the total number of targets and the primary target itself.
It is generic, so it can return different types based on the implementation.
protected delegate P PriorityFunc

(int totalTargets, Actor primaryTarget); - ///

- /// Position checker for determining the best target for an ability that deals Splash damage. - /// + #endregion + + #region Splash + /// Position checker for determining the best target for an ability that deals Splash damage. protected PositionCheck IsSplashTarget => (primary, other) => Hints.TargetInAOECircle(other, primary.Position, 5); - /// - /// Position checker for determining the best target for an ability that deals damage in a Cone . - /// - protected PositionCheck IsConeTarget => (primary, other) => Hints.TargetInAOECone(other, Player.Position, 8, Player.DirectionTo(primary), 45.Degrees()); - /// - /// Position checker for determining the best target for an ability that deals damage in a Line within Ten (10) yalms. - /// - protected PositionCheck Is10yRectTarget => (primary, other) => Hints.TargetInAOERect(other, Player.Position, Player.DirectionTo(primary), 10, 2); - /// - /// Position checker for determining the best target for an ability that deals damage in a Line within Fifteen (15) yalms. - /// - protected PositionCheck Is15yRectTarget => (primary, other) => Hints.TargetInAOERect(other, Player.Position, Player.DirectionTo(primary), 15, 2); - /// - /// Position checker for determining the best target for an ability that deals damage in a Line within Twenty-five (25) yalms - /// - protected PositionCheck Is25yRectTarget => (primary, other) => Hints.TargetInAOERect(other, Player.Position, Player.DirectionTo(primary), 25, 2); #endregion - /// - /// Checks if target is within Zero (0) yalms in distance, or if Player is inside hitbox. - /// - /// The user's specified Target being checked. - /// - protected bool In0y(Actor? target) => Player.DistanceToHitbox(target) <= 0.00f; + #region Cones + //some use-cases for these are mainly for BLU modules, since the ranges for their abilities are all over the place. (e.g. 4y & 16y specifically) - /// - /// Checks if target is within Three (3) yalms in distance. - /// - /// The user's specified Target being checked. - /// - protected bool In3y(Actor? target) => Player.DistanceToHitbox(target) <= 2.99f; + /// Creates a Position Check for Cone AOE attacks with the given range. + private PositionCheck ConeTargetCheck(float range) => (primary, other) => + { + var playerDir = Player.DirectionTo(primary); + return Hints.TargetInAOECone(other, Player.Position, range, playerDir, 45.Degrees()); + }; - /// - /// Checks if target is within Five (5) yalms in distance. - /// - /// The user's specified Target being checked. - /// - protected bool In5y(Actor? target) => Player.DistanceToHitbox(target) <= 4.99f; + /// Checks if the target is within a 4-yalm Cone range. + protected PositionCheck Is4yConeTarget => ConeTargetCheck(4); - /// - /// Checks if target is within Ten (10) yalms in distance. - /// - /// The user's specified Target being checked. - /// - protected bool In10y(Actor? target) => Player.DistanceToHitbox(target) <= 9.99f; + /// Checks if the target is within a 6-yalm Cone range. + protected PositionCheck Is6yConeTarget => ConeTargetCheck(6); - /// - /// Checks if target is within Fifteen (15) yalms in distance. - /// - /// The user's specified Target being checked. - /// - protected bool In15y(Actor? target) => Player.DistanceToHitbox(target) <= 14.99f; + /// Checks if the target is within an 8-yalm Cone range. + protected PositionCheck Is8yConeTarget => ConeTargetCheck(8); - /// - /// Checks if target is within Twenty (20) yalms in distance. - /// - /// The user's specified Target being checked. - /// - protected bool In20y(Actor? target) => Player.DistanceToHitbox(target) <= 19.99f; + /// Checks if the target is within a 10-yalm Cone range. + protected PositionCheck Is10yConeTarget => ConeTargetCheck(10); - /// - /// Checks if target is within Twenty-five (25) yalms in distance. - /// - /// The user's specified Target being checked. - /// - protected bool In25y(Actor? target) => Player.DistanceToHitbox(target) <= 24.99f; + /// Checks if the target is within a 12-yalm Cone range. + protected PositionCheck Is12yConeTarget => ConeTargetCheck(12); - /// - /// A simpler smart-targeting helper for picking a specific target over your current target. - /// Very useful for intricate planning of ability targeting in specific situations. - /// - /// The user's picked strategy's option Track, retrieved from module's enums and definitions. (e.g. strategy.Option(Track.NoMercy)) - /// - protected Actor? TargetChoice(StrategyValues.OptionRef track) => ResolveTargetOverride(track.Value); //Resolves the target choice based on the strategy - - /// Targeting function for indicating when or not AOE Circle abilities should be used based on targets nearby. - /// The range of the AOE Circle ability, or radius from center of Player; this should be adjusted accordingly to user's module specific to job's abilities. - /// - A tuple with the following booleans: - /// -- OnTwoOrMore: A boolean indicating if there are two (2) or more targets inside Player's AOE Circle. - /// -- OnThreeOrMore: A boolean indicating if there are three (3) or more targets inside Player's AOE Circle. - /// -- OnFourOrMore: A boolean indicating if there are four (4) or more targets inside Player's AOE Circle. - /// -- OnFiveOrMore: A boolean indicating if there are five (5) or more targets inside Player's AOE Circle. - protected (bool OnTwoOrMore, bool OnThreeOrMore, bool OnFourOrMore, bool OnFiveOrMore) ShouldUseAOECircle(float range) + /// Checks if the target is within a 15-yalm Cone range. + protected PositionCheck Is15yConeTarget => ConeTargetCheck(15); + + /// Checks if the target is within a 16-yalm Cone range. + protected PositionCheck Is16yConeTarget => ConeTargetCheck(16); + #endregion + + #region Lines (AOE Rectangles) + /// Creates a Position Check for Line AOE (AOE Rectangle) attacks with the given range. + private PositionCheck LineTargetCheck(float range) => (primary, other) => { - var OnTwoOrMore = Hints.NumPriorityTargetsInAOECircle(Player.Position, range) > 1; - var OnThreeOrMore = Hints.NumPriorityTargetsInAOECircle(Player.Position, range) > 2; - var OnFourOrMore = Hints.NumPriorityTargetsInAOECircle(Player.Position, range) > 3; - var OnFiveOrMore = Hints.NumPriorityTargetsInAOECircle(Player.Position, range) > 4; + var playerDir = Player.DirectionTo(primary); // Cache calculation + return Hints.TargetInAOERect(other, Player.Position, playerDir, range, 2); + }; - return (OnTwoOrMore, OnThreeOrMore, OnFourOrMore, OnFiveOrMore); - } + /// Checks if the target is within a 10-yalm AOE Rect range. + protected PositionCheck Is10yRectTarget => LineTargetCheck(10); - /// - /// This function attempts to pick a suitable primary target automatically, even if a target is not already picked. - /// - /// The user's picked Strategy - /// The user's current specified Target. - /// - protected void GetPrimaryTarget(StrategyValues strategy, ref Enemy? primaryTarget, float range) + /// Checks if the target is within a 15-yalm AOE Rect range. + protected PositionCheck Is15yRectTarget => LineTargetCheck(15); + + /// Checks if the target is within a 20-yalm AOE Rect range. + protected PositionCheck Is20yRectTarget => LineTargetCheck(20); + + /// Checks if the target is within a 25-yalm AOE Rect range. + protected PositionCheck Is25yRectTarget => LineTargetCheck(25); + #endregion + + #endregion + + #region Range Checks + /// Checks if target is within the specified distance in yalms. + /// The user's specified Target being checked. + /// The maximum distance threshold. + protected bool InRange(Actor? target, float maxDistance) => Player.DistanceToHitbox(target) <= maxDistance - 0.01f; + + /// Checks if the target is within 0-yalm range. + protected bool In0y(Actor? target) => InRange(target, 0.01f); + + /// Checks if the target is within 3-yalm range. + protected bool In3y(Actor? target) => InRange(target, 3.00f); + + /// Checks if the target is within 5-yalm range. + protected bool In5y(Actor? target) => InRange(target, 5.00f); + + /// Checks if the target is within 10-yalm range. + protected bool In10y(Actor? target) => InRange(target, 10.00f); + + /// Checks if the target is within 15-yalm range. + protected bool In15y(Actor? target) => InRange(target, 15.00f); + + /// Checks if the target is within 20-yalm range. + protected bool In20y(Actor? target) => InRange(target, 20.00f); + + /// Checks if the target is within 25-yalm range. + protected bool In25y(Actor? target) => InRange(target, 25.00f); + #endregion + + #region Smart-Targeting + /// A simpler smart-targeting helper for picking a specific target over your current target.
Very useful for intricate planning of ability targeting in specific situations.
+ /// The user's selected strategy's option Track, retrieved from module's enums and definitions. (e.g. strategy.Option(Track.NoMercy)) + /// + protected Actor? TargetChoice(StrategyValues.OptionRef track) => ResolveTargetOverride(track.Value); + + /// Attempts to select a suitable primary PvE target automatically. + /// NOTE: This function is solely used for auto-targeting enemies without having a target selected or for AI usage. Please use appropriately. + /// The user's selected strategy. + /// The user's current target. + /// The max range to consider a new target. + protected void GetPvETarget(StrategyValues strategy, ref Enemy? primaryTarget, float range) { - var AOEStrat = strategy.Option(SharedTrack.AOE).As(); - if (AOEStrat is AOEStrategy.Automatic) + if (primaryTarget?.Actor == null || Player.DistanceToHitbox(primaryTarget.Actor) > range) { - if (Player.DistanceToHitbox(primaryTarget?.Actor) > range) + var AOEStrat = strategy.Option(SharedTrack.AOE).As(); + if (AOEStrat is AOEStrategy.AutoFinish or AOEStrategy.AutoBreak) { - var newTarget = Hints.PriorityTargets.FirstOrDefault(x => Player.DistanceToHitbox(x.Actor) <= range); - if (newTarget != null) - primaryTarget = newTarget; + primaryTarget = Hints.PriorityTargets.FirstOrDefault(x => Player.DistanceToHitbox(x.Actor) <= range); } } } - /// - /// This function attempts to pick the best target automatically. - /// - /// The user's picked Strategy - /// The user's current picked Target. - /// - /// - /// - protected (Enemy? Best, int Targets) GetBestTarget(Enemy? primaryTarget, float range, PositionCheck isInAOE) => GetTarget(primaryTarget, range, isInAOE, (numTargets, _) => numTargets, a => a); + /// Attempts to select the most suitable PvP target automatically, prioritizing the target with the lowest HP percentage within range. + /// NOTE: This function is solely used for finding the best PvP target without having to manually scan and click on other targets. Please use appropriately. + /// The user's current target. + /// The max range to consider a new target. + protected void GetPvPTarget(ref Enemy? primaryTarget, float range) + { + if (primaryTarget?.Actor == null || Player.DistanceToHitbox(primaryTarget.Actor) > range) + { + primaryTarget = Hints.PriorityTargets + .Where(x => Player.DistanceToHitbox(x.Actor) <= range && x.Actor.FindStatus(ClassShared.SID.Guard) == null) + .OrderBy(x => (float)x.Actor.HPMP.CurHP / x.Actor.HPMP.MaxHP) + .FirstOrDefault(); + } + } + #endregion - /// - /// This function picks the target based on HP, modified by how many targets are in the AOE. - /// - /// - /// The user's current picked Target. - /// - /// - /// - protected (Enemy? Best, int Targets) GetTargetByHP(Enemy? primaryTarget, float range, PositionCheck isInAOE) => GetTarget(primaryTarget, range, isInAOE, (numTargets, enemy) => (numTargets, numTargets > 2 ? enemy.HPMP.CurHP : 0), args => args.numTargets); + /// Targeting function for indicating when AOE Circle abilities should be used based on nearby targets. + /// The radius of the AOE Circle ability from the Player. + /// + /// A tuple with boolean values indicating whether the AOE Circle should be used based on the number of targets nearby:
+ /// - OnAny: At least 1 target is inside.
+ /// - OnTwoOrMore: At least 2 targets are inside.
+ /// - OnThreeOrMore: At least 3 targets are inside.
+ /// - OnFourOrMore: At least 4 targets are inside.
+ /// - OnFiveOrMore: At least 5 targets are inside. + ///
+ protected (bool OnAny, bool OnTwoOrMore, bool OnThreeOrMore, bool OnFourOrMore, bool OnFiveOrMore) ShouldUseAOECircle(float range) + { + var numTargets = Hints.NumPriorityTargetsInAOECircle(Player.Position, range); + return (numTargets > 0, numTargets > 1, numTargets > 2, numTargets > 3, numTargets > 4); + } - /// - /// Main function for picking a target, generalized for any prioritization and simplification logic. - /// + /// This function attempts to pick the best target automatically. + /// The user's current selected Target. + /// The range within which to evaluate potential targets. + /// A flag indicating if the target is within the Area of Effect (AOE). + protected (Enemy? Best, int Targets) GetBestTarget(Enemy? primaryTarget, float range, PositionCheck isInAOE) + => GetTarget(primaryTarget, range, isInAOE, (numTargets, _) => numTargets, a => a); + + /// This function picks the target based on HP, modified by how many targets are in the AOE. + /// The user's current selected Target. + /// The range within which to evaluate potential targets. + /// A flag indicating if the target is within the Area of Effect (AOE). + protected (Enemy? Best, int Targets) GetBestHPTarget(Enemy? primaryTarget, float range, PositionCheck isInAOE) + => GetTarget(primaryTarget, range, isInAOE, (numTargets, enemy) => (numTargets, numTargets > 2 ? enemy.HPMP.CurHP : 0), args => args.numTargets); + + /// Main function for picking a target, generalized for any prioritization and simplification logic. /// - /// - /// The user's current picked Target. - /// - /// - /// - /// - /// + /// The user's current selected Target. + /// The range within which to evaluate potential targets. + /// A flag indicating if the target is within the Area of Effect (AOE). + /// A flag indicating whether prioritization of certain targets should occur. + /// A flag indicating whether the simplification of target selection should apply. protected (Enemy? Best, int Priority) GetTarget

(Enemy? primaryTarget, float range, PositionCheck isInAOE, PriorityFunc

prioritize, Func simplify) where P : struct, IComparable { P targetPrio(Actor potentialTarget) @@ -922,18 +605,13 @@ P targetPrio(Actor potentialTarget) return (newnewprio > 0 ? Hints.FindEnemy(newtarget) : null, newnewprio); } - ///

- /// Identify an appropriate target for applying DoT effect. This has no impact if any auto-targeting is disabled. - /// - /// - /// - /// - /// - /// - /// - protected (Enemy? Target, P Timer) GetDOTTarget

(Enemy? initial, Func getTimer, int maxAllowedTargets) where P : struct, IComparable + ///

Identify an appropriate target for applying DoT effect. This has no impact if any auto-targeting is disabled. + /// The type representing the timer value, which must be a struct and implement . + /// The initial target to consider for applying the DoT effect. + /// A function that retrieves the timer value associated with a given actor. + /// The maximum number of valid targets to evaluate. + protected (Enemy? Best, P Timer) GetDOTTarget

(Enemy? initial, Func getTimer, int maxAllowedTargets) where P : struct, IComparable { - // Check if the initial target is null or if the maxAllowedTargets is 0, in which case no valid target can be selected if (initial == null || maxAllowedTargets <= 0) { return (null, getTimer(null)); @@ -946,18 +624,14 @@ P targetPrio(Actor potentialTarget) foreach (var dotTarget in Hints.PriorityTargets) { - // Skip targets that forbid DOTs if (dotTarget.ForbidDOTs) continue; - // If we exceed the max number of allowed targets, stop and return the current best if (++numTargets > maxAllowedTargets) return (newTarget, newTimer); - // Get the timer for the current target var thisTimer = getTimer(dotTarget.Actor); - // Update the new target and timer if the current one is better (has a smaller timer value) if (thisTimer.CompareTo(newTimer) < 0) { newTarget = dotTarget; @@ -969,49 +643,28 @@ P targetPrio(Actor potentialTarget) } #endregion - #region Actors - ///

- /// Player's "actual" target; guaranteed to be an enemy. - /// - protected Enemy? PlayerTarget { get; private set; } - - //TODO: implement this soon - protected Actor? AnyTarget { get; private set; } - #endregion - #region Positionals - /// - /// Retrieves the current positional of the target based on target's position and rotation. - /// + /// Retrieves the current positional of the target based on target's position and rotation. /// The user's specified Target being checked. - /// - protected Positional GetCurrentPositional(Actor target) => (Player.Position - target.Position).Normalized().Dot(target.Rotation.ToDirection()) switch //Check current positional based on target + protected Positional GetCurrentPositional(Actor target) => (Player.Position - target.Position).Normalized().Dot(target.Rotation.ToDirection()) switch { - < -0.7071068f => Positional.Rear, //value for Rear positional - < 0.7071068f => Positional.Flank, //value for Flank positional - _ => Positional.Front //default to Front positional if not on Rear or Flank + < -0.7071068f => Positional.Rear, + < 0.7071068f => Positional.Flank, + _ => Positional.Front }; - /// - /// Checks if player is on specified target's Rear Positional. - /// + /// Checks if player is on specified target's Rear Positional. /// The user's specified Target being checked. - /// protected bool IsOnRear(Actor target) => GetCurrentPositional(target) == Positional.Rear; - /// - /// Checks if player is on specified target's Flank Positional. - /// + /// Checks if player is on specified target's Flank Positional. /// The user's specified Target being checked. - /// protected bool IsOnFlank(Actor target) => GetCurrentPositional(target) == Positional.Flank; #endregion #region AI - /// - /// Establishes a goal-zone for a single target within a specified range.
- /// Primarily utilized by caster-DPS jobs that lack a dedicated maximize-AOE function. - ///
+ /// Establishes a goal-zone for a single target within a specified range.
+ /// Primarily utilized by caster-DPS jobs that lack a dedicated maximize-AOE function.
/// The range within which the goal zone is applied. protected void GoalZoneSingle(float range) { @@ -1019,9 +672,7 @@ protected void GoalZoneSingle(float range) Hints.GoalZones.Add(Hints.GoalSingleTarget(PlayerTarget.Actor, range)); } - /// - /// Defines a goal-zone using a combined strategy, factoring in AOE considerations. - /// + /// Defines a goal-zone using a combined strategy, factoring in AOE considerations. /// The strategy values that influence the goal zone logic. /// The base range for the goal zone. /// A function determining the area of effect. @@ -1068,7 +719,6 @@ protected void AnyGoalZoneCombined(float range, Func fAoe, AID firs #region Misc /// Estimates the delay caused by animation lock. - /// - A value representing the current Animation Lock Delay protected float AnimationLockDelay { get; private set; } /// Estimates the time remaining until the next Down-time phase. @@ -1082,37 +732,28 @@ protected void AnyGoalZoneCombined(float range, Func fAoe, AID firs /// Checks if player is currently moving. protected bool IsMoving { get; private set; } + + /// Player's actual target; guaranteed to be an enemy. + protected Enemy? PlayerTarget { get; private set; } #endregion #region Shared Abilities - /// - /// Checks if player is able to execute the melee-DPS shared ability: True North - /// + /// Checks if player is able to execute the melee-DPS shared ability: True North protected bool CanTrueNorth { get; private set; } - /// - /// Checks if player is under the effect of the melee-DPS shared ability: True North - /// + /// Checks if player is under the effect of the melee-DPS shared ability: True North protected bool HasTrueNorth { get; private set; } - /// - /// Checks if player is able to execute the caster-DPS shared ability: Swiftcast - /// + /// Checks if player is able to execute the caster-DPS shared ability: Swiftcast protected bool CanSwiftcast { get; private set; } - /// - /// Checks if player is under the effect of the caster-DPS shared ability: Swiftcast - /// + /// Checks if player is under the effect of the caster-DPS shared ability: Swiftcast protected bool HasSwiftcast { get; private set; } - /// - /// Checks if player is able to execute the ranged-DPS shared ability: Peloton - /// + /// Checks if player is able to execute the ranged-DPS shared ability: Peloton protected bool CanPeloton { get; private set; } - /// - /// Checks if player is under the effect of the ranged-DPS shared ability: Peloton - /// + /// Checks if player is under the effect of the ranged-DPS shared ability: Peloton protected bool HasPeloton { get; private set; } #endregion @@ -1140,35 +781,36 @@ public sealed override void Execute(StrategyValues strategy, ref Actor? primaryT Execution(strategy, PlayerTarget); } - /// - /// The core function responsible for orchestrating the execution of all abilities and strategies.
- ///
+ /// The core function responsible for orchestrating the execution of all abilities and strategies.
/// The user's specified Strategy. /// The user's specified Enemy. - /// - Primary execution of user's rotation module. public abstract void Execution(StrategyValues strategy, Enemy? primaryTarget); } static class ModuleExtensions { #region Shared Definitions - /// Defines our shared AOE (rotation) and Hold strategies. + /// Defines our shared AOE (rotation) strategies. /// The definitions of our base module's strategies. - /// - Options for shared custom strategies to be used via AutoRotation or Cooldown Planner - public static RotationModuleDefinition DefineShared(this RotationModuleDefinition res) + public static RotationModuleDefinition.ConfigRef DefineAOE(this RotationModuleDefinition res) { - res.Define(SharedTrack.AOE).As("AOE", uiPriority: 300) - .AddOption(AOEStrategy.Automatic, "Auto", "Automatically execute optimal rotation based on targets", supportedTargets: ActionTargets.Hostile) + return res.Define(SharedTrack.AOE).As("AOE", uiPriority: 300) + .AddOption(AOEStrategy.AutoFinish, "Auto (Finish combo)", "Automatically execute optimal rotation based on targets; finishes combo if possible", supportedTargets: ActionTargets.Hostile) + .AddOption(AOEStrategy.AutoBreak, "Auto (Break combo)", "Automatically execute optimal rotation based on targets; breaks combo if necessary", supportedTargets: ActionTargets.Hostile) .AddOption(AOEStrategy.ForceST, "ForceST", "Force-execute Single Target", supportedTargets: ActionTargets.Hostile) - .AddOption(AOEStrategy.ForceAOE, "ForceAOE", "Force-execute AOE rotation", supportedTargets: ActionTargets.Hostile); + .AddOption(AOEStrategy.ForceAOE, "ForceAOE", "Force-execute AOE rotation", supportedTargets: ActionTargets.Hostile | ActionTargets.Self); + } - res.Define(SharedTrack.Hold).As("Hold", uiPriority: 290) + /// Defines our shared Hold strategies. + /// The definitions of our base module's strategies. + public static RotationModuleDefinition.ConfigRef DefineHold(this RotationModuleDefinition res) + { + return res.Define(SharedTrack.Hold).As("Hold", uiPriority: 290) .AddOption(HoldStrategy.DontHold, "DontHold", "Allow use of all cooldowns, buffs, or gauge abilities") .AddOption(HoldStrategy.HoldCooldowns, "Hold", "Forbid use of all cooldowns only") .AddOption(HoldStrategy.HoldGauge, "HoldGauge", "Forbid use of all gauge abilities only") .AddOption(HoldStrategy.HoldBuffs, "HoldBuffs", "Forbid use of all raidbuffs or buff-related abilities only") .AddOption(HoldStrategy.HoldEverything, "HoldEverything", "Forbid use of all cooldowns, buffs, and gauge abilities"); - return res; } /// A quick and easy helper for shortcutting how we define our GCD abilities. @@ -1181,7 +823,6 @@ public static RotationModuleDefinition DefineShared(this RotationModuleDefinitio /// The Targets Supported for the ability that the user is specifying. /// The Minimum Level required for the ability that the user is specifying. /// The Maximum Level required for the ability that the user is specifying. - /// - Basic GCD options for any specified ability to be used via AutoRotation or Cooldown Planner public static RotationModuleDefinition.ConfigRef DefineGCD(this RotationModuleDefinition res, Index track, AID aid, string internalName, string displayName = "", int uiPriority = 100, float cooldown = 0, float effectDuration = 0, ActionTargets supportedTargets = ActionTargets.None, int minLevel = 1, int maxLevel = 100) where Index : Enum where AID : Enum @@ -1204,7 +845,6 @@ public static RotationModuleDefinition.ConfigRef DefineGCDThe Targets Supported for the ability that the user is specifying. /// The Minimum Level required for the ability that the user is specifying. /// The Maximum Level required for the ability that the user is specifying. - /// - Basic OGCD options for any specified ability to be used via AutoRotation or Cooldown Planner public static RotationModuleDefinition.ConfigRef DefineOGCD(this RotationModuleDefinition res, Index track, AID aid, string internalName, string displayName = "", int uiPriority = 100, float cooldown = 0, float effectDuration = 0, ActionTargets supportedTargets = ActionTargets.None, int minLevel = 1, int maxLevel = 100) where Index : Enum where AID : Enum @@ -1222,44 +862,34 @@ public static RotationModuleDefinition.ConfigRef DefineOGCDA global helper for automatically executing the best optimal rotation. See for more details. - /// - TRUE if is set to - /// - FALSE if set to any other option - public static bool Automatic(this StrategyValues strategy) => strategy.Option(SharedTrack.AOE).As() is AOEStrategy.Automatic; + /// A global helper for easily retrieving the user's Rotation strategy. See for more details. + public static AOEStrategy Rotation(this StrategyValues strategy) => strategy.Option(SharedTrack.AOE).As(); + + /// A global helper for automatically executing the best optimal rotation; finishes combo if possible. See for more details. + public static bool AutoFinish(this StrategyValues strategy) => strategy.Option(SharedTrack.AOE).As() is AOEStrategy.AutoFinish; + + /// A global helper for automatically executing the best optimal rotation; breaks combo if necessary. See for more details. + public static bool AutoBreak(this StrategyValues strategy) => strategy.Option(SharedTrack.AOE).As() is AOEStrategy.AutoBreak; /// A global helper for force-executing the single-target rotation. See for more details. - /// - TRUE if is set to - /// - FALSE if set to any other option public static bool ForceST(this StrategyValues strategy) => strategy.Option(SharedTrack.AOE).As() is AOEStrategy.ForceST; /// A global helper for force-executing the AOE rotation. See for more details. - /// - TRUE if is set to - /// - FALSE if set to any other option public static bool ForceAOE(this StrategyValues strategy) => strategy.Option(SharedTrack.AOE).As() == AOEStrategy.ForceAOE; /// A global helper for forbidding ALL available abilities that are buff, gauge, or cooldown related. See for more details. - /// - TRUE if is set to - /// - FALSE if set to any other option public static bool HoldAll(this StrategyValues strategy) => strategy.Option(SharedTrack.Hold).As() == HoldStrategy.HoldEverything; /// A global helper for forbidding ALL available abilities that are related to raidbuffs. See for more details. - /// - TRUE if is set to - /// - FALSE if set to any other option public static bool HoldBuffs(this StrategyValues strategy) => strategy.Option(SharedTrack.Hold).As() == HoldStrategy.HoldBuffs; /// A global helper for forbidding ALL available abilities that have any sort of cooldown attached to it. See for more details. - /// - TRUE if is set to - /// - FALSE if set to any other option public static bool HoldCDs(this StrategyValues strategy) => strategy.Option(SharedTrack.Hold).As() == HoldStrategy.HoldCooldowns; /// A global helper for forbidding ALL available abilities that are related to the job's gauge. See for more details. - /// - TRUE if is set to - /// - FALSE if set to any other option public static bool HoldGauge(this StrategyValues strategy) => strategy.Option(SharedTrack.Hold).As() == HoldStrategy.HoldGauge; /// A global helper for allowing ALL available abilities that are buff, gauge, or cooldown related. This is the default option for this strategy. See for more details. - /// - TRUE if is set to - /// - FALSE if set to any other option public static bool DontHold(this StrategyValues strategy) => strategy.Option(SharedTrack.Hold).As() == HoldStrategy.DontHold; #endregion } diff --git a/BossMod/Autorotation/Standard/akechi/DPS/AkechiBLM.cs b/BossMod/Autorotation/Standard/akechi/DPS/AkechiBLM.cs index a1a0adac83..ade6cef995 100644 --- a/BossMod/Autorotation/Standard/akechi/DPS/AkechiBLM.cs +++ b/BossMod/Autorotation/Standard/akechi/DPS/AkechiBLM.cs @@ -32,7 +32,11 @@ public static RotationModuleDefinition Definition() BitMask.Build(Class.THM, Class.BLM), //Job 100); //Level supported - res.DefineShared(); + res.DefineAOE().AddAssociatedActions( + AID.Fire1, AID.Fire2, AID.Fire3, AID.Fire4, AID.HighFire2, + AID.Blizzard1, AID.Blizzard2, AID.Blizzard3, AID.Freeze, AID.Blizzard4, AID.HighBlizzard2, + AID.Flare, AID.Despair, AID.FlareStar); + res.DefineHold(); res.Define(Track.Movement).As("Movement", uiPriority: 195) .AddOption(MovementStrategy.Allow, "Allow", "Allow the use of all appropriate abilities for movement") .AddOption(MovementStrategy.AllowNoScathe, "AllowNoScathe", "Allow the use of all appropriate abilities for movement except for Scathe") @@ -396,7 +400,7 @@ movingOption is CastingOption.Forbid && SelfStatusLeft(SID.Firestarter, 30) is < 25 and not 0 || //or can use F3P Unlocked(TraitID.EnhancedAstralFire) && MP is < 1600 and not 0)) //instant cast Despair { - if (strategy.Automatic()) + if (strategy.AutoFinish() || strategy.AutoBreak()) BestRotation(TargetChoice(AOE) ?? BestSplashTarget?.Actor); if (strategy.ForceST()) BestST(TargetChoice(AOE) ?? primaryTarget?.Actor); @@ -409,7 +413,7 @@ movingOption is CastingOption.Forbid && //Thunder if (ShouldUseThunder(BestSplashTarget?.Actor, thunderStrat)) //if Thunder should be used based on strategy { - if (strategy.Automatic()) + if (strategy.AutoFinish() || strategy.AutoBreak()) QueueGCD(BestThunder, TargetChoice(thunder) ?? (ShouldUseAOE ? BestSplashTargets?.Actor : BestDOTTarget?.Actor), thunderLeft <= 3 ? GCDPriority.NeedDOT : diff --git a/BossMod/Autorotation/Standard/akechi/PvP/AkechiGNBPvP.cs b/BossMod/Autorotation/Standard/akechi/PvP/AkechiGNBPvP.cs index 9ac911dba1..b076c1852d 100644 --- a/BossMod/Autorotation/Standard/akechi/PvP/AkechiGNBPvP.cs +++ b/BossMod/Autorotation/Standard/akechi/PvP/AkechiGNBPvP.cs @@ -1,19 +1,20 @@ -using FFXIVClientStructs.FFXIV.Client.Game.Gauge; -using AID = BossMod.GNB.AID; -using SID = BossMod.GNB.SID; +using static BossMod.AIHints; +using FFXIVClientStructs.FFXIV.Client.Game.Gauge; +using BossMod.GNB; namespace BossMod.Autorotation.akechi; //Contribution by Akechi //Discord @akechdz or 'Akechi' on Puni.sh for maintenance -public sealed class AkechiGNBPvP(RotationModuleManager manager, Actor player) : RotationModule(manager, player) +public sealed class AkechiGNBPvP(RotationModuleManager manager, Actor player) : AkechiTools(manager, player) { #region Enums: Abilities / Strategies public enum Track { Burst, Combo, - LimitBreak, + RelentlessRush, + TerminalTrigger, GnashingFang, FatedCircle, RoughDivide, @@ -35,7 +36,14 @@ public enum ComboStrategy Hold } - public enum LimitBreakStrategy + public enum RushStrategy + { + Automatic, + Force, + Hold + } + + public enum TriggerStrategy { Automatic, Force, @@ -65,7 +73,6 @@ public static RotationModuleDefinition Definition() { 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) .AddOption(BurstStrategy.Automatic, "Automatic", "Use everything optimally") .AddOption(BurstStrategy.Force, "Force", "Force everything") @@ -76,13 +83,16 @@ public static RotationModuleDefinition Definition() .AddOption(ComboStrategy.Force, "Force", "Force combo") .AddOption(ComboStrategy.Hold, "Hold", "Hold combo"); - 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 + res.Define(Track.RelentlessRush).As("Relentless Rush", uiPriority: 190) + .AddOption(RushStrategy.Automatic, "Automatic", "Use Relentless Rush optimally") + .AddOption(RushStrategy.Force, "Force", "Force Relentless Rush") + .AddOption(RushStrategy.Hold, "Hold", "Hold Relentless Rush"); + + res.Define(Track.TerminalTrigger).As("Terminal Trigger", uiPriority: 190) + .AddOption(TriggerStrategy.Automatic, "Automatic", "Use Terminal Trigger optimally") + .AddOption(TriggerStrategy.Force, "Force", "Force Terminal Trigger") + .AddOption(TriggerStrategy.Hold, "Hold", "Hold Terminal Trigger"); - #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) @@ -112,7 +122,6 @@ 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.HeartOfCorundumPvP); - #endregion return res; } @@ -143,7 +152,7 @@ public enum OGCDPriority } #endregion - #region Placeholders for Variables + #region Module Variables private float nmLeft; private float rdCD; private bool hasNM; @@ -160,40 +169,26 @@ public enum OGCDPriority 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; - 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, ref Actor? primaryTarget, float estimatedAnimLockDelay, bool isMoving) + public override void Execution(StrategyValues strategy, Enemy? primaryTarget) { #region Variables var gauge = World.Client.GetGauge(); var GunStep = gauge.AmmoComboStep; - rdCD = CD(AID.RoughDividePvP); - nmLeft = SelfStatusLeft(SID.NoMercyPvP, 7); + rdCD = TotalCD(AID.RoughDividePvP); + nmLeft = StatusRemaining(Player, 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); + hasBlast = PlayerHasEffect(SID.ReadyToBlastPvP); + hasRaze = PlayerHasEffect(SID.ReadyToRazePvP); + hasRip = PlayerHasEffect(SID.ReadyToRipPvP) || GunStep == 1; + hasTear = PlayerHasEffect(SID.ReadyToTearPvP) || GunStep == 2; + hasGouge = PlayerHasEffect(SID.ReadyToGougePvP); LBready = World.Party.LimitBreakLevel >= 1; GFcomboStep = ComboLastMove switch { @@ -212,101 +207,81 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, }; inCombo = comboStep > 0; inGF = GFcomboStep > 0; - GCDLength = ActionSpeed.GCDRounded(World.Client.PlayerStats.SkillSpeed, World.Client.PlayerStats.Haste, Player.Level); - NextGCD = AID.None; - NextGCDPrio = GCDPriority.None; - - #region Minimal Requirements + var burst = strategy.Option(Track.Burst); + var burstStrategy = burst.As(); + var hold = burstStrategy == BurstStrategy.Hold; 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 + canHyper = hasBlast && In5y(PlayerTarget?.Actor); + canBrand = hasRaze && In5y(PlayerTarget?.Actor); + canRip = hasRip && In5y(PlayerTarget?.Actor); + canTear = hasTear && In5y(PlayerTarget?.Actor); + canGouge = hasGouge && In5y(PlayerTarget?.Actor); #endregion - var burst = strategy.Option(Track.Burst); - var burstStrategy = burst.As(); - var hold = burstStrategy == BurstStrategy.Hold; - - if (strategy.Option(Track.Combo).As() == ComboStrategy.Force) - QueueGCD(NextCombo(), primaryTarget, GCDPriority.ForcedGCD); - #region Rotation Execution - if (!hold && !inGF) - QueueGCD(NextCombo(), primaryTarget, GCDPriority.Combo); + GetPvPTarget(ref primaryTarget, 3); + + if (!inGF) + QueueGCD(NextCombo(), PlayerTarget?.Actor, GCDPriority.Combo); + if (strategy.Option(Track.Combo).As() is ComboStrategy.Force) + QueueGCD(NextCombo(), PlayerTarget?.Actor, GCDPriority.ForcedGCD); #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); + ShouldUseRoughDivide(rdStrat, PlayerTarget?.Actor)) + QueueOGCD(AID.RoughDividePvP, PlayerTarget?.Actor, 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); + ShouldUseZone(zoneStrat, PlayerTarget?.Actor)) + QueueOGCD(AID.BlastingZonePvP, PlayerTarget?.Actor, 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); + QueueOGCD(AID.JugularRipPvP, PlayerTarget?.Actor, OGCDPriority.Continuation); + if (canTear || GunStep == 2) + QueueOGCD(AID.AbdomenTearPvP, PlayerTarget?.Actor, OGCDPriority.Continuation); if (canGouge) - QueueOGCD(AID.EyeGougePvP, primaryTarget, OGCDPriority.Continuation); + QueueOGCD(AID.EyeGougePvP, PlayerTarget?.Actor, OGCDPriority.Continuation); if (canHyper) - QueueOGCD(AID.HypervelocityPvP, primaryTarget, OGCDPriority.Continuation); + QueueOGCD(AID.HypervelocityPvP, PlayerTarget?.Actor, OGCDPriority.Continuation); if (canBrand) - QueueOGCD(AID.FatedBrandPvP, primaryTarget, OGCDPriority.Continuation); + QueueOGCD(AID.FatedBrandPvP, PlayerTarget?.Actor, OGCDPriority.Continuation); + + if (TargetHPP(Player) < 55) + QueueOGCD(AID.HeartOfCorundumPvP, Player, OGCDPriority.Corundum); #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); + ShouldUseGnashingFang(gfStrat, PlayerTarget?.Actor)) + QueueGCD(AID.GnashingFangPvP, PlayerTarget?.Actor, GCDPriority.GnashingFang); + if (GunStep == 1 && In5y(PlayerTarget?.Actor)) + QueueGCD(AID.SavageClawPvP, PlayerTarget?.Actor, GCDPriority.GnashingFang); + if (GunStep == 2 && In5y(PlayerTarget?.Actor)) + QueueGCD(AID.WickedTalonPvP, PlayerTarget?.Actor, GCDPriority.GnashingFang); var fcStrat = strategy.Option(Track.FatedCircle).As(); - if (ShouldUseFatedCircle(fcStrat, primaryTarget)) - QueueGCD(AID.FatedCirclePvP, primaryTarget, fcStrat == OffensiveStrategy.Force ? GCDPriority.ForcedGCD : GCDPriority.FatedCircle); + if (ShouldUseFatedCircle(fcStrat, PlayerTarget?.Actor)) + QueueGCD(AID.FatedCirclePvP, PlayerTarget?.Actor, fcStrat == OffensiveStrategy.Force ? GCDPriority.ForcedGCD : GCDPriority.FatedCircle); #endregion #endregion - 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 - 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; - } - } - } - 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); - } + #region Limit Break + var rrStrat = strategy.Option(Track.RelentlessRush).As(); + if (ShouldUseRR(rrStrat, PlayerTarget?.Actor)) + QueueOGCD(AID.RelentlessRushPvP, Player, rrStrat == RushStrategy.Force ? OGCDPriority.ForcedOGCD : OGCDPriority.LB); + var ttStrat = strategy.Option(Track.TerminalTrigger).As(); + if (ShouldUseTT(ttStrat, PlayerTarget?.Actor) && Hints.NumPriorityTargetsInAOECircle(Player.Position, 5) > 0) + QueueGCD(AID.TerminalTriggerPvP, Player, ttStrat == TriggerStrategy.Force ? GCDPriority.ForcedGCD : GCDPriority.ForcedGCD); + #endregion } - #endregion - #region Single-Target Helpers + #region Cooldown Helpers private AID NextCombo() => ComboLastMove switch { AID.SolidBarrelPvP => AID.BurstStrikePvP, @@ -314,67 +289,46 @@ private void QueueOGCD(AID aid, Actor? target, OGCDPriority prio, float basePrio AID.KeenEdgePvP => AID.BrutalShellPvP, _ => AID.KeenEdgePvP, }; - #endregion - - #region Cooldown Helpers private bool ShouldUseRoughDivide(OffensiveStrategy strategy, Actor? target) => strategy switch { - OffensiveStrategy.Automatic => - target != null && - !hasNM || rdCD >= 7 || IsOffCooldown(AID.RoughDividePvP), - OffensiveStrategy.Force => true, + OffensiveStrategy.Automatic => target != null && (!hasNM && rdCD <= 14 || !OnCooldown(AID.RoughDividePvP)), + OffensiveStrategy.Force => rdCD <= 14.5f, OffensiveStrategy.Delay => false, _ => false }; - private bool ShouldUseZone(OffensiveStrategy strategy, Actor? target) => strategy switch { - OffensiveStrategy.Automatic => - Player.InCombat && - target != null && - canZone && - hasNM && - In5y(target), + OffensiveStrategy.Automatic => target != null && canZone && hasNM && In5y(target), OffensiveStrategy.Force => canZone, OffensiveStrategy.Delay => false, _ => false }; - private bool ShouldUseGnashingFang(OffensiveStrategy strategy, Actor? target) => strategy switch { - OffensiveStrategy.Automatic => - Player.InCombat && - target != null && - In5y(target) && - hasNM && - canGF, + OffensiveStrategy.Automatic => target != null && In5y(target) && hasNM && canGF, OffensiveStrategy.Force => canGF, OffensiveStrategy.Delay => false, _ => false }; - private bool ShouldUseFatedCircle(OffensiveStrategy strategy, Actor? target) => strategy switch { - OffensiveStrategy.Automatic => - Player.InCombat && - target != null && - In5y(target) && - hasNM && - canFC, + OffensiveStrategy.Automatic => target != null && In5y(target) && hasNM && canFC, OffensiveStrategy.Force => canFC, OffensiveStrategy.Delay => false, _ => false }; - - private bool ShouldUseLimitBreak(LimitBreakStrategy strategy, Actor? target) => strategy switch + private bool ShouldUseRR(RushStrategy strategy, Actor? target) => strategy switch + { + RushStrategy.Automatic => target != null && In5y(target) && hasNM && LBready, + RushStrategy.Force => LBready, + RushStrategy.Hold => false, + _ => false + }; + private bool ShouldUseTT(TriggerStrategy strategy, Actor? target) => strategy switch { - LimitBreakStrategy.Automatic => - target != null && - In5y(target) && - hasNM && - LBready, - LimitBreakStrategy.Force => true, - LimitBreakStrategy.Hold => false, + TriggerStrategy.Automatic => StacksRemaining(target, SID.RelentlessShrapnelPvP) > 0 && PlayerHasEffect(SID.RelentlessRushPvP), + TriggerStrategy.Force => PlayerHasEffect(SID.RelentlessRushPvP), + TriggerStrategy.Hold => false, _ => false }; #endregion diff --git a/BossMod/Autorotation/Standard/akechi/Tank/AkechiDRK.cs b/BossMod/Autorotation/Standard/akechi/Tank/AkechiDRK.cs index 4eb5d9f139..275424b0ef 100644 --- a/BossMod/Autorotation/Standard/akechi/Tank/AkechiDRK.cs +++ b/BossMod/Autorotation/Standard/akechi/Tank/AkechiDRK.cs @@ -29,14 +29,16 @@ public static RotationModuleDefinition Definition() BitMask.Build((int)Class.DRK), //Job 100); //Level supported - res.DefineShared(); + res.DefineAOE().AddAssociatedActions(AID.HardSlash, AID.SyphonStrike, AID.Souleater, AID.Unleash, AID.StalwartSoul); + res.DefineHold(); res.Define(Track.Blood).As("Blood", "Blood", uiPriority: 200) .AddOption(BloodStrategy.Automatic, "Automatic", "Automatically use Blood-related abilities optimally") .AddOption(BloodStrategy.OnlyBloodspiller, "Only Bloodspiller", "Uses Bloodspiller optimally as Blood spender only, regardless of targets", 0, 0, ActionTargets.Hostile, 62) .AddOption(BloodStrategy.OnlyQuietus, "Only Quietus", "Uses Quietus optimally as Blood spender only, regardless of targets", 0, 0, ActionTargets.Hostile, 64) .AddOption(BloodStrategy.ForceBloodspiller, "Force Bloodspiller", "Force use Bloodspiller ASAP", 0, 0, ActionTargets.Hostile, 62) .AddOption(BloodStrategy.ForceQuietus, "Force Quietus", "Force use Quietus ASAP", 0, 0, ActionTargets.Hostile, 64) - .AddOption(BloodStrategy.Conserve, "Conserve", "Conserves all Blood-related abilities as much as possible"); + .AddOption(BloodStrategy.Conserve, "Conserve", "Conserves all Blood-related abilities as much as possible") + .AddAssociatedActions(AID.Bloodspiller, AID.Quietus); res.Define(Track.MP).As("MP", "MP", uiPriority: 190) .AddOption(MPStrategy.Optimal, "Optimal", "Use MP actions optimally; 2 for 1 minute, 4 (or 5 if Dark Arts is active) for 2 minutes") .AddOption(MPStrategy.Auto3k, "Auto 3k", "Automatically decide best MP action to use; Uses when at 3000+ MP", 0, 0, ActionTargets.Self, 30) @@ -60,7 +62,7 @@ public static RotationModuleDefinition Definition() .AddOption(CarveStrategy.ForceCarve, "Force Carve and Spit", "Force use Carve and Spit ASAP", 60, 0, ActionTargets.Hostile, 60) .AddOption(CarveStrategy.ForceDrain, "Force Abyssal Drain", "Force use Abyssal Drain ASAP", 60, 0, ActionTargets.Hostile, 56) .AddOption(CarveStrategy.Delay, "Delay", "Delay the use of Carve and Spit for strategic reasons", 0, 0, ActionTargets.None, 56) - .AddAssociatedActions(AID.CarveAndSpit); + .AddAssociatedActions(AID.CarveAndSpit, AID.AbyssalDrain); res.Define(Track.DeliriumCombo).As("Delirium Combo", "Scarlet", uiPriority: 180) .AddOption(DeliriumComboStrategy.Automatic, "Auto", "Automatically decide when to use Delirium Combo", 0, 0, ActionTargets.Hostile, 96) .AddOption(DeliriumComboStrategy.ScarletDelirum, "Scarlet Delirium", "Force use Scarlet Delirium ASAP", 0, 0, ActionTargets.Hostile, 96) @@ -81,7 +83,7 @@ public static RotationModuleDefinition Definition() .AddOption(UnmendStrategy.Allow, "Allow", "Allow use of Unmend when out of melee range", supportedTargets: ActionTargets.Hostile) .AddOption(UnmendStrategy.Forbid, "Forbid", "Prohibit use of Unmend") .AddAssociatedActions(AID.Unmend); - res.DefineOGCD(Track.Delirium, AID.Delirium, "Delirium", "Deli.", uiPriority: 170, 60, 15, ActionTargets.Self, 35); + res.DefineOGCD(Track.Delirium, AID.Delirium, "Delirium", "Deli.", uiPriority: 170, 60, 15, ActionTargets.Self, 35).AddAssociatedActions(AID.BloodWeapon, AID.Delirium); res.DefineOGCD(Track.SaltedEarth, AID.SaltedEarth, "Salted Earth", "S.Earth", uiPriority: 140, 90, 15, ActionTargets.Self, 52); res.DefineOGCD(Track.SaltAndDarkness, AID.SaltAndDarkness, "Salt & Darkness", "Salt & D.", uiPriority: 135, 20, 0, ActionTargets.Self, 86); res.DefineOGCD(Track.LivingShadow, AID.LivingShadow, "Living Shadow", "L.Shadow", uiPriority: 175, 120, 20, ActionTargets.Self, 80); @@ -233,10 +235,14 @@ public override void Execution(StrategyValues strategy, Enemy? primaryTarget) #region Full Rotation Execution #region Standard Rotations - if (strategy.Automatic()) + if (strategy.AutoFinish()) QueueGCD(BestRotation(), TargetChoice(strategy.Option(SharedTrack.AOE)) ?? primaryTarget?.Actor, GCDPriority.Standard); + if (strategy.AutoBreak()) + QueueGCD(ShouldUseAOE ? AOE() : ST(), + TargetChoice(strategy.Option(SharedTrack.AOE)) ?? primaryTarget?.Actor, + GCDPriority.Standard); if (strategy.ForceST()) QueueGCD(ST(), TargetChoice(strategy.Option(SharedTrack.AOE)) ?? primaryTarget?.Actor, @@ -385,12 +391,12 @@ bloodStrat is BloodStrategy.ForceQuietus ? GCDPriority.ForcedGCD #region AI var goalST = primaryTarget?.Actor != null ? Hints.GoalSingleTarget(primaryTarget!.Actor, 3) : null; //Set goal for single target - var goalAOE = primaryTarget?.Actor != null ? Hints.GoalAOECircle(5) : null; //Set goal for AOE + var goalAOE = Hints.GoalAOECircle(3); //Set goal for AOE var goal = strategy.Option(SharedTrack.AOE).As() switch //Set goal based on AOE strategy { AOEStrategy.ForceST => goalST, //if forced single target AOEStrategy.ForceAOE => goalAOE, //if forced AOE - _ => goalST != null && goalAOE != null ? Hints.GoalCombined(goalST, goalAOE, 2) : goalAOE //otherwise, combine goals + _ => goalST != null ? Hints.GoalCombined(goalST, goalAOE, 3) : goalAOE //otherwise, combine goals }; if (goal != null) //if goal is set Hints.GoalZones.Add(goal); //add goal to zones diff --git a/BossMod/Autorotation/Standard/akechi/Tank/AkechiGNB.cs b/BossMod/Autorotation/Standard/akechi/Tank/AkechiGNB.cs index e167e3b2d4..3c22305764 100644 --- a/BossMod/Autorotation/Standard/akechi/Tank/AkechiGNB.cs +++ b/BossMod/Autorotation/Standard/akechi/Tank/AkechiGNB.cs @@ -12,14 +12,15 @@ public sealed class AkechiGNB(RotationModuleManager manager, Actor player) : Ake public enum Track { AOE, Cooldowns, Cartridges, Potion, LightningShot, NoMercy, SonicBreak, GnashingFang, Reign, Bloodfest, DoubleDown, Zone, BowShock } public enum AOEStrategy { AutoFinishCombo, AutoBreakCombo, ForceSTwithO, ForceSTwithoutO, ForceAOEwithO, ForceAOEwithoutO, GenerateDowntime } public enum CooldownStrategy { Allow, Forbid } - public enum CartridgeStrategy { Automatic, OnlyBS, OnlyFC, ForceBS, ForceFC, Conserve } + public enum CartridgeStrategy { Automatic, OnlyBS, OnlyFC, ForceBS, ForceBS1, ForceBS2, ForceBS3, ForceFC, ForceFC1, ForceFC2, ForceFC3, Conserve } public enum PotionStrategy { Manual, AlignWithRaidBuffs, Immediate } public enum LightningShotStrategy { OpenerFar, OpenerForce, Force, Allow, Forbid } public enum NoMercyStrategy { Automatic, Force, ForceW, ForceQW, Force1, Force1W, Force1QW, Force2, Force2W, Force2QW, Force3, Force3W, Force3QW, Delay } public enum SonicBreakStrategy { Automatic, Force, Early, Late, Delay } - public enum GnashingStrategy { Automatic, ForceGnash, ForceClaw, ForceTalon, Delay } + public enum GnashingStrategy { Automatic, ForceGnash, ForceGnash1, ForceGnash2, ForceGnash3, ForceClaw, ForceTalon, Delay } public enum ReignStrategy { Automatic, ForceReign, ForceNoble, ForceLion, Delay } public enum BloodfestStrategy { Automatic, Force, ForceW, Force0, Force0W, Delay } + public enum DoubleDownStrategy { Automatic, Force, Force1, Force2, Force3, Delay } #endregion #region Module Definitions @@ -40,7 +41,8 @@ public static RotationModuleDefinition Definition() .AddOption(AOEStrategy.ForceSTwithoutO, "Force ST without Overcap", "Force ST rotation without overcap protection", supportedTargets: ActionTargets.Hostile) .AddOption(AOEStrategy.ForceAOEwithO, "Force AOE with Overcap", "Force AOE rotation with overcap protection") .AddOption(AOEStrategy.ForceAOEwithoutO, "Force AOE without Overcap", "Force AOE rotation without overcap protection") - .AddOption(AOEStrategy.GenerateDowntime, "Generate Downtime", "Generate cartridges before downtime"); + .AddOption(AOEStrategy.GenerateDowntime, "Generate Downtime", "Generate cartridges before downtime") + .AddAssociatedActions(AID.KeenEdge, AID.BrutalShell, AID.SolidBarrel, AID.DemonSlice, AID.DemonSlaughter); res.Define(Track.Cooldowns).As("Hold", uiPriority: 190) .AddOption(CooldownStrategy.Allow, "Allow", "Allows the use of all cooldowns & buffs; will use them optimally") .AddOption(CooldownStrategy.Forbid, "Hold", "Forbids the use of all cooldowns & buffs; will not use any actions with a cooldown timer"); @@ -48,9 +50,15 @@ public static RotationModuleDefinition Definition() .AddOption(CartridgeStrategy.Automatic, "Automatic", "Automatically decide when to use cartridges; uses them optimally") .AddOption(CartridgeStrategy.OnlyBS, "Only Burst Strike", "Uses Burst Strike optimally as cartridge spender only, regardless of targets", 0, 0, ActionTargets.Hostile, 30) .AddOption(CartridgeStrategy.OnlyFC, "Only Fated Circle", "Uses Fated Circle optimally as cartridge spender only, regardless of targets", 0, 0, ActionTargets.Hostile, 72) - .AddOption(CartridgeStrategy.ForceBS, "Force Burst Strike", "Force use of Burst Strike; consumes 1 cartridge", 0, 0, ActionTargets.Hostile, 30) - .AddOption(CartridgeStrategy.ForceFC, "Force Fated Circle", "Force use of Fated Circle; consumes 1 cartridge", 0, 0, ActionTargets.Hostile, 72) - .AddOption(CartridgeStrategy.Conserve, "Conserve", "Prohibit use of all cartridge-related abilities; will not use any of these actions listed above") + .AddOption(CartridgeStrategy.ForceBS, "Force Burst Strike", "Force use of Burst Strike regardless of cartridge count", 0, 0, ActionTargets.Hostile, 30) + .AddOption(CartridgeStrategy.ForceBS1, "Force Burst Strike (1 cart)", "Force use of Burst Strike when only 1 cartridge is available", 0, 0, ActionTargets.Hostile, 30) + .AddOption(CartridgeStrategy.ForceBS2, "Force Burst Strike (2 cart)", "Force use of Burst Strike when only 2 cartridges are available", 0, 0, ActionTargets.Hostile, 30) + .AddOption(CartridgeStrategy.ForceBS3, "Force Burst Strike (3 cart)", "Force use of Burst Strike when only 3 cartridges are available", 0, 0, ActionTargets.Hostile, 30) + .AddOption(CartridgeStrategy.ForceFC, "Force Fated Circle", "Force use of Fated Circle when any cartridges are available", 0, 0, ActionTargets.Hostile, 72) + .AddOption(CartridgeStrategy.ForceFC1, "Force Fated Circle (1 cart)", "Force use of Fated Circle when only 1 cartridge is available", 0, 0, ActionTargets.Hostile, 72) + .AddOption(CartridgeStrategy.ForceFC2, "Force Fated Circle (2 cart)", "Force use of Fated Circle when only 2 cartridges are available", 0, 0, ActionTargets.Hostile, 72) + .AddOption(CartridgeStrategy.ForceFC3, "Force Fated Circle (3 cart)", "Force use of Fated Circle when only 3 cartridges are available", 0, 0, ActionTargets.Hostile, 72) + .AddOption(CartridgeStrategy.Conserve, "Conserve", "Forbid use of Burst Strike & Fated Circle", 0, 0, ActionTargets.None, 30) .AddAssociatedActions(AID.BurstStrike, AID.FatedCircle); res.Define(Track.Potion).As("Potion", uiPriority: 20) .AddOption(PotionStrategy.Manual, "Manual", "Do not use automatically") @@ -69,15 +77,15 @@ public static RotationModuleDefinition Definition() .AddOption(NoMercyStrategy.Force, "Force", "Force use of No Mercy, regardless of weaving", 60, 20, ActionTargets.Self, 2) .AddOption(NoMercyStrategy.ForceW, "Force (Weave)", "Force use of No Mercy in next possible weave slot", 60, 20, ActionTargets.Self, 2) .AddOption(NoMercyStrategy.ForceQW, "Force (Q.Weave)", "Force use of No Mercy in next possible last second weave slot", 60, 20, ActionTargets.Self, 2) - .AddOption(NoMercyStrategy.Force1, "Force (1 cart)", "Force use of No Mercy when 1 cartridge is available, regardless of weaving", 60, 20, ActionTargets.Self, 2) - .AddOption(NoMercyStrategy.Force1W, "Force (1 cart, Weave)", "Force use of No Mercy when 1 cartridge is available & in next weave slot", 60, 20, ActionTargets.Self, 2) - .AddOption(NoMercyStrategy.Force1QW, "Force (1 cart, Q.Weave)", "Force use of No Mercy when 1 cartridge is available & in next possible last-second weave slot", 60, 20, ActionTargets.Self, 2) - .AddOption(NoMercyStrategy.Force2, "Force (2 carts)", "Force use of No Mercy when 2 cartridges are available, regardless of weaving", 60, 20, ActionTargets.Self, 2) - .AddOption(NoMercyStrategy.Force2W, "Force (2 carts, Weave)", "Force use of No Mercy when 2 cartridges are available & in next possible weave slot", 60, 20, ActionTargets.Self, 2) - .AddOption(NoMercyStrategy.Force2QW, "Force (2 carts, Q.Weave)", "Force use of No Mercy when 2 cartridges are available & in next possible last-second weave slot", 60, 20, ActionTargets.Self, 2) - .AddOption(NoMercyStrategy.Force3, "Force (3 carts)", "Force use of No Mercy when 3 cartridges are available, regardless of weaving", 60, 20, ActionTargets.Self, 2) - .AddOption(NoMercyStrategy.Force3W, "Force (3 carts, Weave)", "Force use of No Mercy when 3 cartridges are available & in next possible weave slot", 60, 20, ActionTargets.Self, 2) - .AddOption(NoMercyStrategy.Force3QW, "Force (3 carts, Q.Weave)", "Force use of No Mercy when 3 cartridges are available & in next possible last-second weave slot", 60, 20, ActionTargets.Self, 2) + .AddOption(NoMercyStrategy.Force1, "Force (1 cart)", "Force use of No Mercy when only 1 cartridge is available, regardless of weaving", 60, 20, ActionTargets.Self, 2) + .AddOption(NoMercyStrategy.Force1W, "Force (1 cart, Weave)", "Force use of No Mercy when only 1 cartridge is available & in next weave slot", 60, 20, ActionTargets.Self, 2) + .AddOption(NoMercyStrategy.Force1QW, "Force (1 cart, Q.Weave)", "Force use of No Mercy when only 1 cartridge is available & in next possible last-second weave slot", 60, 20, ActionTargets.Self, 2) + .AddOption(NoMercyStrategy.Force2, "Force (2 carts)", "Force use of No Mercy when only 2 cartridges are available, regardless of weaving", 60, 20, ActionTargets.Self, 2) + .AddOption(NoMercyStrategy.Force2W, "Force (2 carts, Weave)", "Force use of No Mercy when only 2 cartridges are available & in next possible weave slot", 60, 20, ActionTargets.Self, 2) + .AddOption(NoMercyStrategy.Force2QW, "Force (2 carts, Q.Weave)", "Force use of No Mercy when only 2 cartridges are available & in next possible last-second weave slot", 60, 20, ActionTargets.Self, 2) + .AddOption(NoMercyStrategy.Force3, "Force (3 carts)", "Force use of No Mercy when only 3 cartridges are available, regardless of weaving", 60, 20, ActionTargets.Self, 2) + .AddOption(NoMercyStrategy.Force3W, "Force (3 carts, Weave)", "Force use of No Mercy when only 3 cartridges are available & in next possible weave slot", 60, 20, ActionTargets.Self, 2) + .AddOption(NoMercyStrategy.Force3QW, "Force (3 carts, Q.Weave)", "Force use of No Mercy when only 3 cartridges are available & in next possible last-second weave slot", 60, 20, ActionTargets.Self, 2) .AddOption(NoMercyStrategy.Delay, "Delay", "Delay use of No Mercy", 0, 0, ActionTargets.None, 2) .AddAssociatedActions(AID.NoMercy); res.Define(Track.SonicBreak).As("Sonic Break", "S.Break", uiPriority: 150) @@ -89,9 +97,12 @@ public static RotationModuleDefinition Definition() .AddAssociatedActions(AID.SonicBreak); 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.ForceGnash, "Force", "Force use of Gnashing Fang", 30, 0, ActionTargets.Hostile, 60) + .AddOption(GnashingStrategy.ForceGnash1, "Force (1 cart)", "Force use of Gnashing Fang when only 1 cartridge is available", 30, 0, ActionTargets.Hostile, 60) + .AddOption(GnashingStrategy.ForceGnash2, "Force (2 carts)", "Force use of Gnashing Fang when only 2 cartridges are available", 30, 0, ActionTargets.Hostile, 60) + .AddOption(GnashingStrategy.ForceGnash3, "Force (3 carts)", "Force use of Gnashing Fang when only 3 cartridges are available", 30, 0, ActionTargets.Hostile, 60) + .AddOption(GnashingStrategy.ForceClaw, "Force Savage Claw", "Force use of Savage Claw", 0, 0, ActionTargets.Hostile, 60) + .AddOption(GnashingStrategy.ForceTalon, "Force Talon", "Force use of Wicked Talon", 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); res.Define(Track.Reign).As("Reign of Beasts", "Reign", uiPriority: 160) @@ -109,7 +120,15 @@ public static RotationModuleDefinition Definition() .AddOption(BloodfestStrategy.Force0W, "Force (0 cart, Weave)", "Force use of Bloodfest only if empty on cartridges & in next possible weave slot", 120, 0, ActionTargets.Hostile, 80) .AddOption(BloodfestStrategy.Delay, "Delay", "Delay use of Bloodfest", 0, 0, ActionTargets.None, 80) .AddAssociatedActions(AID.Bloodfest); - res.DefineGCD(Track.DoubleDown, AID.DoubleDown, "DoubleDown", "D.Down", uiPriority: 160, 60, 0, ActionTargets.Hostile, 90); + res.Define(Track.DoubleDown).As("DoubleDown", "D.Down", uiPriority: 160) + .AddOption(DoubleDownStrategy.Automatic, "Automatic", "Normal use of Double Down") + .AddOption(DoubleDownStrategy.Force, "Force Double Down", "Force use of Double Down regardless of cartridge count", 60, 0, ActionTargets.Hostile, 90) + .AddOption(DoubleDownStrategy.Force1, "Force Double Down (1 cart)", "Force use of Double Down when only 1 cartridge is available", 60, 0, ActionTargets.Hostile, 90) + .AddOption(DoubleDownStrategy.Force2, "Force Double Down (2 cart)", "Force use of Double Down when only 2 cartridges are available", 60, 0, ActionTargets.Hostile, 90) + .AddOption(DoubleDownStrategy.Force3, "Force Double Down (3 cart)", "Force use of Double Down when only 3 cartridges are available", 60, 0, ActionTargets.Hostile, 90) + .AddOption(DoubleDownStrategy.Delay, "Delay", "Delay use of Double Down", 0, 0, ActionTargets.None, 90) + .AddAssociatedActions(AID.DoubleDown); + res.DefineOGCD(Track.Zone, AID.DangerZone, "Zone", "Zone", uiPriority: 150, 30, 0, ActionTargets.Hostile, 18).AddAssociatedActions(AID.BlastingZone, AID.DangerZone); res.DefineOGCD(Track.BowShock, AID.BowShock, "BowShock", "B.Shock", uiPriority: 150, 60, 15, ActionTargets.Self, 62); @@ -122,24 +141,25 @@ public enum GCDPriority { None = 0, Standard = 100, - ForcedCombo = 499, - Gauge = 500, - Reign = 525, - comboNeed = 550, - GF23 = 575, + Gauge = 400, + ForcedCombo = 425, + Reign = 450, + comboNeed = 500, + GF23 = 550, SonicBreak = 600, - DoubleDown = 675, + DoubleDown = 650, GF1 = 700, + Only1Ammo = 750, ForcedGCD = 900, } public enum OGCDPriority { None = 0, - Continuation = 500, - Zone = 550, - BowShock = 600, - Bloodfest = 700, - NoMercy = 875, + Continuation = 400, + Zone = 450, + BowShock = 500, + Bloodfest = 600, + NoMercy = 650, Potion = 900, ForcedOGCD = 1100, //Enough to put it past CDPlanner's "Automatic" priority, which is really only Medium priority } @@ -147,7 +167,7 @@ public enum OGCDPriority #region Upgrade Paths private AID BestZone => Unlocked(AID.BlastingZone) ? AID.BlastingZone : AID.DangerZone; - private AID BestCartSpender => ShouldUseFC ? BestFatedCircle : canBS ? AID.BurstStrike : BestRotation(); + private AID BestCartSpender => ShouldUseAOE ? BestFatedCircle : canBS ? AID.BurstStrike : BestRotation(); private AID BestFatedCircle => Unlocked(AID.FatedCircle) ? AID.FatedCircle : AID.BurstStrike; private AID BestContinuation => hasRaze ? AID.FatedBrand : hasBlast ? AID.Hypervelocity : hasGouge ? AID.EyeGouge : hasTear ? AID.AbdomenTear : hasRip ? AID.JugularRip : AID.Continuation; #endregion @@ -180,7 +200,6 @@ public enum OGCDPriority private bool canContinue; //Checks if Continuation is completely available private bool canReign; //Checks if Reign of Beasts & its combo chain are completely available private bool ShouldUseAOE; //Checks if AOE rotation should be used - private bool ShouldUseFC; //Checks if Fated Circle should be used private int NumSplashTargets; private Enemy? BestSplashTargets; private Enemy? BestSplashTarget; @@ -205,21 +224,20 @@ public override void Execution(StrategyValues strategy, Enemy? primaryTarget) hasTear = PlayerHasEffect(SID.ReadyToTear, 10f) && !LastActionUsed(AID.AbdomenTear); //Checks for Ready To Tear buff hasGouge = PlayerHasEffect(SID.ReadyToGouge, 10f) && !LastActionUsed(AID.EyeGouge); //Checks for Ready To Gouge buff inOdd = bfCD is <= 90 and >= 30; //Checks if we are in an odd-minute window - ShouldUseAOE = Unlocked(TraitID.MeleeMastery) ? ShouldUseAOECircle(5).OnThreeOrMore : ShouldUseAOECircle(5).OnTwoOrMore; - ShouldUseFC = ShouldUseAOECircle(5).OnTwoOrMore; //Determine if we should use Fated Circle + ShouldUseAOE = ShouldUseAOECircle(5).OnTwoOrMore; (BestSplashTargets, NumSplashTargets) = GetBestTarget(primaryTarget, 3, IsSplashTarget); BestSplashTarget = Unlocked(AID.ReignOfBeasts) && NumSplashTargets > 1 ? BestSplashTargets : primaryTarget; #region Minimal Requirements - canNM = TotalCD(AID.NoMercy) < 1; //No Mercy conditions + canNM = ActionReady(AID.NoMercy); //No Mercy conditions canBS = Unlocked(AID.BurstStrike) && Ammo > 0; //Burst Strike conditions; -1 Ammo ST - canGF = Unlocked(AID.GnashingFang) && ActionReady(AID.GnashingFang) && Ammo > 0; //Gnashing Fang conditions; -1 Ammo ST + canGF = ActionReady(AID.GnashingFang) && Ammo > 0; //Gnashing Fang conditions; -1 Ammo ST canFC = Unlocked(AID.FatedCircle) && Ammo > 0; //Fated Circle conditions; -1 Ammo AOE - canDD = Unlocked(AID.DoubleDown) && ActionReady(AID.DoubleDown) && Ammo > 0; //Double Down conditions; -1 Ammo AOE - canBF = Unlocked(AID.Bloodfest) && ActionReady(AID.Bloodfest); //Bloodfest conditions; +all Ammo (must have target) - canZone = Unlocked(AID.DangerZone) && ActionReady(AID.DangerZone); //Zone conditions - canBreak = hasBreak && Unlocked(AID.SonicBreak); //Sonic Break conditions - canBow = Unlocked(AID.BowShock) && ActionReady(AID.BowShock); //Bow Shock conditions + canDD = ActionReady(AID.DoubleDown) && Ammo > 0; //Double Down conditions; -1 Ammo AOE + canBF = ActionReady(AID.Bloodfest); //Bloodfest conditions; +all Ammo (must have target) + canZone = ActionReady(AID.DangerZone); //Zone conditions + canBreak = Unlocked(AID.SonicBreak) && hasBreak; //Sonic Break conditions + canBow = ActionReady(AID.BowShock); //Bow Shock conditions canContinue = Unlocked(AID.Continuation); //Continuation conditions canReign = Unlocked(AID.ReignOfBeasts) && hasReign; //Reign of Beasts conditions #endregion @@ -238,7 +256,7 @@ public override void Execution(StrategyValues strategy, Enemy? primaryTarget) var bf = strategy.Option(Track.Bloodfest); //Bloodfest track var bfStrat = bf.As(); //Bloodfest strategy var dd = strategy.Option(Track.DoubleDown); //Double Down track - var ddStrat = dd.As(); //Double Down strategy + var ddStrat = dd.As(); //Double Down strategy var gf = strategy.Option(Track.GnashingFang); //Gnashing Fang track var gfStrat = gf.As(); //Gnashing Fang strategy var reign = strategy.Option(Track.Reign); //Reign of Beasts track @@ -282,34 +300,34 @@ public override void Execution(StrategyValues strategy, Enemy? primaryTarget) //TODO: refactor this if (AOEStrategy == AOEStrategy.GenerateDowntime) //if Generate Downtime option is selected { - if (DowntimeIn == GCD * 2 && Ammo == 2 || //if 2 GCDs until downtime & has 2 cartridges - DowntimeIn == GCD * 4 && Ammo == 1 || //if 4 GCDs until downtime & has 1 cartridge - DowntimeIn == GCD * 6 && Ammo == 0) //if 6 GCDs until downtime & has 0 cartridges + if (DowntimeIn == SkSGCDLength * 2 && Ammo == 2 || //if 2 GCDs until downtime & has 2 cartridges + DowntimeIn == SkSGCDLength * 4 && Ammo == 1 || //if 4 GCDs until downtime & has 1 cartridge + DowntimeIn == SkSGCDLength * 6 && Ammo == 0) //if 6 GCDs until downtime & has 0 cartridges QueueGCD(AID.DemonSlice, //queue Demon Slice Player, //on Self (no target needed) GCDPriority.ForcedCombo); //with priority for forced GCDs - if (DowntimeIn == GCD * 3 && Ammo == 2 || //if 3 GCDs until downtime & has 2 cartridges - DowntimeIn == GCD * 5 && Ammo == 1 || //if 5 GCDs until downtime & has 1 cartridge - DowntimeIn == GCD * 8 && Ammo == 0 || //if 8 GCDs until downtime & has 0 cartridges - DowntimeIn == GCD * 9 && Ammo == 0) //if 9 GCDs until downtime & has 0 cartridges + if (DowntimeIn == SkSGCDLength * 3 && Ammo == 2 || //if 3 GCDs until downtime & has 2 cartridges + DowntimeIn == SkSGCDLength * 5 && Ammo == 1 || //if 5 GCDs until downtime & has 1 cartridge + DowntimeIn == SkSGCDLength * 8 && Ammo == 0 || //if 8 GCDs until downtime & has 0 cartridges + DowntimeIn == SkSGCDLength * 9 && Ammo == 0) //if 9 GCDs until downtime & has 0 cartridges QueueGCD(AID.KeenEdge, //queue Keen Edge primaryTarget?.Actor, //on the primary target GCDPriority.ForcedCombo); //with priority for forced GCDs if (ComboLastMove == AID.DemonSlice && //if last move was Demon Slice (DowntimeIn == GCD && Ammo == 2 || //if 1 GCD until downtime & has 2 cartridges - DowntimeIn == GCD * 3 && Ammo == 1 || //if 3 GCDs until downtime & has 1 cartridge - DowntimeIn == GCD * 5 && Ammo == 0)) //if 5 GCDs until downtime & has 0 cartridges + DowntimeIn == SkSGCDLength * 3 && Ammo == 1 || //if 3 GCDs until downtime & has 1 cartridge + DowntimeIn == SkSGCDLength * 5 && Ammo == 0)) //if 5 GCDs until downtime & has 0 cartridges QueueGCD(AID.DemonSlaughter, //queue Demon Slaughter Player, //on Self (no target needed) GCDPriority.ForcedCombo); //with priority for forced GCDs if (ComboLastMove == AID.KeenEdge && //if last move was Keen Edge - (DowntimeIn == GCD * 2 && Ammo == 2 || //if 2 GCDs until downtime & has 2 cartridges - DowntimeIn == GCD * 4 && Ammo == 1 || //if 4 GCDs until downtime & has 1 cartridge - DowntimeIn == GCD * 7 && Ammo == 2 || //if 7 GCDs until downtime & has 2 cartridges - DowntimeIn == GCD * 8 && Ammo == 2)) //if 8 GCDs until downtime & has 2 cartridges + (DowntimeIn == SkSGCDLength * 2 && Ammo == 2 || //if 2 GCDs until downtime & has 2 cartridges + DowntimeIn == SkSGCDLength * 4 && Ammo == 1 || //if 4 GCDs until downtime & has 1 cartridge + DowntimeIn == SkSGCDLength * 7 && Ammo == 2 || //if 7 GCDs until downtime & has 2 cartridges + DowntimeIn == SkSGCDLength * 8 && Ammo == 2)) //if 8 GCDs until downtime & has 2 cartridges QueueGCD(AID.BrutalShell, //queue Brutal Shell primaryTarget?.Actor, //on the primary target GCDPriority.ForcedCombo); //with priority for forced GCDs @@ -317,8 +335,8 @@ public override void Execution(StrategyValues strategy, Enemy? primaryTarget) if (ComboLastMove == AID.BrutalShell) //if last move was Brutal Shell { if (DowntimeIn == GCD && (Ammo == 2 || Ammo == 3) || //if 1 GCD until downtime & has 2 or 3 cartridges - DowntimeIn == GCD * 4 && Ammo == 1 || //if 4 GCDs until downtime & has 1 cartridge - DowntimeIn == GCD * 7 && Ammo == 0) //if 7 GCDs until downtime & has 0 cartridges + DowntimeIn == SkSGCDLength * 4 && Ammo == 1 || //if 4 GCDs until downtime & has 1 cartridge + DowntimeIn == SkSGCDLength * 7 && Ammo == 0) //if 7 GCDs until downtime & has 0 cartridges QueueGCD(AID.SolidBarrel, //queue Solid Barrel primaryTarget?.Actor, //on the primary target GCDPriority.ForcedCombo); //with priority for forced GCDs @@ -394,12 +412,13 @@ reignStrat is ReignStrategy.ForceReign if (ShouldUseDoubleDown(ddStrat, primaryTarget?.Actor)) QueueGCD(AID.DoubleDown, primaryTarget?.Actor, - ddStrat is GCDStrategy.Force || Ammo == 1 - ? GCDPriority.ForcedGCD : GCDPriority.DoubleDown); + ddStrat is DoubleDownStrategy.Force or DoubleDownStrategy.Force1 or DoubleDownStrategy.Force2 or DoubleDownStrategy.Force3 + ? GCDPriority.ForcedGCD + : Ammo == 1 ? GCDPriority.Only1Ammo : GCDPriority.DoubleDown); if (ShouldUseGnashingFang(gfStrat, primaryTarget?.Actor)) QueueGCD(AID.GnashingFang, TargetChoice(gf) ?? primaryTarget?.Actor, - gfStrat is GnashingStrategy.ForceGnash + gfStrat is GnashingStrategy.ForceGnash or GnashingStrategy.ForceGnash1 or GnashingStrategy.ForceGnash2 or GnashingStrategy.ForceGnash3 ? GCDPriority.ForcedGCD : GCDPriority.GF1); if (ShouldUseCartridges(cartStrat, primaryTarget?.Actor)) { @@ -408,11 +427,11 @@ gfStrat is GnashingStrategy.ForceGnash TargetChoice(carts) ?? primaryTarget?.Actor, nmCD < 1 && Ammo == 3 ? GCDPriority.ForcedGCD : GCDPriority.Gauge); - if (cartStrat is CartridgeStrategy.OnlyBS or CartridgeStrategy.ForceBS) + if (cartStrat is CartridgeStrategy.OnlyBS or CartridgeStrategy.ForceBS or CartridgeStrategy.ForceBS1 or CartridgeStrategy.ForceBS2 or CartridgeStrategy.ForceBS3) QueueGCD(AID.BurstStrike, TargetChoice(carts) ?? primaryTarget?.Actor, GCDPriority.Gauge); - if (cartStrat is CartridgeStrategy.ForceFC or CartridgeStrategy.OnlyFC) + if (cartStrat is CartridgeStrategy.ForceFC or CartridgeStrategy.OnlyFC or CartridgeStrategy.ForceFC1 or CartridgeStrategy.ForceFC2 or CartridgeStrategy.ForceFC3) QueueGCD(BestFatedCircle, Unlocked(AID.FatedCircle) ? Player : primaryTarget?.Actor, GCDPriority.Gauge); @@ -458,14 +477,14 @@ reignStrat is ReignStrategy.ForceLion #region AI var goalST = primaryTarget?.Actor != null ? Hints.GoalSingleTarget(primaryTarget!.Actor, 3) : null; //Set goal for single target - var goalAOE = primaryTarget?.Actor != null ? Hints.GoalAOECircle(5) : null; //Set goal for AOE - var goal = AOEStrategy switch //Set goal based on AOE strategy + var goalAOE = Hints.GoalAOECircle(3); //Set goal for AOE + var goal = strategy.Option(Track.AOE).As() switch //Set goal based on AOE strategy { + AOEStrategy.ForceSTwithO => goalST, //if forced single target AOEStrategy.ForceSTwithoutO => goalST, //if forced single target - AOEStrategy.ForceSTwithO => goalST, //if forced 123 combo - AOEStrategy.ForceAOEwithoutO => goalAOE, //if forced buffs combo - AOEStrategy.ForceAOEwithO => goalAOE, //if forced AOE action - _ => goalST != null && goalAOE != null ? Hints.GoalCombined(goalST, goalAOE, 2) : goalAOE //otherwise, combine goals + AOEStrategy.ForceAOEwithO => goalAOE, //if forced single target + AOEStrategy.ForceAOEwithoutO => goalAOE, //if forced single target + _ => goalST != null ? Hints.GoalCombined(goalST, goalAOE, 3) : goalAOE //otherwise, combine goals }; if (goal != null) //if goal is set Hints.GoalZones.Add(goal); //add goal to zones @@ -519,10 +538,8 @@ reignStrat is ReignStrategy.ForceLion private bool ShouldUseNoMercy(NoMercyStrategy strategy, Actor? target) => strategy switch { NoMercyStrategy.Automatic => Player.InCombat && target != null && canNM && - ((Unlocked(AID.DoubleDown) && (inOdd && Ammo >= 2 || !inOdd && Ammo < 3)) || - (!Unlocked(AID.DoubleDown) && CanQuarterWeaveIn && - ((Unlocked(AID.Bloodfest) && Ammo >= 1) || (!Unlocked(AID.Bloodfest) && canGF) || - !Unlocked(AID.GnashingFang)))), + ((Unlocked(AID.DoubleDown) && (inOdd && Ammo >= 2 || !inOdd && Ammo < 3)) || //90+ + (!Unlocked(AID.DoubleDown) && CanQuarterWeaveIn && Ammo >= 1)), //2-89 NoMercyStrategy.Force => canNM, NoMercyStrategy.ForceW => canNM && CanWeaveIn, NoMercyStrategy.ForceQW => canNM && CanQuarterWeaveIn, @@ -570,42 +587,48 @@ reignStrat is ReignStrategy.ForceLion }; private bool ShouldUseCartridges(CartridgeStrategy strategy, Actor? target) => strategy switch { - CartridgeStrategy.Automatic => ShouldUseFC ? ShouldUseFatedCircle(CartridgeStrategy.Automatic, target) : ShouldUseBurstStrike(CartridgeStrategy.Automatic, target), - CartridgeStrategy.OnlyBS => ShouldUseBurstStrike(CartridgeStrategy.Automatic, target), - CartridgeStrategy.OnlyFC => ShouldUseFatedCircle(CartridgeStrategy.Automatic, target), + CartridgeStrategy.Automatic => ShouldSpendCarts(CartridgeStrategy.Automatic, target), + CartridgeStrategy.OnlyBS => ShouldSpendCarts(CartridgeStrategy.Automatic, target), + CartridgeStrategy.OnlyFC => ShouldSpendCarts(CartridgeStrategy.Automatic, target), CartridgeStrategy.ForceBS => canBS, + CartridgeStrategy.ForceBS1 => canBS && Ammo == 1, + CartridgeStrategy.ForceBS2 => canBS && Ammo == 2, + CartridgeStrategy.ForceBS3 => canBS && Ammo == 3, CartridgeStrategy.ForceFC => canFC, + CartridgeStrategy.ForceFC1 => canFC && Ammo == 1, + CartridgeStrategy.ForceFC2 => canFC && Ammo == 2, + CartridgeStrategy.ForceFC3 => canFC && Ammo == 3, CartridgeStrategy.Conserve => false, _ => false }; - private bool ShouldUseDoubleDown(GCDStrategy strategy, Actor? target) => strategy switch + private bool ShouldUseDoubleDown(DoubleDownStrategy strategy, Actor? target) => strategy switch { - GCDStrategy.Automatic => Player.InCombat && target != null && In5y(target) && canDD && hasNM, - GCDStrategy.Force => canDD, - GCDStrategy.Delay => false, + DoubleDownStrategy.Automatic => Player.InCombat && target != null && In5y(target) && canDD && hasNM, + DoubleDownStrategy.Force => canDD, + DoubleDownStrategy.Force1 => canDD && Ammo == 1, + DoubleDownStrategy.Force2 => canDD && Ammo == 2, + DoubleDownStrategy.Force3 => canDD && Ammo == 3, + DoubleDownStrategy.Delay => false, _ => false }; 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.ForceGnash1 => canGF && Ammo == 1, + GnashingStrategy.ForceGnash2 => canGF && Ammo == 2, + GnashingStrategy.ForceGnash3 => canGF && Ammo == 3, GnashingStrategy.ForceClaw => Player.InCombat && GunComboStep == 1, GnashingStrategy.ForceTalon => Player.InCombat && GunComboStep == 2, GnashingStrategy.Delay => false, _ => false }; - private bool ShouldUseBurstStrike(CartridgeStrategy strategy, Actor? target) => strategy switch - { - CartridgeStrategy.Automatic => Player.InCombat && target != null && In3y(target) && canBS && - (hasNM || (!(bfCD is <= 90 and >= 30) && nmCD < 1 && Ammo == 3)) || - Ammo == MaxCartridges && ComboLastMove is AID.BrutalShell or AID.DemonSlice, - _ => false - }; - private bool ShouldUseFatedCircle(CartridgeStrategy strategy, Actor? target) => strategy switch + private bool ShouldSpendCarts(CartridgeStrategy strategy, Actor? target) => strategy switch { - CartridgeStrategy.Automatic => Player.InCombat && target != null && In5y(target) && canFC && + CartridgeStrategy.Automatic => Player.InCombat && target != null && + ((ShouldUseAOE ? (In5y(target) && canFC) : (In3y(target) && canBS)) && (hasNM || (!(bfCD is <= 90 and >= 30) && nmCD < 1 && Ammo == 3)) || - Ammo == MaxCartridges && ComboLastMove is AID.BrutalShell or AID.DemonSlice, + (Ammo == MaxCartridges && ComboLastMove is AID.BrutalShell or AID.DemonSlice)), _ => false }; private bool ShouldUseSonicBreak(SonicBreakStrategy strategy, Actor? target) => strategy switch diff --git a/BossMod/Autorotation/Standard/akechi/Tank/AkechiPLD.cs b/BossMod/Autorotation/Standard/akechi/Tank/AkechiPLD.cs index 85b9c6458e..d3e671a9b7 100644 --- a/BossMod/Autorotation/Standard/akechi/Tank/AkechiPLD.cs +++ b/BossMod/Autorotation/Standard/akechi/Tank/AkechiPLD.cs @@ -35,7 +35,8 @@ public static RotationModuleDefinition Definition() .AddOption(AOEStrategy.AutoFinishCombo, "Auto (Finish Combo)", "Auto-selects best rotation dependant on targets; Finishes combo first", supportedTargets: ActionTargets.Hostile) .AddOption(AOEStrategy.AutoBreakCombo, "Auto (Break Combo)", "Auto-selects best rotation dependant on targets; Breaks combo if needed", supportedTargets: ActionTargets.Hostile) .AddOption(AOEStrategy.ForceST, "Use AOE", "Force single-target rotation", supportedTargets: ActionTargets.Hostile) - .AddOption(AOEStrategy.ForceAOE, "Force AOE", "Force AOE rotation"); + .AddOption(AOEStrategy.ForceAOE, "Force AOE", "Force AOE rotation") + .AddAssociatedActions(AID.FastBlade, AID.RiotBlade, AID.RageOfHalone, AID.RoyalAuthority, AID.Prominence, AID.TotalEclipse); res.Define(Track.Cooldowns).As("Hold", uiPriority: 190) .AddOption(CooldownStrategy.Allow, "Allow", "Allows the use of all cooldowns & buffs; will use them optimally") .AddOption(CooldownStrategy.Forbid, "Hold", "Forbids the use of all cooldowns & buffs; will not use any actions with a cooldown timer"); @@ -385,12 +386,12 @@ gbStrat is GCDStrategy.Force #region AI var goalST = primaryTarget?.Actor != null ? Hints.GoalSingleTarget(primaryTarget!.Actor, 3) : null; //Set goal for single target - var goalAOE = primaryTarget?.Actor != null ? Hints.GoalAOECircle(5) : null; //Set goal for AOE + var goalAOE = Hints.GoalAOECircle(3); //Set goal for AOE var goal = strategy.Option(Track.AOE).As() switch //Set goal based on AOE strategy { AOEStrategy.ForceST => goalST, //if forced single target - AOEStrategy.ForceAOE => goalAOE, //if forced AOE - _ => goalST != null && goalAOE != null ? Hints.GoalCombined(goalST, goalAOE, 2) : goalAOE //otherwise, combine goals + AOEStrategy.ForceAOE => goalAOE, //if forced single target + _ => goalST != null ? Hints.GoalCombined(goalST, goalAOE, 3) : goalAOE //otherwise, combine goals }; if (goal != null) //if goal is set Hints.GoalZones.Add(goal); //add goal to zones diff --git a/BossMod/Autorotation/Standard/akechi/Tank/AkechiWAR.cs b/BossMod/Autorotation/Standard/akechi/Tank/AkechiWAR.cs index fa04af7635..4fe6f3c4ac 100644 --- a/BossMod/Autorotation/Standard/akechi/Tank/AkechiWAR.cs +++ b/BossMod/Autorotation/Standard/akechi/Tank/AkechiWAR.cs @@ -1,4 +1,4 @@ -using static BossMod.AIHints; +using static BossMod.AIHints; using FFXIVClientStructs.FFXIV.Client.Game.Gauge; using BossMod.WAR; @@ -31,7 +31,8 @@ public static RotationModuleDefinition Definition() BitMask.Build(Class.MRD, Class.WAR), //Job 100); //Level supported - res.DefineShared(); + res.DefineAOE().AddAssociatedActions(AID.HeavySwing, AID.Maim, AID.StormEye, AID.StormPath, AID.Overpower, AID.MythrilTempest); + res.DefineHold(); res.Define(Track.Gauge).As("Gauge", "Gauge", uiPriority: 200) .AddOption(GaugeStrategy.Automatic, "Automatic", "Automatically use Gauge-related abilities optimally", minLevel: 35) .AddOption(GaugeStrategy.OnlyST, "Only ST", "Uses Inner Beast / Fell Cleave / Inner Chaos optimally as Beast Gauge spender only, regardless of targets", 0, 0, ActionTargets.Hostile, 35) @@ -148,13 +149,13 @@ private GCDPriority FellCleave() public enum GCDPriority { None = 0, - Standard = 100, Gauge = 300, - PrimalRuination = 400, DelayFC = 390, + Standard = 400, FlexibleFC = 470, FlexibleIR = 490, PrimalRend = 500, + PrimalRuination = 500, BuffedFC = 550, BuffedIR = 570, NeedTempest = 650, @@ -225,6 +226,8 @@ public enum OGCDPriority #region Module Helpers public bool IsRiskingGauge() { + if (InnerRelease.Stacks > 0) + return true; if (BeastGauge >= 90 && //if 90 ComboLastMove is AID.Maim) //next is Storm's Path, which overcaps. We need spender here return true; @@ -236,7 +239,9 @@ public bool IsRiskingGauge() if (ComboLastMove is AID.HeavySwing) return true; } - + if (NascentChaos.IsActive && //if NC is active + InnerRelease.CD > 5) //and IR is not imminent + return true; return false; } #endregion @@ -334,6 +339,9 @@ public override void Execution(StrategyValues strategy, Enemy? primaryTarget) #endregion ShouldUseAOE = ShouldUseAOECircle(5).OnThreeOrMore; + + BurstWindowLeft = (InnerRelease.CD >= 40) ? 1.0f : 0.0f; + BurstWindowIn = (InnerRelease.CD == 0) ? 1.0f : 0.0f; (BestSplashTargets, NumSplashTargets) = GetBestTarget(primaryTarget, 20, IsSplashTarget); BestSplashTarget = Unlocked(AID.PrimalRend) && NumSplashTargets >= 2 ? BestSplashTargets : primaryTarget; (TwoMinuteLeft, TwoMinuteIn) = EstimateRaidBuffTimings(primaryTarget?.Actor); @@ -342,18 +350,22 @@ public override void Execution(StrategyValues strategy, Enemy? primaryTarget) #region Full Rotation Execution #region Standard Rotations - if (strategy.Automatic()) + if (strategy.AutoFinish()) QueueGCD(BestRotation(), TargetChoice(strategy.Option(SharedTrack.AOE)) ?? primaryTarget?.Actor, - GCDPriority.ForcedCombo); + IsRiskingGauge() ? GCDPriority.Standard - 400 : GCDPriority.Standard); + if (strategy.AutoBreak()) + QueueGCD(ShouldUseAOE ? AOE() : ST(), + TargetChoice(strategy.Option(SharedTrack.AOE)) ?? primaryTarget?.Actor, + IsRiskingGauge() ? GCDPriority.Standard - 400 : GCDPriority.Standard); if (strategy.ForceST()) QueueGCD(ST(), TargetChoice(strategy.Option(SharedTrack.AOE)) ?? primaryTarget?.Actor, - GCDPriority.ForcedCombo); + IsRiskingGauge() ? GCDPriority.Standard - 400 : GCDPriority.ForcedCombo); if (strategy.ForceAOE()) QueueGCD(AOE(), Player, - GCDPriority.ForcedCombo); + IsRiskingGauge() ? GCDPriority.Standard - 400 : GCDPriority.ForcedCombo); #endregion #region Cooldowns @@ -484,12 +496,12 @@ bgStrat is GaugeStrategy.ForceAOE #region AI var goalST = primaryTarget?.Actor != null ? Hints.GoalSingleTarget(primaryTarget!.Actor, 3) : null; //Set goal for single target - var goalAOE = primaryTarget?.Actor != null ? Hints.GoalAOECircle(5) : null; //Set goal for AOE + var goalAOE = Hints.GoalAOECircle(3); //Set goal for AOE var goal = strategy.Option(SharedTrack.AOE).As() switch //Set goal based on AOE strategy { AOEStrategy.ForceST => goalST, //if forced single target AOEStrategy.ForceAOE => goalAOE, //if forced AOE - _ => goalST != null && goalAOE != null ? Hints.GoalCombined(goalST, goalAOE, 2) : goalAOE //otherwise, combine goals + _ => goalST != null ? Hints.GoalCombined(goalST, goalAOE, 3) : goalAOE //otherwise, combine goals }; if (goal != null) //if goal is set Hints.GoalZones.Add(goal); //add goal to zones diff --git a/BossMod/Autorotation/Utility/ClassASTUtility.cs b/BossMod/Autorotation/Utility/ClassASTUtility.cs index b34badf2c7..c8e7b4ff1e 100644 --- a/BossMod/Autorotation/Utility/ClassASTUtility.cs +++ b/BossMod/Autorotation/Utility/ClassASTUtility.cs @@ -33,8 +33,8 @@ public static RotationModuleDefinition Definition() res.Define(Track.EarthlyStar).As("EarthlyStar", "E.Star", 200) //AoE GCD heal, 60s CD, 10s + 10s effect duration .AddOption(StarOption.None, "None", "Do not use automatically") - .AddOption(StarOption.Use, "Earthly Star", "Use Earthly Star", 60, 10, ActionTargets.Hostile, 62) - .AddOption(StarOption.End, "Stellar Detonation", "Use Stellar Detonation", 0, 1, ActionTargets.Hostile, 62) + .AddOption(StarOption.Use, "Earthly Star", "Use Earthly Star", 60, 10, ActionTargets.Area, 62) + .AddOption(StarOption.End, "Stellar Detonation", "Use Stellar Detonation", 0, 1, ActionTargets.Self, 62) .AddAssociatedActions(AST.AID.EarthlyStar, AST.AID.StellarDetonation); DefineSimpleConfig(res, Track.CelestialIntersection, "CelestialIntersection", "C.Inter", 100, AST.AID.CelestialIntersection, 30); //ST oGCD heal/shield, 30s CD (60s Total), 2 charges @@ -83,7 +83,7 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, _ => default }; if (starAction != default) - QueueOGCD(starAction, ResolveTargetOverride(star.Value) ?? primaryTarget ?? Player); + Hints.ActionsToExecute.Push(ActionID.MakeSpell(starAction), null, star.Priority(), star.Value.ExpireIn, targetPos: ResolveTargetLocation(star.Value).ToVec3(Player.PosRot.Y)); //Aspected Helios full execution var heliosUp = StatusDetails(Player, AST.SID.AspectedHelios, Player.InstanceID).Left > 0.1f || StatusDetails(Player, AST.SID.HeliosConjunction, Player.InstanceID).Left > 0.1f; @@ -95,87 +95,39 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, _ => default }; if (heliosAction != default && !heliosUp) - QueueGCD(heliosAction, Player); + Hints.ActionsToExecute.Push(ActionID.MakeSpell(heliosAction), Player, helios.Priority(), helios.Value.ExpireIn); //Horoscope full execution var horo = strategy.Option(Track.Horoscope); + var horoStrat = horo.As() switch + { + HoroscopeOption.Use => Player.FindStatus(AST.SID.Horoscope) == null, + HoroscopeOption.End => Player.FindStatus(AST.SID.Horoscope) != null, + _ => default + }; var horoAction = horo.As() switch { HoroscopeOption.Use => AST.AID.Horoscope, HoroscopeOption.End => AST.AID.HoroscopeEnd, _ => default }; - if (horoAction != default) - QueueOGCD(horoAction, Player); + if (horoStrat != default && horoAction != default) + Hints.ActionsToExecute.Push(ActionID.MakeSpell(horoAction), Player, horo.Priority(), horo.Value.ExpireIn); var cosmos = strategy.Option(Track.Macrocosmos); + var cosmosStrat = cosmos.As() switch + { + MacrocosmosOption.Use => Player.FindStatus(AST.SID.Macrocosmos) == null, + MacrocosmosOption.End => Player.FindStatus(AST.SID.Macrocosmos) != null, + _ => default + }; var cosmosAction = cosmos.As() switch { MacrocosmosOption.Use => AST.AID.Macrocosmos, MacrocosmosOption.End => AST.AID.MicrocosmosEnd, _ => default }; - if (cosmosAction != default) - QueueOGCD(cosmosAction, primaryTarget); + if (cosmosStrat != default) + Hints.ActionsToExecute.Push(ActionID.MakeSpell(cosmosAction), primaryTarget, cosmos.Priority(), cosmos.Value.ExpireIn); } - - #region Core Execution Helpers - - public AST.AID NextGCD; //Next global cooldown action to be used - public void QueueGCD

(AST.AID aid, Actor? target, P priority, float delay = 0) where P : Enum - => QueueGCD(aid, target, (int)(object)priority, delay); - - public void QueueGCD(AST.AID aid, Actor? target, int priority = 8, float delay = 0) - { - var NextGCDPrio = 0; - - if (priority == 0) - return; - - if (QueueAction(aid, target, ActionQueue.Priority.High, delay) && priority > NextGCDPrio) - { - NextGCD = aid; - } - } - - public void QueueOGCD

(AST.AID aid, Actor? target, P priority, float delay = 0) where P : Enum - => QueueOGCD(aid, target, (int)(object)priority, delay); - - public void QueueOGCD(AST.AID aid, Actor? target, int priority = 4, float delay = 0) - { - if (priority == 0) - return; - - QueueAction(aid, target, ActionQueue.Priority.Medium + priority, delay); - } - - public bool QueueAction(AST.AID aid, Actor? target, float priority, float delay) - { - if ((uint)(object)aid == 0) - return false; - - var def = ActionDefinitions.Instance.Spell(aid); - if (def == null) - return false; - - if (def.Range != 0 && target == null) - { - return false; - } - - Vector3 targetPos = default; - - if (def.AllowedTargets.HasFlag(ActionTargets.Area)) - { - if (def.Range == 0) - targetPos = Player.PosRot.XYZ(); - else if (target != null) - targetPos = target.PosRot.XYZ(); - } - - Hints.ActionsToExecute.Push(ActionID.MakeSpell(aid), target, priority, delay: delay, castTime: def.CastTime, targetPos: targetPos); // TODO[cast-time]: this probably needs explicit cast-time argument (adjusted by swiftcast etc) - return true; - } - #endregion - } diff --git a/BossMod/Autorotation/Utility/ClassDRGUtility.cs b/BossMod/Autorotation/Utility/ClassDRGUtility.cs index e4e699f9c1..bc6afba67f 100644 --- a/BossMod/Autorotation/Utility/ClassDRGUtility.cs +++ b/BossMod/Autorotation/Utility/ClassDRGUtility.cs @@ -12,8 +12,8 @@ public static RotationModuleDefinition Definition() var res = new RotationModuleDefinition("Utility: DRG", "Cooldown Planner support for Utility Actions.\nNOTE: This is NOT a rotation preset! All Utility modules are STRICTLY for cooldown-planning usage.", "Utility for planner", "Akechi", RotationModuleQuality.Excellent, BitMask.Build((int)Class.DRG), 100); DefineShared(res, IDLimitBreak3); - res.Define(Track.WingedGlide).As("Winged Glide", "Dash", 20) - .AddOption(DashStrategy.None, "Automatic", "No use.") + res.Define(Track.WingedGlide).As("Winged Glide", "W.Glide", 20) + .AddOption(DashStrategy.None, "None", "No use.") .AddOption(DashStrategy.GapClose, "GapClose", "Use as gapcloser if outside melee range", 60, 0, ActionTargets.Hostile, 45) .AddOption(DashStrategy.GapCloseHold1, "GapCloseHold1", "Use as gapcloser if outside melee range; conserves 1 charge for manual usage", 60, 0, ActionTargets.Hostile, 84) .AddAssociatedActions(DRG.AID.WingedGlide); @@ -27,7 +27,7 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, var dash = strategy.Option(Track.WingedGlide); var dashStrategy = strategy.Option(Track.WingedGlide).As(); - var dashTarget = ResolveTargetOverride(dash.Value); //Smart-Targeting: Target needs to be set in autorotation or CDPlanner to prevent unexpected behavior + var dashTarget = ResolveTargetOverride(dash.Value) ?? primaryTarget; //Smart-Targeting var distance = Player.DistanceToHitbox(dashTarget); var cd = World.Client.Cooldowns[ActionDefinitions.Instance.Spell(DRG.AID.WingedGlide)!.MainCooldownGroup].Remaining; var shouldDash = dashStrategy switch diff --git a/BossMod/Autorotation/Utility/ClassNINUtility.cs b/BossMod/Autorotation/Utility/ClassNINUtility.cs index 997d705905..4b893a82e0 100644 --- a/BossMod/Autorotation/Utility/ClassNINUtility.cs +++ b/BossMod/Autorotation/Utility/ClassNINUtility.cs @@ -16,8 +16,8 @@ public static RotationModuleDefinition Definition() res.Define(Track.Shukuchi).As("Shukuchi", "Dash", 20) .AddOption(DashStrategy.None, "Automatic", "No use.") - .AddOption(DashStrategy.GapClose, "GapClose", "Use as gapcloser if outside melee range", 60, 0, ActionTargets.Hostile, 45) - .AddOption(DashStrategy.GapCloseHold1, "GapCloseHold1", "Use as gapcloser if outside melee range; conserves 1 charge for manual usage", 60, 0, ActionTargets.Hostile, 74) + .AddOption(DashStrategy.GapClose, "GapClose", "Use as gapcloser if outside melee range", 60, 0, ActionTargets.Area, 45) + .AddOption(DashStrategy.GapCloseHold1, "GapCloseHold1", "Use as gapcloser if outside melee range; conserves 1 charge for manual usage", 60, 0, ActionTargets.Area, 74) .AddAssociatedActions(NIN.AID.Shukuchi); return res; @@ -31,17 +31,16 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, // TODO: revise, this doesn't look correct (shukuchi is area targeted, so it should use that; probably should expose options to use regardless of melee distance...) var dash = strategy.Option(Track.Shukuchi); var dashStrategy = strategy.Option(Track.Shukuchi).As(); - var dashTarget = ResolveTargetOverride(dash.Value) ?? primaryTarget; //Smart-Targeting - var distance = Player.DistanceToHitbox(dashTarget); + var distance = Player.DistanceToPoint(ResolveTargetLocation(dash.Value)); var cd = World.Client.Cooldowns[ActionDefinitions.Instance.Spell(NIN.AID.Shukuchi)!.MainCooldownGroup].Remaining; var shouldDash = dashStrategy switch { DashStrategy.None => false, DashStrategy.GapClose => distance is > 3f and <= 20f, - DashStrategy.GapCloseHold1 => distance is > 3f and <= 20f && cd <= 60.5f, // TODO: this condition doesn't look correct... + DashStrategy.GapCloseHold1 => distance is > 3f and <= 20f && cd < 0.6f, _ => false, }; - if (shouldDash && dashTarget != null) - Hints.ActionsToExecute.Push(ActionID.MakeSpell(NIN.AID.Shukuchi), null, dash.Priority(), dash.Value.ExpireIn, 0, 0, dashTarget.PosRot.XYZ()); + if (shouldDash) + Hints.ActionsToExecute.Push(ActionID.MakeSpell(NIN.AID.Shukuchi), null, dash.Priority(), dash.Value.ExpireIn, targetPos: ResolveTargetLocation(dash.Value).ToVec3(Player.PosRot.Y)); } } diff --git a/BossMod/Autorotation/Utility/ClassPLDUtility.cs b/BossMod/Autorotation/Utility/ClassPLDUtility.cs index 624bcc528b..5a472c3dab 100644 --- a/BossMod/Autorotation/Utility/ClassPLDUtility.cs +++ b/BossMod/Autorotation/Utility/ClassPLDUtility.cs @@ -2,7 +2,7 @@ public sealed class ClassPLDUtility(RotationModuleManager manager, Actor player) : RoleTankUtility(manager, player) { - public enum Track { Sheltron = SharedTrack.Count, Sentinel, Cover, Bulwark, DivineVeil, PassageOfArms, HallowedGround } //What we're tracking + public enum Track { Sheltron = SharedTrack.Count, Sentinel, Cover, Bulwark, DivineVeil, HallowedGround } //What we're tracking public enum ShelOption { None, Sheltron, HolySheltron, Intervention } //Sheltron Options public enum SentOption { None, Sentinel, Guardian } //Sentinel enhancement @@ -32,7 +32,6 @@ public static RotationModuleDefinition Definition() DefineSimpleConfig(res, Track.Cover, "Cover", "", 320, PLD.AID.Cover, 12); //120s CD, 12s duration, -50 OathGauge cost DefineSimpleConfig(res, Track.Bulwark, "Bulwark", "Bul", 450, PLD.AID.Bulwark, 10); //90s CD, 15s duration DefineSimpleConfig(res, Track.DivineVeil, "DivineVeil", "Veil", 220, PLD.AID.DivineVeil, 30); //90s CD, 30s duration - DefineSimpleConfig(res, Track.PassageOfArms, "PassageOfArms", "Arms", 470, PLD.AID.PassageOfArms, 3); //120s CD, 18s max duration DefineSimpleConfig(res, Track.HallowedGround, "HallowedGround", "Inv", 400, PLD.AID.HallowedGround, 10); //420s CD, 10s duration return res; @@ -44,7 +43,6 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, ExecuteSimple(strategy.Option(Track.Cover), PLD.AID.Cover, primaryTarget ?? Player); //Cover execution ExecuteSimple(strategy.Option(Track.Bulwark), PLD.AID.Bulwark, Player); //Bulwark execution ExecuteSimple(strategy.Option(Track.DivineVeil), PLD.AID.DivineVeil, Player); //DivineVeil execution - ExecuteSimple(strategy.Option(Track.PassageOfArms), PLD.AID.PassageOfArms, Player); //PassageOfArms execution ExecuteSimple(strategy.Option(Track.HallowedGround), PLD.AID.HallowedGround, Player); //HallowedGround execution var shel = strategy.Option(Track.Sheltron); diff --git a/BossMod/Autorotation/Utility/ClassSCHUtility.cs b/BossMod/Autorotation/Utility/ClassSCHUtility.cs index 2d7bbe67cb..e850fcc360 100644 --- a/BossMod/Autorotation/Utility/ClassSCHUtility.cs +++ b/BossMod/Autorotation/Utility/ClassSCHUtility.cs @@ -2,13 +2,12 @@ public sealed class ClassSCHUtility(RotationModuleManager manager, Actor player) : RoleHealerUtility(manager, player) { - public enum Track { WhisperingDawn = SharedTrack.Count, Adloquium, Succor, FeyIllumination, Lustrate, SacredSoil, Indomitability, DeploymentTactics, EmergencyTactics, Dissipation, Excogitation, Aetherpact, Recitation, FeyBlessing, Consolation, Protraction, Expedient, Seraphism, Summons } + public enum Track { WhisperingDawn = SharedTrack.Count, Adloquium, Succor, FeyIllumination, Lustrate, SacredSoil, Indomitability, DeploymentTactics, EmergencyTactics, Dissipation, Excogitation, Aetherpact, Recitation, FeyBlessing, Consolation, Protraction, Expedient, Seraphism, Seraph } public enum SuccorOption { None, Succor, Concitation } public enum SacredSoilOption { None, Use, UseEx } public enum DeployOption { None, Use, UseEx } public enum AetherpactOption { None, Use, End } public enum RecitationOption { None, Use, UseEx } - public enum PetOption { None, Eos, Seraph } public static readonly ActionID IDLimitBreak3 = ActionID.MakeSpell(SCH.AID.AngelFeathers); @@ -31,8 +30,8 @@ public static RotationModuleDefinition Definition() res.Define(Track.SacredSoil).As("Sacred Soil", "S.Soil", 200) .AddOption(SacredSoilOption.None, "None", "Do not use automatically") - .AddOption(SacredSoilOption.Use, "Use", "Use Sacred Soil", 30, 15, ActionTargets.All, 50, 77) - .AddOption(SacredSoilOption.UseEx, "UseEx", "Use Enhanced Sacred Soil", 30, 15, ActionTargets.All, 78) + .AddOption(SacredSoilOption.Use, "Use", "Use Sacred Soil", 30, 15, ActionTargets.Area, 50, 77) + .AddOption(SacredSoilOption.UseEx, "UseEx", "Use Enhanced Sacred Soil", 30, 15, ActionTargets.Area, 78) .AddAssociatedActions(SCH.AID.SacredSoil); DefineSimpleConfig(res, Track.Indomitability, "Indomitability", "Indom.", 90, SCH.AID.Indomitability); @@ -64,13 +63,7 @@ public static RotationModuleDefinition Definition() DefineSimpleConfig(res, Track.Protraction, "Protraction", "Prot.", 110, SCH.AID.Protraction, 10); DefineSimpleConfig(res, Track.Expedient, "Expedient", "Exped.", 200, SCH.AID.Expedient, 20); DefineSimpleConfig(res, Track.Seraphism, "Seraphism", "Seraphism", 300, SCH.AID.Seraphism, 20); - - // Pet Summons - res.Define(Track.Summons).As("Pet", "", 180) - .AddOption(PetOption.None, "None", "Do not use automatically") - .AddOption(PetOption.Eos, "Eos", "Summon Eos", 2, 0, ActionTargets.Self, 4) - .AddOption(PetOption.Seraph, "Seraph", "Summon Seraph", 120, 22, ActionTargets.Self, 80) - .AddAssociatedActions(SCH.AID.SummonEos, SCH.AID.SummonSeraph); + DefineSimpleConfig(res, Track.Seraph, "Seraph", "Seraph", 300, SCH.AID.SummonSeraph, 20); return res; } @@ -91,6 +84,7 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, ExecuteSimple(strategy.Option(Track.Protraction), SCH.AID.Protraction, Player); ExecuteSimple(strategy.Option(Track.Expedient), SCH.AID.Expedient, Player); ExecuteSimple(strategy.Option(Track.Seraphism), SCH.AID.Seraphism, Player); + ExecuteSimple(strategy.Option(Track.Seraph), SCH.AID.SummonSeraph, Player); var shieldUp = StatusDetails(Player, SCH.SID.Galvanize, Player.InstanceID).Left > 0.1f || StatusDetails(Player, SGE.SID.EukrasianPrognosis, Player.InstanceID).Left > 0.1f; var succ = strategy.Option(Track.Succor); @@ -101,7 +95,7 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, _ => default }; if (succAction != default && !shieldUp) - QueueOGCD(succAction, Player); + Hints.ActionsToExecute.Push(ActionID.MakeSpell(succAction), Player, succ.Priority(), succ.Value.ExpireIn, castTime: 2); // TODO[cast-time]: adjustment (swiftcast etc) var soil = strategy.Option(Track.SacredSoil); var soilAction = soil.As() switch @@ -110,12 +104,11 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, _ => default }; if (soilAction != default) - QueueOGCD(soilAction, ResolveTargetOverride(soil.Value) ?? primaryTarget ?? Player); + Hints.ActionsToExecute.Push(ActionID.MakeSpell(soilAction), null, soil.Priority(), soil.Value.ExpireIn, targetPos: ResolveTargetLocation(soil.Value).ToVec3(Player.PosRot.Y)); var deploy = strategy.Option(Track.DeploymentTactics); if (deploy.As() != DeployOption.None) - QueueOGCD(SCH.AID.DeploymentTactics, Player); - + Hints.ActionsToExecute.Push(ActionID.MakeSpell(SCH.AID.DeploymentTactics), Player, deploy.Priority(), deploy.Value.ExpireIn); var pact = strategy.Option(Track.Aetherpact); var pactStrat = pact.As(); @@ -124,64 +117,13 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, if (pactStrat != AetherpactOption.None) { if (pactStrat == AetherpactOption.Use && !juicing) - QueueOGCD(SCH.AID.Aetherpact, pactTarget); + Hints.ActionsToExecute.Push(ActionID.MakeSpell(SCH.AID.Aetherpact), pactTarget, pact.Priority(), pact.Value.ExpireIn); if (pactStrat == AetherpactOption.End && juicing) - QueueOGCD(SCH.AID.DissolveUnion, pactTarget); + Hints.ActionsToExecute.Push(ActionID.MakeSpell(SCH.AID.DissolveUnion), pactTarget, pact.Priority(), pact.Value.ExpireIn); } var recit = strategy.Option(Track.Recitation); if (recit.As() != RecitationOption.None) - QueueOGCD(SCH.AID.Recitation, Player); - - var pet = strategy.Option(Track.Summons); - var petSummons = pet.As() switch - { - PetOption.Eos => SCH.AID.SummonEos, - PetOption.Seraph => SCH.AID.SummonSeraph, - _ => default - }; - if (petSummons != default) - QueueOGCD(petSummons, Player); - } - - #region Core Execution Helpers - public void QueueOGCD

(SCH.AID aid, Actor? target, P priority, float delay = 0) where P : Enum - => QueueOGCD(aid, target, (int)(object)priority, delay); - - public void QueueOGCD(SCH.AID aid, Actor? target, int priority = 4, float delay = 0) - { - if (priority == 0) - return; - - QueueAction(aid, target, ActionQueue.Priority.Medium + priority, delay); - } - - public bool QueueAction(SCH.AID aid, Actor? target, float priority, float delay) - { - if ((uint)(object)aid == 0) - return false; - - var def = ActionDefinitions.Instance.Spell(aid); - if (def == null) - return false; - - if (def.Range != 0 && target == null) - { - return false; - } - - Vector3 targetPos = default; - - if (def.AllowedTargets.HasFlag(ActionTargets.Area)) - { - if (def.Range == 0) - targetPos = Player.PosRot.XYZ(); - else if (target != null) - targetPos = target.PosRot.XYZ(); - } - - Hints.ActionsToExecute.Push(ActionID.MakeSpell(aid), target, priority, delay: delay, castTime: def.CastTime, targetPos: targetPos); // TODO[cast-time]: this probably needs explicit cast-time argument (adjusted by swiftcast etc) - return true; + Hints.ActionsToExecute.Push(ActionID.MakeSpell(SCH.AID.Recitation), Player, recit.Priority(), recit.Value.ExpireIn); } - #endregion } diff --git a/BossMod/Autorotation/Utility/RolePvPUtility.cs b/BossMod/Autorotation/Utility/RolePvPUtility.cs index f299d119f6..3f67c40baa 100644 --- a/BossMod/Autorotation/Utility/RolePvPUtility.cs +++ b/BossMod/Autorotation/Utility/RolePvPUtility.cs @@ -4,52 +4,11 @@ public sealed class RolePvPUtility(RotationModuleManager manager, Actor player) : RotationModule(manager, player) { - #region Enums: Abilities / Strategies - public enum Track - { - Elixir, - Recuperate, - Guard, - Purify, - Sprint, - } - public enum ElixirStrategy - { - Automatic, - Close, - Mid, - Far, - Force, - Hold - } - - public enum RecuperateStrategy - { - Automatic, - Seventy, - Fifty, - Thirty, - Force, - Hold - } - - public enum GuardStrategy - { - Automatic, - Seventy, - Fifty, - Thirty, - Force, - Hold - } - - public enum DefensiveStrategy - { - Automatic, - Force, - Delay - } - #endregion + public enum Track { Elixir, Recuperate, Guard, Purify, Sprint } + public enum ElixirStrategy { Automatic, Close, Far, Force, Delay } + public enum RecuperateStrategy { Automatic, Seventy, Fifty, Thirty, Force, Delay } + public enum GuardStrategy { Automatic, Seventy, Fifty, Thirty, Force, Delay } + public enum DefensiveStrategy { Automatic, Force, Delay } public static RotationModuleDefinition Definition() { @@ -62,38 +21,37 @@ public static RotationModuleDefinition Definition() Class.BLM, Class.SMN, Class.RDM, Class.PCT), 100, 30); res.Define(Track.Elixir).As("Elixir", uiPriority: 150) - .AddOption(ElixirStrategy.Automatic, "Automatic") - .AddOption(ElixirStrategy.Close, "Close") - .AddOption(ElixirStrategy.Mid, "Mid") - .AddOption(ElixirStrategy.Far, "Far") - .AddOption(ElixirStrategy.Force, "Force") - .AddOption(ElixirStrategy.Hold, "Hold") + .AddOption(ElixirStrategy.Automatic, "Automatic", "Automatically use Elixir when no targets are nearby within 30 yalms") + .AddOption(ElixirStrategy.Close, "Close", "Automatically use Elixir when no targets are nearby within 15 yalms") + .AddOption(ElixirStrategy.Far, "Far", "Automatically use Elixir when no targets are nearby within 45 yalms") + .AddOption(ElixirStrategy.Force, "Force", "Force use Elixir") + .AddOption(ElixirStrategy.Delay, "Delay", "Forbids use of Elixir") .AddAssociatedActions(ClassShared.AID.Elixir); res.Define(Track.Recuperate).As("Recuperate", uiPriority: 150) - .AddOption(RecuperateStrategy.Automatic, "Automatic") - .AddOption(RecuperateStrategy.Seventy, "Seventy") - .AddOption(RecuperateStrategy.Fifty, "Fifty") - .AddOption(RecuperateStrategy.Thirty, "Thirty") - .AddOption(RecuperateStrategy.Force, "Force") - .AddOption(RecuperateStrategy.Hold, "Hold") + .AddOption(RecuperateStrategy.Automatic, "Automatic", "Automatically use Recuperate when HP% is under 40%") + .AddOption(RecuperateStrategy.Seventy, "Seventy", "Automatically use Recuperate when HP% is under 70%") + .AddOption(RecuperateStrategy.Fifty, "Fifty", "Automatically use Recuperate when HP% is under 50%") + .AddOption(RecuperateStrategy.Thirty, "Thirty", "Automatically use Recuperate when HP% is under 30%") + .AddOption(RecuperateStrategy.Force, "Force", "Force use Recuperate") + .AddOption(RecuperateStrategy.Delay, "Delay", "Forbids use of Recuperate") .AddAssociatedActions(ClassShared.AID.Recuperate); res.Define(Track.Guard).As("Guard", uiPriority: 150) - .AddOption(GuardStrategy.Automatic, "Automatic") - .AddOption(GuardStrategy.Seventy, "Seventy") - .AddOption(GuardStrategy.Fifty, "Fifty") - .AddOption(GuardStrategy.Thirty, "Thirty") - .AddOption(GuardStrategy.Force, "Force") - .AddOption(GuardStrategy.Hold, "Hold") + .AddOption(GuardStrategy.Automatic, "Automatic", "Automatically use Guard when HP% is under 35%") + .AddOption(GuardStrategy.Seventy, "Seventy", "Automatically use Guard when HP% is under 70%") + .AddOption(GuardStrategy.Fifty, "Fifty", "Automatically use Guard when HP% is under 50%") + .AddOption(GuardStrategy.Thirty, "Thirty", "Automatically use Guard when HP% is under 30%") + .AddOption(GuardStrategy.Force, "Force", "Force use Guard") + .AddOption(GuardStrategy.Delay, "Delay", "Forbids use of Guard") .AddAssociatedActions(ClassShared.AID.Guard); res.Define(Track.Purify).As("Purify", uiPriority: 150) - .AddOption(DefensiveStrategy.Automatic, "Automatic") - .AddOption(DefensiveStrategy.Force, "Force") - .AddOption(DefensiveStrategy.Delay, "Delay") + .AddOption(DefensiveStrategy.Automatic, "Automatic", "Automatically use Purify when under any debuff that can be cleansed") + .AddOption(DefensiveStrategy.Force, "Force", "Force use Purify") + .AddOption(DefensiveStrategy.Delay, "Delay", "Forbids use of Purify") .AddAssociatedActions(ClassShared.AID.Purify); res.Define(Track.Sprint).As("Sprint", uiPriority: 150) - .AddOption(DefensiveStrategy.Automatic, "Automatic") - .AddOption(DefensiveStrategy.Force, "Force") - .AddOption(DefensiveStrategy.Delay, "Delay") + .AddOption(DefensiveStrategy.Automatic, "Automatic", "Automatically uses Sprint when no target is nearby within 15 yalms") + .AddOption(DefensiveStrategy.Force, "Force", "Force use Sprint") + .AddOption(DefensiveStrategy.Delay, "Delay", "Forbids use of Sprint") .AddAssociatedActions(ClassShared.AID.Sprint); return res; } @@ -116,22 +74,8 @@ public enum OGCDPriority } #endregion - #region Placeholders for Variables - private bool hasSprint; - private bool canElixir; - private bool canRecuperate; - private bool canGuard; - private bool canPurify; - private bool canSprint; - public float GCDLength; - #endregion - #region Module Helpers - 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(ClassShared.AID aid) => World.Client.Cooldowns[ActionDefinitions.Instance.Spell(aid)!.MainCooldownGroup].Remaining < 0.6f; - #endregion + public float PlayerHPP() => (float)Player.HPMP.CurHP / Player.HPMP.MaxHP * 100; public float DebuffsLeft(Actor? target) { return target == null ? 0f @@ -145,23 +89,27 @@ public float DebuffsLeft(Actor? target) ); } public bool HasAnyDebuff(Actor? target) => DebuffsLeft(target) > 0; + private bool IsOffCooldown(ClassShared.AID aid) => World.Client.Cooldowns[ActionDefinitions.Instance.Spell(aid)!.MainCooldownGroup].Remaining < 0.6f; + #endregion + + private bool hasSprint; + private bool canElixir; + private bool canRecuperate; + private bool canGuard; + private bool canPurify; + private bool canSprint; public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, float estimatedAnimLockDelay, bool isMoving) { - #region Variables hasSprint = Player.FindStatus(ClassShared.SID.SprintPvP) != null; - - #region Minimal Requirements - canElixir = IsOffCooldown(ClassShared.AID.Elixir) && strategy.Option(Track.Elixir).As() != ElixirStrategy.Hold; - canRecuperate = Player.HPMP.CurMP >= 2500 && strategy.Option(Track.Recuperate).As() != RecuperateStrategy.Hold; - canGuard = IsOffCooldown(ClassShared.AID.Guard) && strategy.Option(Track.Guard).As() != GuardStrategy.Hold; + canElixir = IsOffCooldown(ClassShared.AID.Elixir) && strategy.Option(Track.Elixir).As() != ElixirStrategy.Delay; + canRecuperate = Player.HPMP.CurMP >= 2500 && strategy.Option(Track.Recuperate).As() != RecuperateStrategy.Delay; + canGuard = IsOffCooldown(ClassShared.AID.Guard) && strategy.Option(Track.Guard).As() != GuardStrategy.Delay; canPurify = IsOffCooldown(ClassShared.AID.Purify) && strategy.Option(Track.Purify).As() != DefensiveStrategy.Delay; canSprint = !hasSprint && strategy.Option(Track.Sprint).As() != DefensiveStrategy.Delay; - #endregion - #endregion var elixirStrat = strategy.Option(Track.Elixir).As(); - if (ShouldUseElixir(elixirStrat, primaryTarget)) + if (ShouldUseElixir(elixirStrat)) Hints.ActionsToExecute.Push(ActionID.MakeSpell(ClassShared.AID.Elixir), Player, strategy.Option(Track.Elixir).Priority(), strategy.Option(Track.Elixir).Value.ExpireIn); var recuperateStrat = strategy.Option(Track.Recuperate).As(); @@ -173,7 +121,7 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, Hints.ActionsToExecute.Push(ActionID.MakeSpell(ClassShared.AID.Guard), Player, strategy.Option(Track.Guard).Priority(), strategy.Option(Track.Guard).Value.ExpireIn); var purifyStrat = strategy.Option(Track.Purify).As(); - if (ShouldUsePurify(purifyStrat, primaryTarget)) + if (ShouldUsePurify(purifyStrat)) Hints.ActionsToExecute.Push(ActionID.MakeSpell(ClassShared.AID.Purify), Player, strategy.Option(Track.Purify).Priority(), strategy.Option(Track.Purify).Value.ExpireIn); var sprintStrat = strategy.Option(Track.Sprint).As(); @@ -181,90 +129,46 @@ public override void Execute(StrategyValues strategy, ref Actor? primaryTarget, Hints.ActionsToExecute.Push(ActionID.MakeSpell(ClassShared.AID.Sprint), Player, strategy.Option(Track.Sprint).Priority(), strategy.Option(Track.Sprint).Value.ExpireIn); } - //TODO: fix this later - #region Core Execution Helpers - /* - public ClassShared.AID NextGCD; //Next global cooldown action to be used - public GCDPriority NextGCDPrio; //Priority of the next GCD for cooldown decision making - - private void QueueGCD(ClassShared.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; - } - } - } - private void QueueOGCD(ClassShared.AID aid, Actor? target, OGCDPriority prio, float basePrio = ActionQueue.Priority.Medium) + public bool ShouldUseElixir(ElixirStrategy strategy) => strategy switch { - 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.Automatic => canElixir && PlayerHPP() <= 60 && Hints.NumPriorityTargetsInAOECircle(Player.Position, 30) == 0, + ElixirStrategy.Close => canElixir && PlayerHPP() <= 60 && Hints.NumPriorityTargetsInAOECircle(Player.Position, 15) == 0, + ElixirStrategy.Far => canElixir && PlayerHPP() <= 60 && Hints.NumPriorityTargetsInAOECircle(Player.Position, 45) == 0, ElixirStrategy.Force => canElixir, - ElixirStrategy.Hold => false, + ElixirStrategy.Delay => 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.Automatic => canRecuperate && PlayerHPP() <= 40, + RecuperateStrategy.Seventy => canRecuperate && PlayerHPP() <= 70, + RecuperateStrategy.Fifty => canRecuperate && PlayerHPP() <= 50, + RecuperateStrategy.Thirty => canRecuperate && PlayerHPP() <= 30, RecuperateStrategy.Force => canRecuperate, - RecuperateStrategy.Hold => false, + RecuperateStrategy.Delay => false, _ => false, }; - public bool ShouldUseGuard(GuardStrategy strategy) => 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.Automatic => canGuard && PlayerHPP() <= 35, + GuardStrategy.Seventy => canGuard && PlayerHPP() <= 70, + GuardStrategy.Fifty => canGuard && PlayerHPP() <= 50, + GuardStrategy.Thirty => canGuard && PlayerHPP() <= 30, GuardStrategy.Force => canGuard, - GuardStrategy.Hold => false, + GuardStrategy.Delay => false, _ => false, }; - - public bool ShouldUsePurify(DefensiveStrategy strategy, Actor? target) => strategy switch + public bool ShouldUsePurify(DefensiveStrategy strategy) => strategy switch { - DefensiveStrategy.Automatic => - canPurify && - HasAnyDebuff(target), + DefensiveStrategy.Automatic => canPurify && HasAnyDebuff(Player), DefensiveStrategy.Force => canPurify, DefensiveStrategy.Delay => false, _ => false, }; - public bool ShouldUseSprint(DefensiveStrategy strategy) => strategy switch { - DefensiveStrategy.Automatic => - !Player.InCombat && - canSprint, - DefensiveStrategy.Force => true, + DefensiveStrategy.Automatic => Hints.NumPriorityTargetsInAOECircle(Player.Position, 15) == 0 && canSprint, + DefensiveStrategy.Force => canSprint, DefensiveStrategy.Delay => false, _ => false, }; diff --git a/BossMod/Data/Actor.cs b/BossMod/Data/Actor.cs index c82d118c7a..765651add6 100644 --- a/BossMod/Data/Actor.cs +++ b/BossMod/Data/Actor.cs @@ -171,6 +171,7 @@ public sealed class Actor(ulong instanceID, uint oid, int spawnIndex, string nam public Angle AngleTo(Actor other) => Angle.FromDirection(other.Position - Position); public float DistanceToHitbox(Actor? other) => other == null ? float.MaxValue : (other.Position - Position).Length() - other.HitboxRadius - HitboxRadius; + public float DistanceToPoint(WPos pos) => (pos - Position).Length(); public override string ToString() => $"{OID:X} '{Name}' <{InstanceID:X}>"; }