diff --git a/XIVSlothCombo/Combos/CustomComboPreset.cs b/XIVSlothCombo/Combos/CustomComboPreset.cs index 3c3ab6606..dc4e3eb8c 100644 --- a/XIVSlothCombo/Combos/CustomComboPreset.cs +++ b/XIVSlothCombo/Combos/CustomComboPreset.cs @@ -685,9 +685,9 @@ public enum CustomComboPreset [CustomComboInfo("Simple Raging Jaws Option", "Enable the snapshotting of DoTs, within the remaining time of Raging Strikes below:", BRD.JobID)] BRD_Simple_RagingJaws = 3025, - [ParentCombo(BRD_AoE_Simple_Songs)] - [CustomComboInfo("Exclude Wanderer's Minuet Option", "Dont use Wanderer's Minuet.", BRD.JobID)] - BRD_AoE_Simple_SongsExcludeWM = 3027, + //[ParentCombo(BRD_AoE_Simple_Songs)] + //[CustomComboInfo("Exclude Wanderer's Minuet Option", "Dont use Wanderer's Minuet.", BRD.JobID)] + //BRD_AoE_Simple_SongsExcludeWM = 3027, [ParentCombo(BRD_ST_SimpleMode)] [CustomComboInfo("Second Wind Option", "Uses Second Wind when below set HP percentage.", BRD.JobID)] @@ -707,7 +707,14 @@ public enum CustomComboPreset [CustomComboInfo("Cure Option", "Use Variant Cure when HP is below set threshold.", BRD.JobID)] BRD_Variant_Cure = 3031, - // Last value = 3031 + [ParentCombo(BRD_AoE_Simple_Songs)] + [CustomComboInfo("Simple AoE Buffs Option", "Adds buffs onto the Simple AoE Bard feature.", BRD.JobID)] + BRD_AoE_Simple_Buffs = 3032, + + [ParentCombo(BRD_AoE_SimpleMode)] + [CustomComboInfo("Simple AoE No Waste Option", "Adds enemy health checking on targetted mob for songs.\nThey will not be reapplied if less than specified.", BRD.JobID)] + BRD_AoE_Simple_NoWaste = 3033, + // Last value = 3033 #endregion diff --git a/XIVSlothCombo/Combos/PvE/BRD.cs b/XIVSlothCombo/Combos/PvE/BRD.cs index 68fa8c43d..d5570a874 100644 --- a/XIVSlothCombo/Combos/PvE/BRD.cs +++ b/XIVSlothCombo/Combos/PvE/BRD.cs @@ -6,6 +6,7 @@ using XIVSlothCombo.Combos.PvE.Content; using XIVSlothCombo.Core; using XIVSlothCombo.CustomComboNS; +using static FFXIVClientStructs.FFXIV.Client.UI.AddonJobHudBRD0; namespace XIVSlothCombo.Combos.PvE { @@ -78,6 +79,7 @@ public static class Config public const string BRD_RagingJawsRenewTime = "ragingJawsRenewTime", BRD_NoWasteHPPercentage = "noWasteHpPercentage", + BRD_AoENoWasteHPPercentage = "AoENoWasteHpPercentage", BRD_STSecondWindThreshold = "BRD_STSecondWindThreshold", BRD_AoESecondWindThreshold = "BRD_AoESecondWindThreshold", BRD_VariantCure = "BRD_VariantCure"; @@ -321,6 +323,9 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim internal class BRD_AoE_SimpleMode : CustomCombo { + internal static bool inOpener = false; + internal static bool openerFinished = false; + protected internal override CustomComboPreset Preset { get; } = CustomComboPreset.BRD_AoE_SimpleMode; protected override uint Invoke(uint actionID, uint lastComboMove, float comboTime, byte level) @@ -329,6 +334,15 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim { BRDGauge? gauge = GetJobGauge(); bool canWeave = CanWeave(actionID); + bool canWeaveBuffs = CanWeave(actionID, 0.6); + bool canWeaveDelayed = CanDelayedWeave(actionID, 0.9); + int songTimerInSeconds = gauge.SongTimer / 1000; + bool songNone = gauge.Song == Song.NONE; + bool songWanderer = gauge.Song == Song.WANDERER; + bool songMage = gauge.Song == Song.MAGE; + bool songArmy = gauge.Song == Song.ARMY; + int targetHPThreshold = PluginConfiguration.GetCustomIntValue(Config.BRD_AoENoWasteHPPercentage); + bool isEnemyHealthHigh = !IsEnabled(CustomComboPreset.BRD_AoE_Simple_NoWaste) || GetTargetHPPercent() > targetHPThreshold; if (IsEnabled(CustomComboPreset.BRD_Variant_Cure) && IsEnabled(Variant.VariantCure) && PlayerHealthPercentageHp() <= GetOptionValue(Config.BRD_VariantCure)) return Variant.VariantCure; @@ -341,47 +355,188 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim if (IsEnabled(CustomComboPreset.BRD_AoE_Simple_Songs) && canWeave) { - int songTimerInSeconds = gauge.SongTimer / 1000; - bool songNone = gauge.Song == Song.NONE; - if (songTimerInSeconds < 3 || songNone) + // Limit optimisation to when you are high enough level to benefit from it. + if (LevelChecked(WanderersMinuet)) { - if (LevelChecked(WanderersMinuet) && IsOffCooldown(WanderersMinuet) && - !(JustUsed(MagesBallad) || JustUsed(ArmysPaeon)) && - !IsEnabled(CustomComboPreset.BRD_AoE_Simple_SongsExcludeWM)) - return WanderersMinuet; + bool minuetReady = IsOffCooldown(WanderersMinuet); + bool balladReady = IsOffCooldown(MagesBallad); + bool paeonReady = IsOffCooldown(ArmysPaeon); + bool empyrealReady = LevelChecked(EmpyrealArrow) && IsOffCooldown(EmpyrealArrow); - if (LevelChecked(MagesBallad) && IsOffCooldown(MagesBallad) && - !(JustUsed(WanderersMinuet) || JustUsed(ArmysPaeon))) - return MagesBallad; + if (canWeave) + { + if (songNone) + { + // Logic to determine first song + if (minuetReady && !(JustUsed(MagesBallad) || JustUsed(ArmysPaeon))) + return WanderersMinuet; + if (balladReady && !(JustUsed(WanderersMinuet) || JustUsed(ArmysPaeon))) + return MagesBallad; + if (paeonReady && !(JustUsed(MagesBallad) || JustUsed(WanderersMinuet))) + return ArmysPaeon; + } - if (LevelChecked(ArmysPaeon) && IsOffCooldown(ArmysPaeon) && - !(JustUsed(MagesBallad) || JustUsed(WanderersMinuet))) + if (songWanderer) + { + if (songTimerInSeconds <= 3 && gauge.Repertoire > 0) // Spend any repertoire before switching to next song + return OriginalHook(WanderersMinuet); + if (songTimerInSeconds <= 3 && balladReady) // Move to Mage's Ballad if <= 3 seconds left on song + return MagesBallad; + } + + if (songMage) + { + + // Move to Army's Paeon if < 3 seconds left on song + if (songTimerInSeconds <= 3 && paeonReady) + { + // Special case for Empyreal Arrow: it must be cast before you change to it to avoid drift! + if (empyrealReady) + return EmpyrealArrow; + return ArmysPaeon; + } + } + } + + if (songArmy && canWeaveDelayed) + { + // Move to Wanderer's Minuet if <= 12 seconds left on song or WM off CD and have 4 repertoires of AP + if (songTimerInSeconds <= 12 || (minuetReady && gauge.Repertoire == 4)) + return WanderersMinuet; + } + } + else if (songTimerInSeconds <= 3 && canWeave) + { + bool balladReady = LevelChecked(MagesBallad) && IsOffCooldown(MagesBallad); + bool paeonReady = LevelChecked(ArmysPaeon) && IsOffCooldown(ArmysPaeon); + + if (balladReady) + return MagesBallad; + if (paeonReady) return ArmysPaeon; } } + if (IsEnabled(CustomComboPreset.BRD_AoE_Simple_Buffs) && (!songNone || !LevelChecked(MagesBallad)) && isEnemyHealthHigh) + { + bool radiantReady = LevelChecked(RadiantFinale) && IsOffCooldown(RadiantFinale); + bool ragingReady = LevelChecked(RagingStrikes) && IsOffCooldown(RagingStrikes); + bool battleVoiceReady = LevelChecked(BattleVoice) && IsOffCooldown(BattleVoice); + bool barrageReady = LevelChecked(Barrage) && IsOffCooldown(Barrage); + bool firstMinute = CombatEngageDuration().Minutes == 0; + bool restOfFight = CombatEngageDuration().Minutes > 0; + + if (!openerFinished && (!JustUsed(WanderersMinuet) || HasEffect(Buffs.BattleVoice))) + { + if (ragingReady && ((canWeaveBuffs && firstMinute) || (canWeaveDelayed && restOfFight)) && + (GetCooldownElapsed(BattleVoice) >= 2.3 || battleVoiceReady || !LevelChecked(BattleVoice))) + return RagingStrikes; + } + else if (openerFinished) + { + if (ragingReady && ((canWeaveBuffs && firstMinute) || (canWeaveDelayed && restOfFight)) && + (HasEffect(Buffs.BattleVoice) || battleVoiceReady || !LevelChecked(BattleVoice))) + return RagingStrikes; + + } + + if (canWeaveBuffs && barrageReady && !HasEffect(Buffs.HawksEye) && HasEffect(Buffs.RagingStrikes)) + { + if (LevelChecked(RadiantFinale) && HasEffect(Buffs.RadiantFinale)) + return Barrage; + else if (LevelChecked(BattleVoice) && HasEffect(Buffs.BattleVoice)) + return Barrage; + else if (!LevelChecked(BattleVoice) && HasEffect(Buffs.RagingStrikes)) + return Barrage; + } + + if (canWeaveBuffs && IsEnabled(CustomComboPreset.BRD_Simple_BuffsRadiant) && radiantReady && + (Array.TrueForAll(gauge.Coda, SongIsNotNone) || Array.Exists(gauge.Coda, SongIsWandererMinuet)) && + (battleVoiceReady || GetCooldownRemainingTime(BattleVoice) < 0.7) && + (GetBuffRemainingTime(Buffs.RagingStrikes) <= 16.5 || openerFinished)) + { + if (!JustUsed(RagingStrikes)) + return RadiantFinale; + } + + if (canWeaveBuffs && battleVoiceReady && + ((GetBuffRemainingTime(Buffs.RagingStrikes) <= 16.5 || GetBuffRemainingTime(Buffs.RadiantFinale) <= 16.5) || openerFinished) && (IsOnCooldown(RagingStrikes) || IsOnCooldown(RadiantFinale))) + { + if (!JustUsed(RagingStrikes)) + return BattleVoice; + } + + } + + if (HasEffect(Buffs.RadiantEncoreReady) && !JustUsed(RadiantFinale) && GetCooldownElapsed(BattleVoice) >= 4.2f) + return OriginalHook(RadiantEncore); + if (canWeave) { - bool songWanderer = gauge.Song == Song.WANDERER; bool empyrealReady = LevelChecked(EmpyrealArrow) && IsOffCooldown(EmpyrealArrow); - bool rainOfDeathReady = LevelChecked(RainOfDeath) && GetRemainingCharges(RainOfDeath) > 0; bool sidewinderReady = LevelChecked(Sidewinder) && IsOffCooldown(Sidewinder); - bool barrageReady = LevelChecked(Shadowbite) && IsOffCooldown(Barrage); // Adding Barrage to AoE, but only if we can use Shadowbite. + float battleVoiceCD = GetCooldownRemainingTime(BattleVoice); + float empyrealCD = GetCooldownRemainingTime(EmpyrealArrow); + float ragingCD = GetCooldownRemainingTime(RagingStrikes); + float radiantCD = GetCooldownRemainingTime(RadiantFinale); - if (LevelChecked(PitchPerfect) && songWanderer && gauge.Repertoire == 3) - return OriginalHook(WanderersMinuet); - if (empyrealReady) + if (empyrealReady && ((!openerFinished && IsOnCooldown(RagingStrikes) || (!openerFinished && JustUsed(WanderersMinuet)) || (openerFinished && battleVoiceCD >= 3.5) || !IsEnabled(CustomComboPreset.BRD_Simple_Buffs)))) return EmpyrealArrow; - if (rainOfDeathReady) - return RainOfDeath; - if (sidewinderReady) - return Sidewinder; - if (barrageReady) - return Barrage; + + if (LevelChecked(PitchPerfect) && songWanderer && + (gauge.Repertoire == 3 || (gauge.Repertoire == 2 && empyrealCD < 2)) && + ((!openerFinished && IsOnCooldown(RagingStrikes)) || (openerFinished && battleVoiceCD >= 3.5))) + return OriginalHook(PitchPerfect); + + if (sidewinderReady && ((!openerFinished && IsOnCooldown(RagingStrikes)) || (openerFinished && battleVoiceCD >= 3.5) || !IsEnabled(CustomComboPreset.BRD_AoE_Simple_Songs))) + { + if (IsEnabled(CustomComboPreset.BRD_Simple_Pooling)) + { + if (songWanderer) + { + if ((HasEffect(Buffs.RagingStrikes) || ragingCD > 10) && + (HasEffect(Buffs.BattleVoice) || battleVoiceCD > 10) && + (HasEffect(Buffs.RadiantFinale) || radiantCD > 10 || + !LevelChecked(RadiantFinale))) + return Sidewinder; + } + else return Sidewinder; + } + else return Sidewinder; + } + + + if (LevelChecked(Bloodletter) && ((!openerFinished && IsOnCooldown(RagingStrikes)) || openerFinished)) + { + uint rainOfDeathCharges = GetRemainingCharges(RainOfDeath); + + if (IsEnabled(CustomComboPreset.BRD_Simple_Pooling) && LevelChecked(WanderersMinuet)) + { + if (songWanderer) + { + if (((HasEffect(Buffs.RagingStrikes) || ragingCD > 10) && + (HasEffect(Buffs.BattleVoice) || battleVoiceCD > 10 || + !LevelChecked(BattleVoice)) && + (HasEffect(Buffs.RadiantFinale) || radiantCD > 10 || + !LevelChecked(RadiantFinale)) && + rainOfDeathCharges > 0) || rainOfDeathCharges > 2) + return OriginalHook(RainOfDeath); + } + + if (songArmy && (rainOfDeathCharges == 3 || ((gauge.SongTimer / 1000) > 30 && rainOfDeathCharges > 0))) + return OriginalHook(RainOfDeath); + if (songMage && rainOfDeathCharges > 0) + return OriginalHook(RainOfDeath); + if (songNone && rainOfDeathCharges == 3) + return OriginalHook(RainOfDeath); + } + else if (rainOfDeathCharges > 0) + return OriginalHook(RainOfDeath); + } // healing - please move if not appropriate priority - if (IsEnabled(CustomComboPreset.BRD_AoE_SecondWind)) + if (IsEnabled(CustomComboPreset.BRD_ST_SecondWind)) { if (PlayerHealthPercentageHp() <= PluginConfiguration.GetCustomIntValue(Config.BRD_AoESecondWindThreshold) && ActionReady(All.SecondWind)) return All.SecondWind; @@ -401,7 +556,7 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim if (resonantArrowReady) return ResonantArrow; if (HasEffect(Buffs.RadiantEncoreReady)) - return RadiantEncore; + return OriginalHook(RadiantEncore); } @@ -557,17 +712,17 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim if (songWanderer) { - if (songTimerInSeconds < 3 && gauge.Repertoire > 0) // Spend any repertoire before switching to next song + if (songTimerInSeconds <= 3 && gauge.Repertoire > 0) // Spend any repertoire before switching to next song return OriginalHook(WanderersMinuet); - if (songTimerInSeconds < 3 && balladReady) // Move to Mage's Ballad if < 3 seconds left on song + if (songTimerInSeconds <= 3 && balladReady) // Move to Mage's Ballad if <= 3 seconds left on song return MagesBallad; } if (songMage) { - // Move to Army's Paeon if < 12 seconds left on song - if (songTimerInSeconds < 12 && paeonReady) + // Move to Army's Paeon if <= 3 seconds left on song + if (songTimerInSeconds <= 3 && paeonReady) { // Special case for Empyreal Arrow: it must be cast before you change to it to avoid drift! if (empyrealReady) @@ -579,12 +734,12 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim if (songArmy && canWeaveDelayed) { - // Move to Wanderer's Minuet if < 3 seconds left on song or WM off CD and have 4 repertoires of AP - if (songTimerInSeconds < 3 || (minuetReady && gauge.Repertoire == 4)) + // Move to Wanderer's Minuet if <= 12 seconds left on song or WM off CD and have 4 repertoires of AP + if (songTimerInSeconds <= 12 || (minuetReady && gauge.Repertoire == 4)) return WanderersMinuet; } } - else if (songTimerInSeconds < 3 && canWeave) + else if (songTimerInSeconds <= 3 && canWeave) { bool balladReady = LevelChecked(MagesBallad) && IsOffCooldown(MagesBallad); bool paeonReady = LevelChecked(ArmysPaeon) && IsOffCooldown(ArmysPaeon); @@ -648,7 +803,7 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim } if (HasEffect(Buffs.RadiantEncoreReady) && !JustUsed(RadiantFinale) && GetCooldownElapsed(BattleVoice) >= 4.2f) - return RadiantEncore; + return OriginalHook(RadiantEncore); if (canWeave) { @@ -751,7 +906,7 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim HasEffect(Buffs.RagingStrikes) && ragingStrikesDuration < ragingJawsRenewTime) && HasEffect(Buffs.RadiantFinale) && radiantFinaleDuration < 4 && poisonRecast(40) && windRecast(40)); - + if (!LevelChecked(Stormbite)) { @@ -869,10 +1024,10 @@ protected override uint Invoke(uint actionID, uint lastComboMove, float comboTim bool magesBalladReady = LevelChecked(MagesBallad) && IsOffCooldown(MagesBallad); bool armysPaeonReady = LevelChecked(ArmysPaeon) && IsOffCooldown(ArmysPaeon); - if (wanderersMinuetReady || (gauge.Song == Song.WANDERER && songTimerInSeconds > 2)) + if (wanderersMinuetReady || (gauge.Song == Song.WANDERER && songTimerInSeconds > 11)) return WanderersMinuet; - if (magesBalladReady || (gauge.Song == Song.MAGE && songTimerInSeconds > 11)) + if (magesBalladReady || (gauge.Song == Song.MAGE && songTimerInSeconds > 2)) return MagesBallad; if (armysPaeonReady || (gauge.Song == Song.ARMY && songTimerInSeconds > 2)) diff --git a/XIVSlothCombo/Window/Functions/UserConfig.cs b/XIVSlothCombo/Window/Functions/UserConfig.cs index 47cce6015..832556e5d 100644 --- a/XIVSlothCombo/Window/Functions/UserConfig.cs +++ b/XIVSlothCombo/Window/Functions/UserConfig.cs @@ -1391,6 +1391,9 @@ internal static void Draw(CustomComboPreset preset, bool enabled) if (preset == CustomComboPreset.BRD_Simple_NoWaste) UserConfig.DrawSliderInt(1, 10, BRD.Config.BRD_NoWasteHPPercentage, "Remaining target HP percentage"); + if (preset == CustomComboPreset.BRD_AoE_Simple_NoWaste) + UserConfig.DrawSliderInt(1, 10, BRD.Config.BRD_AoENoWasteHPPercentage, "Remaining target HP percentage"); + if (preset == CustomComboPreset.BRD_ST_SecondWind) UserConfig.DrawSliderInt(0, 100, BRD.Config.BRD_STSecondWindThreshold, "HP percent threshold to use Second Wind below.", 150, SliderIncrements.Ones);