--- Localize Vars -- Addon local addonName, addonTable = ... -- HeroDBC local DBC = HeroDBC.DBC -- HeroLib local HL = HeroLib local Cache = HeroCache local Unit = HL.Unit local Player = Unit.Player local Target = Unit.Target local Spell = HL.Spell local MultiSpell = HL.MultiSpell local Item = HL.Item local BoolToInt = HL.Utils.BoolToInt -- HeroRotation local HR = HeroRotation local AoEON = HR.AoEON local CDsON = HR.CDsON -- Num/Bool Helper Functions local num = HR.Commons.Everyone.num local bool = HR.Commons.Everyone.bool -- Lua local pairs = pairs local tableinsert = table.insert local mathmin = math.min local mathmax = math.max local mathabs = math.abs --- APL Local Vars -- Commons local Everyone = HR.Commons.Everyone local Rogue = HR.Commons.Rogue -- Define S/I for spell and item arrays local S = Spell.Rogue.Subtlety local I = Item.Rogue.Subtlety -- Create table to exclude above trinkets from On Use function local OnUseExcludes = { I.ManicGrieftorch:ID(), I.BeaconToTheBeyond:ID(), } -- Rotation Var local MeleeRange, AoERange, TargetInMeleeRange, TargetInAoERange local Enemies30y, MeleeEnemies10y, MeleeEnemies10yCount, MeleeEnemies5y local ShouldReturn; -- Used to get the return string local PoolingAbility, PoolingEnergy, PoolingFinisher; -- Used to store an ability we might want to pool for as a fallback in the current situation local RuptureThreshold, RuptureDMGThreshold local EffectiveComboPoints, ComboPoints, ComboPointsDeficit, StealthEnergyRequired local PriorityRotation S.Eviscerate:RegisterDamageFormula( -- Eviscerate DMG Formula (Pre-Mitigation): --- Player Modifier -- AP * CP * EviscR1_APCoef * Aura_M * NS_M * DS_M * DSh_M * SoD_M * Finality_M * Mastery_M * Versa_M --- Target Modifier -- EviscR2_M * Sinful_M function () return --- Player Modifier -- Attack Power Player:AttackPowerDamageMod() * -- Combo Points EffectiveComboPoints * -- Eviscerate R1 AP Coef 0.176 * -- Aura Multiplier (SpellID: 137035) 1.21 * -- Nightstalker Multiplier (S.Nightstalker:IsAvailable() and Player:StealthUp(true, false) and 1.08 or 1) * -- Deeper Stratagem Multiplier (S.DeeperStratagem:IsAvailable() and 1.05 or 1) * -- Shadow Dance Multiplier (S.DarkShadow:IsAvailable() and Player:BuffUp(S.ShadowDanceBuff) and 1.3 or 1) * -- Symbols of Death Multiplier (Player:BuffUp(S.SymbolsofDeath) and 1.1 or 1) * -- Finality Multiplier (Player:BuffUp(S.FinalityEviscerateBuff) and 1.3 or 1) * -- Mastery Finisher Multiplier (1 + Player:MasteryPct() / 100) * -- Versatility Damage Multiplier (1 + Player:VersatilityDmgPct() / 100) * --- Target Modifier -- Eviscerate R2 Multiplier (Target:DebuffUp(S.FindWeaknessDebuff) and 1.5 or 1) end ) S.Rupture:RegisterPMultiplier( function () return Player:BuffUp(S.FinalityRuptureBuff) and 1.3 or 1 end ) -- GUI Settings local Settings = { General = HR.GUISettings.General, Commons = HR.GUISettings.APL.Rogue.Commons, Commons2 = HR.GUISettings.APL.Rogue.Commons2, Subtlety = HR.GUISettings.APL.Rogue.Subtlety } local function SetPoolingAbility(PoolingSpell, EnergyThreshold) if not PoolingAbility then PoolingAbility = PoolingSpell PoolingEnergy = EnergyThreshold or 0 end end local function SetPoolingFinisher(PoolingSpell) if not PoolingFinisher then PoolingFinisher = PoolingSpell end end local function MayBurnShadowDance() if Settings.Subtlety.BurnShadowDance == "On Bosses not in Dungeons" and Player:IsInDungeonArea() then return false elseif Settings.Subtlety.BurnShadowDance ~= "Always" and not Target:IsInBossList() then return false else return true end end local function UsePriorityRotation() if MeleeEnemies10yCount < 2 then return false elseif Settings.Commons.UsePriorityRotation == "Always" then return true elseif Settings.Commons.UsePriorityRotation == "On Bosses" and Target:IsInBossList() then return true elseif Settings.Commons.UsePriorityRotation == "Auto" then -- Zul Mythic if Player:InstanceDifficulty() == 16 and Target:NPCID() == 138967 then return true -- Council of Blood elseif Target:NPCID() == 166969 or Target:NPCID() == 166971 or Target:NPCID() == 166970 then return true -- Anduin (Remnant of a Fallen King/Monstrous Soul) elseif Target:NPCID() == 183463 or Target:NPCID() == 183671 then return true end end return false end -- Handle CastLeftNameplate Suggestions for DoT Spells local function SuggestCycleDoT(DoTSpell, DoTEvaluation, DoTMinTTD, Enemies) -- Prefer melee cycle units local BestUnit, BestUnitTTD = nil, DoTMinTTD local TargetGUID = Target:GUID() for _, CycleUnit in pairs(Enemies) do if CycleUnit:GUID() ~= TargetGUID and Everyone.UnitIsCycleValid(CycleUnit, BestUnitTTD, -CycleUnit:DebuffRemains(DoTSpell)) and DoTEvaluation(CycleUnit) then BestUnit, BestUnitTTD = CycleUnit, CycleUnit:TimeToDie() end end if BestUnit then HR.CastLeftNameplate(BestUnit, DoTSpell) -- Check ranged units next, if the RangedMultiDoT option is enabled elseif Settings.Commons.RangedMultiDoT then BestUnit, BestUnitTTD = nil, DoTMinTTD for _, CycleUnit in pairs(MeleeEnemies10y) do if CycleUnit:GUID() ~= TargetGUID and Everyone.UnitIsCycleValid(CycleUnit, BestUnitTTD, -CycleUnit:DebuffRemains(DoTSpell)) and DoTEvaluation(CycleUnit) then BestUnit, BestUnitTTD = CycleUnit, CycleUnit:TimeToDie() end end if BestUnit then HR.CastLeftNameplate(BestUnit, DoTSpell) end end end -- APL Action Lists (and Variables) local function Stealth_Threshold () -- actions+=/variable,name=stealth_threshold,value=25+talent.vigor.enabled*20+talent.master_of_shadows.enabled*20+talent.shadow_focus.enabled*25+talent.alacrity.enabled*20+25*(spell_targets.shuriken_storm>=4) return 25 + num(S.Vigor:IsAvailable()) * 20 + num(S.MasterofShadows:IsAvailable()) * 20 + num(S.ShadowFocus:IsAvailable()) * 25 + num(S.Alacrity:IsAvailable()) * 20 + num(MeleeEnemies10yCount >= 4) * 25 end local function ShD_Threshold () -- actions.stealth_cds=variable,name=shd_threshold,value=cooldown.shadow_dance.charges_fractional>=0.75+talent.shadow_dance return S.ShadowDance:ChargesFractional() >= 0.75 + BoolToInt(S.ShadowDanceTalent:IsAvailable()) end local function ShD_Combo_Points () -- actions.stealth_cds+=/variable,name=shd_combo_points,value=combo_points<=1 -- actions.stealth_cds+=/variable,name=shd_combo_points,value=combo_points.deficit<=1,if=spell_targets.shuriken_storm>(4-2*talent.shuriken_tornado.enabled)|variable.priority_rotation&spell_targets.shuriken_storm>=4 -- actions.stealth_cds+=/variable,name=shd_combo_points,value=1,if=spell_targets.shuriken_storm=4 if MeleeEnemies10yCount == (4 - num(S.SealFate:IsAvailable())) then return true elseif MeleeEnemies10yCount > (4 - 2 * BoolToInt(S.ShurikenTornado:IsAvailable())) or PriorityRotation and MeleeEnemies10yCount >= 4 then return ComboPointsDeficit <= 1 else return ComboPoints <= 1 end end local function SnD_Condition () -- actions+=/variable,name=snd_condition,value=buff.slice_and_dice.up|spell_targets.shuriken_storm>=cp_max_spend return Player:BuffUp(S.SliceandDice) or MeleeEnemies10yCount >= Rogue.CPMaxSpend() end local function Skip_Rupture (ShadowDanceBuff) -- actions.finish+=/variable,name=skip_rupture,value=buff.thistle_tea.up&spell_targets.shuriken_storm=1|buff.shadow_dance.up&(spell_targets.shuriken_storm=1|dot.rupture.ticking&spell_targets.shuriken_storm>=2) return Player:BuffUp(S.ThistleTea) and MeleeEnemies10yCount == 1 or ShadowDanceBuff and (MeleeEnemies10yCount == 1 or Target:DebuffUp(S.Rupture) and MeleeEnemies10yCount >= 2) end local function Rotten_Condition () -- variable,name=rotten_condition,value=!buff.premeditation.up&spell_targets.shuriken_storm=1|!talent.the_rotten|spell_targets.shuriken_storm>1 return Player:BuffDown(S.PremeditationBuff) and MeleeEnemies10yCount == 1 or not S.TheRotten:IsAvailable() or MeleeEnemies10yCount > 1 end local function Rotten_Threshold () -- variable,name=rotten_threshold,value=!buff.the_rotten.up|spell_targets.shuriken_storm>1|combo_points<=2&buff.the_rotten.up&!set_bonus.tier30_2pc return not Player:BuffUp(S.TheRottenBuff) or MeleeEnemies10yCount > 1 or (ComboPoints <= 2 and Player:BuffUp(S.TheRottenBuff) and not Player:HasTier(30, 2)) end local function Secret_Condition(ShadowDanceBuff, PremeditationBuff) -- actions.finish=variable,name=secret_condition,value=buff.shadow_dance.up&(buff.danse_macabre.stack>=3|!talent.danse_macabre)&(!buff.premeditation.up|spell_targets.shuriken_storm!=2) return ShadowDanceBuff and (Player:BuffStack(S.DanseMacabreBuff) >= 3 or not S.DanseMacabre:IsAvailable()) and (not PremeditationBuff or MeleeEnemies10yCount ~= 2) end local function Used_For_Danse(Spell) return Player:BuffUp(S.ShadowDanceBuff) and Spell:TimeSinceLastCast() < S.ShadowDance:TimeSinceLastCast() end -- # Finishers -- ReturnSpellOnly and StealthSpell parameters are to Predict Finisher in case of Stealth Macros local function Finish (ReturnSpellOnly, StealthSpell) local ShadowDanceBuff = Player:BuffUp(S.ShadowDanceBuff) local ShadowDanceBuffRemains = Player:BuffRemains(S.ShadowDanceBuff) local SymbolsofDeathBuffRemains = Player:BuffRemains(S.SymbolsofDeath) local FinishComboPoints = ComboPoints -- State changes based on predicted Stealth casts local PremeditationBuff = StealthSpell or Player:BuffUp(S.PremeditationBuff) if StealthSpell and StealthSpell:ID() == S.ShadowDance:ID() then ShadowDanceBuff = true ShadowDanceBuffRemains = 8 + S.ImprovedShadowDance:TalentRank() if S.TheFirstDance:IsAvailable() then FinishComboPoints = mathmin(Player:ComboPointsMax(), ComboPoints + 4) end if Player:HasTier(30, 2) then SymbolsofDeathBuffRemains = mathmax(SymbolsofDeathBuffRemains, 6) end end if S.SliceandDice:IsCastable() and HL.FilteredFightRemains(MeleeEnemies10y, ">", Player:BuffRemains(S.SliceandDice)) then -- actions.finish=variable,name=premed_snd_condition,value=talent.premeditation.enabled&spell_targets.shuriken_storm<5 if S.Premeditation:IsAvailable() and MeleeEnemies10yCount < 5 then -- actions.finish+=/slice_and_dice,if=variable.premed_snd_condition&cooldown.shadow_dance.charges_fractional<1.75&buff.slice_and_dice.remains= 1 and SymbolsofDeathBuffRemains - ShadowDanceBuffRemains < 1.2) then if ReturnSpellOnly then return S.SliceandDice else if S.SliceandDice:IsReady() and HR.Cast(S.SliceandDice) then return "Cast Slice and Dice (Premed)" end SetPoolingFinisher(S.SliceandDice) end end else -- actions.finish+=/slice_and_dice,if=!variable.premed_snd_condition&spell_targets.shuriken_storm<6&!buff.shadow_dance.up&buff.slice_and_dice.remains6&refreshable if (not SkipRupture or PriorityRotation) and S.Rupture:IsCastable() then if TargetInMeleeRange and (Target:FilteredTimeToDie(">", 6, -Target:DebuffRemains(S.Rupture)) or Target:TimeToDieIsNotValid()) and Rogue.CanDoTUnit(Target, RuptureDMGThreshold) and Target:DebuffRefreshable(S.Rupture, RuptureThreshold) then if ReturnSpellOnly then return S.Rupture else if S.Rupture:IsReady() and HR.Cast(S.Rupture) then return "Cast Rupture 1" end SetPoolingFinisher(S.Rupture) end end end -- actions.finish+=/rupture,if=!variable.skip_rupture&buff.finality_rupture.up&cooldown.shadow_dance.remains<12&cooldown.shadow_dance.charges_fractional<=1&spell_targets.shuriken_storm=1&(talent.dark_brew|talent.danse_macabre) if not SkipRupture and S.Rupture:IsCastable() then if MeleeEnemies10yCount == 1 and Player:BuffUp(S.FinalityRuptureBuff) and (S.DarkBrew:IsAvailable() or S.DanseMacabre:IsAvailable()) and S.ShadowDance:CooldownRemains() < 12 and S.ShadowDance:ChargesFractional() <= 1 then if ReturnSpellOnly then return S.Rupture else if S.Rupture:IsReady() and HR.Cast(S.Rupture) then return "Cast Rupture (Finality)" end SetPoolingFinisher(S.Rupture) end end end -- actions.finish+=/cold_blood,if=variable.secret_condition&cooldown.secret_technique.ready if S.ColdBlood:IsReady() and Secret_Condition(ShadowDanceBuff, PremeditationBuff) and S.SecretTechnique:CooldownUp() then if Settings.Commons.OffGCDasOffGCD.ColdBlood then HR.Cast(S.ColdBlood, Settings.Commons.OffGCDasOffGCD.ColdBlood) else if ReturnSpellOnly then return S.ColdBlood end if HR.Cast(S.ColdBlood) then return "Cast Cold Blood (SecTec)" end end end -- actions.finish+=/secret_technique,if=variable.secret_condition&(!talent.cold_blood|cooldown.cold_blood.remains>buff.shadow_dance.remains-2) -- Attention: Due to the SecTec/ColdBlood interaction, this adaption has additional checks not found in the APL string if S.SecretTechnique:IsReady() and Secret_Condition(ShadowDanceBuff, PremeditationBuff) and (not S.ColdBlood:IsAvailable() or (Settings.Commons.OffGCDasOffGCD.ColdBlood and S.ColdBlood:IsReady()) or Player:BuffUp(S.ColdBlood) or S.ColdBlood:CooldownRemains() > ShadowDanceBuffRemains - 2) then if ReturnSpellOnly then return S.SecretTechnique end if HR.Cast(S.SecretTechnique) then return "Cast Secret Technique" end end if not SkipRupture and S.Rupture:IsCastable() then -- actions.finish+=/rupture,cycle_targets=1,if=!variable.skip_rupture&!variable.priority_rotation&spell_targets.shuriken_storm>=2&target.time_to_die>=(2*combo_points)&refreshable if not ReturnSpellOnly and HR.AoEON() and not PriorityRotation and MeleeEnemies10yCount >= 2 then local function Evaluate_Rupture_Target(TargetUnit) return Everyone.CanDoTUnit(TargetUnit, RuptureDMGThreshold) and TargetUnit:DebuffRefreshable(S.Rupture, RuptureThreshold) end SuggestCycleDoT(S.Rupture, Evaluate_Rupture_Target, (2 * FinishComboPoints), MeleeEnemies5y) end -- actions.finish+=/rupture,if=!variable.skip_rupture&remainscooldown.symbols_of_death.remains+5 if TargetInMeleeRange and Target:DebuffRemains(S.Rupture) < S.SymbolsofDeath:CooldownRemains() + 10 and S.SymbolsofDeath:CooldownRemains() <= 5 and Rogue.CanDoTUnit(Target, RuptureDMGThreshold) and Target:FilteredTimeToDie(">", 5 + S.SymbolsofDeath:CooldownRemains(), -Target:DebuffRemains(S.Rupture)) then if ReturnSpellOnly then return S.Rupture else if S.Rupture:IsReady() and HR.Cast(S.Rupture) then return "Cast Rupture 2" end SetPoolingFinisher(S.Rupture) end end end -- actions.finish+=/black_powder,if=!variable.priority_rotation&spell_targets>=3|!used_for_danse&buff.shadow_dance.up&spell_targets.shuriken_storm=2&talent.danse_macabre if S.BlackPowder:IsCastable() and (not PriorityRotation and MeleeEnemies10yCount >= 3 or (MeleeEnemies10yCount == 2 and ShadowDanceBuff and S.DanseMacabre:IsAvailable() and not Used_For_Danse(S.BlackPowder))) then if ReturnSpellOnly then return S.BlackPowder else if S.BlackPowder:IsReady() and HR.Cast(S.BlackPowder) then return "Cast Black Powder" end SetPoolingFinisher(S.BlackPowder) end end -- actions.finish+=/eviscerate if S.Eviscerate:IsCastable() and TargetInMeleeRange then if ReturnSpellOnly then return S.Eviscerate else if S.Eviscerate:IsReady() and HR.Cast(S.Eviscerate) then return "Cast Eviscerate" end SetPoolingFinisher(S.Eviscerate) end end return false end -- # Stealthed Rotation -- ReturnSpellOnly and StealthSpell parameters are to Predict Finisher in case of Stealth Macros local function Stealthed (ReturnSpellOnly, StealthSpell) local ShadowDanceBuff = Player:BuffUp(S.ShadowDanceBuff) local ShadowDanceBuffRemains = Player:BuffRemains(S.ShadowDanceBuff) local TheRottenBuff = Player:BuffUp(S.TheRottenBuff) local StealthComboPoints, StealthComboPointsDeficit = ComboPoints, ComboPointsDeficit -- State changes based on predicted Stealth casts local PremeditationBuff = Player:BuffUp(S.PremeditationBuff) or (StealthSpell and S.Premeditation:IsAvailable()) local SilentStormBuff = Player:BuffUp(S.SilentStormBuff) or (StealthSpell and S.SilentStorm:IsAvailable()) local StealthBuff = Player:BuffUp(Rogue.StealthSpell()) or (StealthSpell and StealthSpell:ID() == Rogue.StealthSpell():ID()) local VanishBuffCheck = Player:BuffUp(Rogue.VanishBuffSpell()) or (StealthSpell and StealthSpell:ID() == S.Vanish:ID()) if StealthSpell and StealthSpell:ID() == S.ShadowDance:ID() then ShadowDanceBuff = true ShadowDanceBuffRemains = 8 + S.ImprovedShadowDance:TalentRank() if S.TheRotten:IsAvailable() and Player:HasTier(30, 2) then TheRottenBuff = true end if S.TheFirstDance:IsAvailable() then StealthComboPoints = mathmin(Player:ComboPointsMax(), ComboPoints + 4) StealthComboPointsDeficit = Player:ComboPointsMax() - StealthComboPoints end end local StealthEffectiveComboPoints = Rogue.EffectiveComboPoints(StealthComboPoints) local ShadowstrikeIsCastable = S.Shadowstrike:IsCastable() or StealthBuff or VanishBuffCheck or ShadowDanceBuff or Player:BuffUp(S.SepsisBuff) if StealthBuff or VanishBuffCheck then ShadowstrikeIsCastable = ShadowstrikeIsCastable and Target:IsInRange(25) else ShadowstrikeIsCastable = ShadowstrikeIsCastable and TargetInMeleeRange end -- actions.stealthed=shadowstrike,if=(buff.stealth.up|buff.vanish.up)&(spell_targets.shuriken_storm<4|variable.priority_rotation) if ShadowstrikeIsCastable and (StealthBuff or VanishBuffCheck) and (MeleeEnemies10yCount < 4 or PriorityRotation) then if ReturnSpellOnly then return S.Shadowstrike else if HR.Cast(S.Shadowstrike) then return "Cast Shadowstrike (Stealth)" end end end -- #Variable to Gloomblade / Backstab when on 4 or 5 combo points with premediation and when the combo point is not anima charged -- actions.stealthed+=/variable,name=gloomblade_condition,value=buff.danse_macabre.stack<5&(combo_points.deficit=2|combo_points.deficit=3)&(buff.premeditation.up|effective_combo_points<7)&(spell_targets.shuriken_storm<=8|talent.lingering_shadow) local GloombladeCondition = (Player:BuffStack(S.DanseMacabreBuff) < 5 and (StealthComboPointsDeficit == 2 or StealthComboPointsDeficit == 3) and (PremeditationBuff or StealthEffectiveComboPoints < 7) and (MeleeEnemies10yCount <= 8 or S.LingeringShadow:IsAvailable())) -- actions.stealthed+=/shuriken_storm,if=variable.gloomblade_condition&buff.silent_storm.up&!debuff.find_weakness.remains&talent.improved_shuriken_storm.enabled|combo_points<=1&!used_for_danse&spell_targets.shuriken_storm=2&talent.danse_macabre if (GloombladeCondition and SilentStormBuff and Target:DebuffDown(S.FindWeaknessDebuff) and S.ImprovedShurikenStorm:IsAvailable()) or (S.DanseMacabre:IsAvailable() and StealthComboPoints <= 1 and MeleeEnemies10yCount == 2 and not Used_For_Danse(S.ShurikenStorm)) then if ReturnSpellOnly then return S.ShurikenStorm else if HR.Cast(S.ShurikenStorm) then return "Cast Shuriken Storm (FW)" end end end -- actions.stealthed+=/gloomblade,if=variable.gloomblade_condition&(!used_for_danse|spell_targets.shuriken_storm!=2)|combo_points<=2&buff.the_rotten.up&spell_targets.shuriken_storm<=3 if S.Gloomblade:IsCastable() and ((GloombladeCondition and (not Used_For_Danse(S.Gloomblade) or MeleeEnemies10yCount ~= 2)) or (StealthComboPoints <= 2 and TheRottenBuff and MeleeEnemies10yCount <= 3)) then if ReturnSpellOnly then -- If calling from a Stealth macro, we don't need the PV suggestion since it's already a macro cast if StealthSpell then return S.Gloomblade else return { S.Gloomblade, S.Stealth } end else if HR.CastQueue(S.Gloomblade, S.Stealth) then return "Cast Gloomblade (Stealth)" end end end -- actions.stealthed+=/backstab,if=variable.gloomblade_condition&talent.danse_macabre&buff.danse_macabre.stack<=2&spell_targets.shuriken_storm<=2 if S.Backstab:IsCastable() and GloombladeCondition and S.DanseMacabre:IsAvailable() and not Used_For_Danse(S.Backstab) and Player:BuffStack(S.DanseMacabreBuff) <= 2 and MeleeEnemies10yCount <= 2 then if ReturnSpellOnly then -- If calling from a Stealth macro, we don't need the PV suggestion since it's already a macro cast if StealthSpell then return S.Backstab else return { S.Backstab, S.Stealth } end else if HR.CastQueue(S.Backstab, S.Stealth) then return "Cast Backstab (Stealth)" end end end -- actions.stealthed+=/call_action_list,name=finish,if=effective_combo_points>=cp_max_spend if StealthEffectiveComboPoints >= Rogue.CPMaxSpend() then return Finish(ReturnSpellOnly, StealthSpell) end -- actions.stealthed+=/call_action_list,name=finish,if=buff.shuriken_tornado.up&combo_points.deficit<=2 if Player:BuffUp(S.ShurikenTornado) and StealthComboPointsDeficit <= 2 then return Finish(ReturnSpellOnly, StealthSpell) end -- actions.stealthed+=/call_action_list,name=finish,if=spell_targets.shuriken_storm>=4-talent.seal_fate&variable.effective_combo_points>=4 if MeleeEnemies10yCount >= (4 - BoolToInt(S.SealFate:IsAvailable())) and StealthEffectiveComboPoints >= 4 then return Finish(ReturnSpellOnly, StealthSpell) end -- actions.stealthed+=/call_action_list,name=finish,if=combo_points.deficit<=1+(talent.seal_fate|talent.deeper_stratagem|talent.secret_stratagem) if StealthComboPointsDeficit <= 1 + num(S.SealFate:IsAvailable() or S.DeeperStratagem:IsAvailable() or S.SecretStratagem:IsAvailable()) then return Finish(ReturnSpellOnly, StealthSpell) end -- As we're in stealth, show a special macro combo with the PV icon to make it clear we are casting Backstab specifically within Shadow Dance -- actions.stealthed+=/gloomblade,if=buff.perforated_veins.stack>=5&spell_targets.shuriken_storm<3 -- actions.stealthed+=/backstab,if=buff.perforated_veins.stack>=5&spell_targets.shuriken_storm<3 if Player:BuffStack(S.PerforatedVeinsBuff) >= 5 and MeleeEnemies10yCount < 3 then if S.Gloomblade:IsCastable() then if ReturnSpellOnly then -- If calling from a Stealth macro, we don't need the PV suggestion since it's already a macro cast if StealthSpell then return S.Gloomblade else return { S.Gloomblade, S.PerforatedVeins } end else if HR.CastQueue(S.Gloomblade, S.PerforatedVeins) then return "Cast Gloomblade (Stealth PV)" end end elseif S.Backstab:IsCastable() then if ReturnSpellOnly then -- If calling from a Stealth macro, we don't need the PV suggestion since it's already a macro cast if StealthSpell then return S.Backstab else return { S.Backstab, S.PerforatedVeins } end else if HR.CastQueue(S.Backstab, S.PerforatedVeins) then return "Cast Backstab (Stealth PV)" end end end end -- actions.stealthed+=/shadowstrike,if=stealthed.sepsis&spell_targets.shuriken_storm<4 if ShadowstrikeIsCastable and not Player:StealthUp(true, false) and not StealthSpell and Player:BuffUp(S.SepsisBuff) and MeleeEnemies10yCount < 4 then if ReturnSpellOnly then return S.Shadowstrike else if HR.Cast(S.Shadowstrike) then return "Cast Shadowstrike (Sepsis)" end end end -- actions.stealthed+=/shuriken_storm,if=spell_targets>=3+buff.the_rotten.up&(!buff.premeditation.up|spell_targets>=7&!variable.priority_rotation) if HR.AoEON() and S.ShurikenStorm:IsCastable() and MeleeEnemies10yCount >= (3 + BoolToInt(TheRottenBuff)) and (not PremeditationBuff or (MeleeEnemies10yCount >= 7 and not PriorityRotation)) then if ReturnSpellOnly then return S.ShurikenStorm else if HR.Cast(S.ShurikenStorm) then return "Cast Shuriken Storm" end end end -- actions.stealthed+=/shadowstrike,if=debuff.find_weakness.remains<=1|cooldown.symbols_of_death.remains<18&debuff.find_weakness.remains3&combo_points<=2&(cooldown.secret_technique.remains>=30|!talent.secret_technique) if S.Vanish:IsCastable() and ComboPoints <= 2 and Player:BuffStack(S.DanseMacabreBuff) > 3 and (S.SecretTechnique:CooldownRemains() >= 30 or not S.SecretTechnique:IsAvailable()) then ShouldReturn = StealthMacro(S.Vanish) if ShouldReturn then return "Vanish Macro (DM) " .. ShouldReturn end end -- actions.cds+=/cold_blood,if=!talent.secret_technique&combo_points>=5 if S.ColdBlood:IsReady() and not S.SecretTechnique:IsAvailable() and ComboPoints >= 5 then if HR.Cast(S.ColdBlood, Settings.Commons.OffGCDasOffGCD.ColdBlood) then return "Cast Cold Blood" end end if TargetInMeleeRange then -- actions.cds+=/flagellation,target_if=max:target.time_to_die,if=variable.snd_condition&combo_points>=5&target.time_to_die>10 if HR.CDsON() and S.Flagellation:IsReady() and SnDCondition and not Player:StealthUp(false, false) and ComboPoints >= 5 and Target:FilteredTimeToDie(">", 10) then if HR.Cast(S.Flagellation, nil, Settings.Commons.CovenantDisplayStyle) then return "Cast Flagellation" end end end -- actions.cds+=/shuriken_tornado,if=spell_targets.shuriken_storm<=1&energy>=60&variable.snd_condition&cooldown.symbols_of_death.up&cooldown.shadow_dance.charges>=1&(!talent.flagellation.enabled&!cooldown.flagellation.up|buff.flagellation_buff.up|spell_targets.shuriken_storm>=5)&combo_points<=2&!buff.premeditation.up if S.ShurikenTornado:IsCastable() and MeleeEnemies10yCount <= 1 and SnDCondition and S.SymbolsofDeath:CooldownUp() and S.ShadowDance:Charges() >= 1 and (not S.Flagellation:IsAvailable() or Player:BuffUp(S.Flagellation) or MeleeEnemies10yCount >= 5) and ComboPoints <= 2 and not Player:BuffUp(S.PremeditationBuff) then -- actions.cds+=/pool_resource,for_next=1,if=talent.shuriken_tornado.enabled&!talent.shadow_focus.enabled if Player:Energy() >= 60 then if HR.Cast(S.ShurikenTornado, Settings.Subtlety.GCDasOffGCD.ShurikenTornado) then return "Cast Shuriken Tornado" end elseif not S.ShadowFocus:IsAvailable() then if HR.CastPooling(S.ShurikenTornado) then return "Pool for Shuriken Tornado" end end end if TargetInMeleeRange then -- actions.cds+=/sepsis,if=variable.snd_condition&combo_points.deficit>=1&target.time_to_die>=16 if HR.CDsON() and S.Sepsis:IsReady() and SnDCondition and ComboPointsDeficit >= 1 and not Target:FilteredTimeToDie("<", 16) then if HR.Cast(S.Sepsis, nil, Settings.Commons.CovenantDisplayStyle) then return "Cast Sepsis" end end -- actions.cds+=/symbols_of_death,if=(buff.symbols_of_death.remains<=3&!cooldown.shadow_dance.ready|!set_bonus.tier30_2pc)&variable.rotten_condition&variable.snd_condition&(!talent.flagellation&(combo_points<=1|!talent.the_rotten)|cooldown.flagellation.remains>10|cooldown.flagellation.up&combo_points>=5) if S.SymbolsofDeath:IsCastable() then if ((Player:BuffRemains(S.SymbolsofDeath) <= 3 and not S.ShadowDance:CooldownUp()) or not Player:HasTier(30, 2)) and Rotten_Condition() and SnDCondition and ((not S.Flagellation:IsAvailable() and (ComboPoints <= 1 or not S.TheRotten:IsAvailable())) or S.Flagellation:CooldownRemains() > 10 or (S.Flagellation:CooldownUp() and ComboPoints >= 5)) then if HR.Cast(S.SymbolsofDeath, Settings.Subtlety.OffGCDasOffGCD.SymbolsofDeath) then return "Cast Symbols of Death" end end end end if S.MarkedforDeath:IsCastable() then -- actions.cds+=/marked_for_death,target_if=min:target.time_to_die,if=target.time_to_die30&!stealthed.all&combo_points.deficit>=cp_max_spend if not Player:StealthUp(true, true) and ComboPointsDeficit >= Rogue.CPMaxSpend() then if not Settings.Commons.STMfDAsDPSCD then HR.CastSuggested(S.MarkedforDeath) elseif HR.CDsON() then if HR.Cast(S.MarkedforDeath, Settings.Commons.OffGCDasOffGCD.MarkedforDeath) then return "Cast Marked for Death" end end end end if HR.CDsON() then -- actions.cds+=/shadow_blades,if=variable.snd_condition&combo_points.deficit>=2&target.time_to_die>=10&(dot.sepsis.ticking|cooldown.sepsis.remains<=8|!talent.sepsis)|fight_remains<=20 if S.ShadowBlades:IsCastable() and (SnDCondition and ComboPointsDeficit >= 2 and Target:FilteredTimeToDie(">=", 10) and (not S.Sepsis:IsAvailable() or S.Sepsis:CooldownRemains() <= 8 or Target:DebuffUp(S.Sepsis)) or HL.BossFilteredFightRemains("<=", 20)) then if HR.Cast(S.ShadowBlades, Settings.Subtlety.OffGCDasOffGCD.ShadowBlades) then return "Cast Shadow Blades" end end -- actions.cds+=/echoing_reprimand,if=variable.snd_condition&combo_points.deficit>=3&(variable.priority_rotation|spell_targets.shuriken_storm<=4|talent.resounding_clarity)&(buff.shadow_dance.up|!talent.danse_macabre) if S.EchoingReprimand:IsReady() and TargetInMeleeRange and ComboPointsDeficit >= 3 and (PriorityRotation or MeleeEnemies10yCount <= 4 or S.ResoundingClarity:IsAvailable()) and (Player:BuffUp(S.ShadowDanceBuff) or not S.DanseMacabre:IsAvailable()) then if HR.Cast(S.EchoingReprimand, nil, Settings.Commons.CovenantDisplayStyle) then return "Cast Echoing Reprimand" end end -- actions.cds+=/shuriken_tornado,if=variable.snd_condition&buff.symbols_of_death.up&combo_points<=2&(!buff.premeditation.up|spell_targets.shuriken_storm>4) -- actions.cds+=/shuriken_tornado,if=cooldown.shadow_dance.ready&!stealthed.all&spell_targets.shuriken_storm>=3&!talent.flagellation.enabled if S.ShurikenTornado:IsReady() then if SnD_Condition and Player:BuffUp(S.SymbolsofDeath) and ComboPoints <= 2 and (not Player:BuffUp(S.PremeditationBuff) or MeleeEnemies10yCount > 4) then if HR.Cast(S.ShurikenTornado, Settings.Subtlety.GCDasOffGCD.ShurikenTornado) then return "Cast Shuriken Tornado (SoD)" end end if not S.Flagellation:IsAvailable() and MeleeEnemies10yCount >= 3 and S.ShadowDance:Charges() >= 1 and not Player:StealthUp(true, true) then if HR.Cast(S.ShurikenTornado, Settings.Subtlety.GCDasOffGCD.ShurikenTornado) then return "Cast Shuriken Tornado (Dance)" end end end -- actions.cds+=/shadow_dance,if=!buff.shadow_dance.up&fight_remains<=8+talent.subterfuge.enabled if S.ShadowDance:IsCastable() and MayBurnShadowDance() and not Player:BuffUp(S.ShadowDanceBuff) and HL.BossFilteredFightRemains("<=", 8) then ShouldReturn = StealthMacro(S.ShadowDance) if ShouldReturn then return "Shadow Dance Macro (Low TTD) " .. ShouldReturn end end -- actions.cds+=/thistle_tea,if=(cooldown.symbols_of_death.remains>=3|buff.symbols_of_death.up)&!buff.thistle_tea.up&(energy.deficit>=100&(combo_points.deficit>=2|spell_targets.shuriken_storm>=3)|cooldown.thistle_tea.charges_fractional>=2.75&buff.shadow_dance.up)|buff.shadow_dance.remains>=4&!buff.thistle_tea.up&spell_targets.shuriken_storm>=3|!buff.thistle_tea.up&fight_remains<=(6*cooldown.thistle_tea.charges) if S.ThistleTea:IsReady() then if (S.SymbolsofDeath:CooldownRemains() >= 3 or Player:BuffUp(S.SymbolsofDeath)) and not Player:BuffUp(S.ThistleTea) and (Player:EnergyDeficitPredicted() >= 100 and (Player:ComboPointsDeficit() >= 2 or MeleeEnemies10yCount >= 3) or S.ThistleTea:ChargesFractional() >= 2.75 and Player:BuffUp(S.ShadowDanceBuff)) or Player:BuffRemains(S.ShadowDanceBuff) >= 4 and not Player:BuffUp(S.ThistleTea) and MeleeEnemies10yCount >= 3 or not Player:BuffUp(S.ThistleTea) and HL.BossFilteredFightRemains("<=", 6 * S.ThistleTea:Charges()) then if HR.Cast(S.ThistleTea, nil, Settings.Commons.TrinketDisplayStyle) then return "Thistle Tea"; end end end -- TODO: Add Potion Suggestion -- actions.cds+=/potion,if=buff.bloodlust.react|fight_remains<30|buff.symbols_of_death.up&(buff.shadow_blades.up|cooldown.shadow_blades.remains<=10) -- Racials if Player:BuffUp(S.SymbolsofDeath) then -- actions.cds+=/blood_fury,if=buff.symbols_of_death.up if S.BloodFury:IsCastable() then if HR.Cast(S.BloodFury, Settings.Commons.OffGCDasOffGCD.Racials) then return "Cast Blood Fury" end end -- actions.cds+=/berserking,if=buff.symbols_of_death.up if S.Berserking:IsCastable() then if HR.Cast(S.Berserking, Settings.Commons.OffGCDasOffGCD.Racials) then return "Cast Berserking" end end -- actions.cds+=/fireblood,if=buff.symbols_of_death.up if S.Fireblood:IsCastable() then if HR.Cast(S.Fireblood, Settings.Commons.OffGCDasOffGCD.Racials) then return "Cast Fireblood" end end -- actions.cds+=/ancestral_call,if=buff.symbols_of_death.up if S.AncestralCall:IsCastable() then if HR.Cast(S.AncestralCall, Settings.Commons.OffGCDasOffGCD.Racials) then return "Cast Ancestral Call" end end end -- Trinkets if Settings.Commons.UseTrinkets then -- actions.cds+=/use_item,name=manic_grieftorch,use_off_gcd=1,if=!stealthed.all&(!raid_event.adds.up|!equipped.stormeaters_boon|trinket.stormeaters_boon.cooldown.remains>20) if I.ManicGrieftorch:IsEquippedAndReady() then if (not Player:StealthUp(true, true) and (not I.StormEatersBoon:IsEquipped() or I.StormEatersBoon:CooldownRemains() > 20)) then if HR.Cast(I.ManicGrieftorch, nil, Settings.Commons.TrinketDisplayStyle) then return "Manic Grieftorch" end end end -- actions.cds+=/use_item,name=beacon_to_the_beyond,use_off_gcd=1,if=!stealthed.all&(buff.deeper_daggers.up|!talent.deeper_daggers)&(!raid_event.adds.up|!equipped.stormeaters_boon|trinket.stormeaters_boon.cooldown.remains>20) if I.BeaconToTheBeyond:IsEquippedAndReady() then if (not Player:StealthUp(true, true) and (Player:BuffUp(S.DeeperDaggersBuff) or not S.DeeperDaggers:IsAvailable()) and (not I.StormEatersBoon:IsEquipped() or I.StormEatersBoon:CooldownRemains() > 20)) then if HR.Cast(I.BeaconToTheBeyond, nil, Settings.Commons.TrinketDisplayStyle) then return "Beacon To The Beyond" end end end -- actions.cds+=/use_items,if=!stealthed.all|fight_remains<10 if not Player:StealthUp(true, true) or HL.BossFilteredFightRemains("<", 10) then local TrinketToUse = Player:GetUseableItems(OnUseExcludes) if TrinketToUse then if HR.Cast(TrinketToUse, nil, Settings.Commons.TrinketDisplayStyle) then return "Generic use_items for " .. TrinketToUse:Name() end end end end end return false end -- # Stealth Cooldowns local function Stealth_CDs (EnergyThreshold) if HR.CDsON() and S.ShadowDance:TimeSinceLastDisplay() > 0.3 and S.Shadowmeld:TimeSinceLastDisplay() > 0.3 and not Player:IsTanking(Target) then -- actions.stealth_cds+=/vanish,if=(!talent.danse_macabre|spell_targets.shuriken_storm>=3)&!variable.shd_threshold&combo_points.deficit>1&(cooldown.flagellation.remains>=60|!talent.flagellation|fight_remains<=(30*cooldown.vanish.charges)) if S.Vanish:IsCastable() and (not S.DanseMacabre:IsAvailable() or MeleeEnemies10yCount >= 3) and not ShD_Threshold() and ComboPointsDeficit > 1 and (S.Flagellation:CooldownRemains() >= 60 or not S.Flagellation:IsAvailable() or HL.BossFilteredFightRemains("<=", 30 * S.Vanish:Charges())) then ShouldReturn = StealthMacro(S.Vanish, EnergyThreshold) if ShouldReturn then return "Vanish Macro " .. ShouldReturn end end -- actions.stealth_cds+=/shadowmeld,if=energy>=40&energy.deficit>=10&!variable.shd_threshold&combo_points.deficit>4 if S.Shadowmeld:IsCastable() and TargetInMeleeRange and not Player:IsMoving() and Player:EnergyDeficitPredicted() > 10 and not ShD_Threshold() and ComboPointsDeficit > 4 then -- actions.stealth_cds+=/pool_resource,for_next=1,extra_amount=40 if Player:Energy() < 40 then if HR.CastPooling(S.Shadowmeld, Player:EnergyTimeToX(40)) then return "Pool for Shadowmeld" end end ShouldReturn = StealthMacro(S.Shadowmeld, EnergyThreshold) if ShouldReturn then return "Shadowmeld Macro " .. ShouldReturn end end end if TargetInMeleeRange and S.ShadowDance:IsCastable() and S.ShadowDance:Charges() >= 1 and S.Vanish:TimeSinceLastDisplay() > 0.3 and S.Shadowmeld:TimeSinceLastDisplay() > 0.3 and (HR.CDsON() or (S.ShadowDance:ChargesFractional() >= Settings.Subtlety.ShDEcoCharge - (not S.ShadowDanceTalent:IsAvailable() and 0.75 or 0))) then -- actions.stealth_cds+=/shadow_dance,if=(variable.shd_combo_points&(!talent.shadow_dance&buff.symbols_of_death.remains>=(2.2-talent.flagellation.enabled)|variable.shd_threshold)|talent.shadow_dance&cooldown.secret_technique.remains<=9&(spell_targets.shuriken_storm<=3|talent.danse_macabre)|buff.flagellation.up|buff.flagellation_persist.remains>=6|spell_targets.shuriken_storm>=4&cooldown.symbols_of_death.remains>10)&variable.rotten_threshold -- NOTE: |buff.flagellation.up is a dead operation in SimC due to a typo, since the buff we use in-game is buff.flagellation_buff.up, ignoring if ((ShD_Combo_Points() and (not S.ShadowDanceTalent:IsAvailable() and Player:BuffRemains(S.SymbolsofDeath) >= (2.2 - BoolToInt(S.Flagellation:IsAvailable())) or ShD_Threshold())) or (S.ShadowDanceTalent:IsAvailable() and S.SecretTechnique:CooldownRemains() <= 9 and (MeleeEnemies10yCount <= 3 or S.DanseMacabre:IsAvailable())) or Player:BuffRemains(S.FlagellationPersistBuff) >= 6 or MeleeEnemies10yCount >= 4 and S.SymbolsofDeath:CooldownRemains() > 10) and Rotten_Threshold() then ShouldReturn = StealthMacro(S.ShadowDance, EnergyThreshold) if ShouldReturn then return "ShadowDance Macro 1 " .. ShouldReturn end end -- actions.stealth_cds+=/shadow_dance,if=variable.shd_combo_points&fight_remains= EnergyThreshold -- actions.build=shuriken_storm,if=spell_targets>=2+(talent.gloomblade&buff.lingering_shadow.remains>=6|buff.perforated_veins.up) if HR.AoEON() and S.ShurikenStorm:IsCastable() and MeleeEnemies10yCount >= 2 + BoolToInt(S.Gloomblade:IsAvailable() and Player:BuffRemains(S.LingeringShadowBuff) >= 6 or Player:BuffUp(S.PerforatedVeinsBuff)) then if ThresholdMet and HR.Cast(S.ShurikenStorm) then return "Cast Shuriken Storm" end SetPoolingAbility(S.ShurikenStorm, EnergyThreshold) end if TargetInMeleeRange then -- # Build immediately unless the next CP is Animacharged and we won't cap energy waiting for it. -- actions.build+=/variable,name=anima_helper,value=!talent.echoing_reprimand.enabled|!(variable.is_next_cp_animacharged&(time_to_sht.3.plus<0.5|time_to_sht.4.plus<1)&energy<60) if S.EchoingReprimand:IsAvailable() and Player:Energy() < 60 and (ComboPoints == 2 and Player:BuffUp(S.EchoingReprimand3) or ComboPoints == 3 and Player:BuffUp(S.EchoingReprimand4) or ComboPoints == 4 and Player:BuffUp(S.EchoingReprimand5)) and (Rogue.TimeToSht(3) < 0.5 or Rogue.TimeToSht(4) < 1.0 or Rogue.TimeToSht(5) < 1.0) then HR.Cast(S.PoolEnergy) return "ER Generator Pooling" end -- actions.build+=/gloomblade,if=variable.anima_helper if S.Gloomblade:IsCastable() then if ThresholdMet and HR.Cast(S.Gloomblade) then return "Cast Gloomblade" end SetPoolingAbility(S.Gloomblade, EnergyThreshold) -- actions.build+=/backstab,if=variable.anima_helper elseif S.Backstab:IsCastable() then if ThresholdMet and HR.Cast(S.Backstab) then return "Cast Backstab" end SetPoolingAbility(S.Backstab, EnergyThreshold) end end return false end local Interrupts = { {S.Blind, "Cast Blind (Interrupt)", function () return true end}, {S.KidneyShot, "Cast Kidney Shot (Interrupt)", function () return ComboPoints > 0 end}, {S.CheapShot, "Cast Cheap Shot (Interrupt)", function () return Player:StealthUp(true, true) end} } -- APL Main local function APL () -- Reset pooling cache PoolingAbility = nil PoolingFinisher = nil PoolingEnergy = 0 -- Unit Update MeleeRange = S.AcrobaticStrikes:IsAvailable() and 8 or 5 AoERange = S.AcrobaticStrikes:IsAvailable() and 13 or 10 TargetInMeleeRange = Target:IsInMeleeRange(MeleeRange) TargetInAoERange = Target:IsInMeleeRange(AoERange) if AoEON() then Enemies30y = Player:GetEnemiesInRange(30) -- Serrated Bone Spike MeleeEnemies10y = Player:GetEnemiesInMeleeRange(AoERange) -- Shuriken Storm & Black Powder MeleeEnemies10yCount = #MeleeEnemies10y MeleeEnemies5y = Player:GetEnemiesInMeleeRange(MeleeRange) -- Melee cycle else Enemies30y = {} MeleeEnemies10y = {} MeleeEnemies10yCount = 1 MeleeEnemies5y = {} end -- Cache updates ComboPoints = Player:ComboPoints() EffectiveComboPoints = Rogue.EffectiveComboPoints(ComboPoints) ComboPointsDeficit = Player:ComboPointsDeficit() PriorityRotation = UsePriorityRotation() StealthEnergyRequired = Player:EnergyMax() - Stealth_Threshold() -- Adjust Animacharged CP Prediction for Shadow Techniques -- If we are on a non-optimal Animacharged CP, ignore it if the time to ShT is less than GCD + 500ms, unless the ER buff will expire soon -- Reduces the risk of queued finishers into ShT procs for non-optimal CP amounts -- This is an adaptation of the following APL lines: -- actions+=/variable,name=is_next_cp_animacharged,if=talent.echoing_reprimand.enabled,value=combo_points=1&buff.echoing_reprimand_2.up|combo_points=2&buff.echoing_reprimand_3.up|combo_points=3&buff.echoing_reprimand_4.up|combo_points=4&buff.echoing_reprimand_5.up -- actions+=/variable,name=effective_combo_points,value=effective_combo_points -- actions+=/variable,name=effective_combo_points,if=talent.echoing_reprimand.enabled&effective_combo_points>combo_points&combo_points.deficit>2&time_to_sht.4.plus<0.5&!variable.is_next_cp_animacharged,value=combo_points if EffectiveComboPoints > ComboPoints and ComboPointsDeficit > 2 and Player:AffectingCombat() then if ComboPoints == 2 and not Player:BuffUp(S.EchoingReprimand3) or ComboPoints == 3 and not Player:BuffUp(S.EchoingReprimand4) or ComboPoints == 4 and not Player:BuffUp(S.EchoingReprimand5) then local TimeToSht = Rogue.TimeToSht(4) if TimeToSht == 0 then TimeToSht = Rogue.TimeToSht(5) end if TimeToSht < (mathmax(Player:EnergyTimeToX(35), Player:GCDRemains()) + 0.5) then EffectiveComboPoints = ComboPoints end end end -- Shuriken Tornado Combo Point Prediction if Player:BuffUp(S.ShurikenTornado, nil, true) and ComboPoints < Rogue.CPMaxSpend() then local TimeToNextTornadoTick = Rogue.TimeToNextTornado() if TimeToNextTornadoTick <= Player:GCDRemains() or mathabs(Player:GCDRemains() - TimeToNextTornadoTick) < 0.25 then local PredictedComboPointGeneration = MeleeEnemies10yCount + num(Player:BuffUp(S.ShadowBlades)) ComboPoints = mathmin(ComboPoints + PredictedComboPointGeneration, Rogue.CPMaxSpend()) ComboPointsDeficit = mathmax(ComboPointsDeficit - PredictedComboPointGeneration, 0) if EffectiveComboPoints < Rogue.CPMaxSpend() then EffectiveComboPoints = ComboPoints end end end -- Damage Cache updates (after EffectiveComboPoints adjustments) RuptureThreshold = (4 + EffectiveComboPoints * 4) * 0.3 RuptureDMGThreshold = S.Eviscerate:Damage()*Settings.Subtlety.EviscerateDMGOffset; -- Used to check if Rupture is worth to be casted since it's a finisher. --- Defensives -- Crimson Vial ShouldReturn = Rogue.CrimsonVial() if ShouldReturn then return ShouldReturn end -- Feint ShouldReturn = Rogue.Feint() if ShouldReturn then return ShouldReturn end -- Poisons Rogue.Poisons() --- Out of Combat if not Player:AffectingCombat() then -- Stealth -- Note: Since 7.2.5, Blizzard disallowed Stealth cast under ShD (workaround to prevent the Extended Stealth bug) if not Player:BuffUp(S.ShadowDanceBuff) and not Player:BuffUp(Rogue.VanishBuffSpell()) then ShouldReturn = Rogue.Stealth(Rogue.StealthSpell()) if ShouldReturn then return ShouldReturn end end -- Flask -- Food -- Rune -- PrePot w/ Bossmod Countdown -- Opener if Everyone.TargetIsValid() and (Target:IsSpellInRange(S.Shadowstrike) or TargetInMeleeRange) then -- Precombat CDs if HR.CDsON() then if S.MarkedforDeath:IsCastable() and Player:ComboPointsDeficit() >= Rogue.CPMaxSpend() then if HR.Cast(S.MarkedforDeath, Settings.Commons.OffGCDasOffGCD.MarkedforDeath) then return "Cast Marked for Death (OOC)" end end -- TODO: actions.precombat+=/fleshcraft,if=soulbind.pustule_eruption|soulbind.volatile_solvent end if Player:StealthUp(true, true) then PoolingAbility = Stealthed(true) if PoolingAbility then -- To avoid pooling icon spam if type(PoolingAbility) == "table" and #PoolingAbility > 1 then if HR.CastQueuePooling(nil, unpack(PoolingAbility)) then return "Stealthed Macro Cast or Pool (OOC): ".. PoolingAbility[1]:Name() end else if HR.CastPooling(PoolingAbility) then return "Stealthed Cast or Pool (OOC): "..PoolingAbility:Name() end end end elseif ComboPoints >= 5 then ShouldReturn = Finish() if ShouldReturn then return ShouldReturn .. " (OOC)" end elseif S.Backstab:IsCastable() then if HR.Cast(S.Backstab) then return "Cast Backstab (OOC)" end end end return end -- In Combat -- MfD Sniping Rogue.MfDSniping(S.MarkedforDeath) if Everyone.TargetIsValid() then -- Interrupts ShouldReturn = Everyone.Interrupt(5, S.Kick, Settings.Commons2.OffGCDasOffGCD.Kick, Interrupts) if ShouldReturn then return ShouldReturn end -- # Check CDs at first -- actions=call_action_list,name=cds ShouldReturn = CDs() if ShouldReturn then return "CDs: " .. ShouldReturn end -- # Apply Slice and Dice at 4+ CP if it expires within the next GCD or is not up -- actions+=/slice_and_dice,if=spell_targets.shuriken_storm6&combo_points>=4 if S.SliceandDice:IsCastable() and MeleeEnemies10yCount < Rogue.CPMaxSpend() and HL.FilteredFightRemains(MeleeEnemies10y, ">", 6) and Player:BuffRemains(S.SliceandDice) < Player:GCD() and ComboPoints >= 4 then if S.SliceandDice:IsReady() and HR.Cast(S.SliceandDice) then return "Cast Slice and Dice (Low Duration)" end SetPoolingFinisher(S.SliceandDice) end -- # Run fully switches to the Stealthed Rotation (by doing so, it forces pooling if nothing is available). -- actions+=/run_action_list,name=stealthed,if=stealthed.all if Player:StealthUp(true, true) then PoolingAbility = Stealthed(true) if PoolingAbility then -- To avoid pooling icon spam if type(PoolingAbility) == "table" and #PoolingAbility > 1 then if HR.CastQueuePooling(nil, unpack(PoolingAbility)) then return "Stealthed Macro " .. PoolingAbility[1]:Name() .. "|" .. PoolingAbility[2]:Name() end else -- Special case for Shuriken Tornado if Player:BuffUp(S.ShurikenTornado) and ComboPoints ~= Player:ComboPoints() and (PoolingAbility == S.BlackPowder or PoolingAbility == S.Eviscerate or PoolingAbility == S.Rupture or PoolingAbility == S.SliceandDice) then if HR.CastQueuePooling(nil, S.ShurikenTornado, PoolingAbility) then return "Stealthed Tornado Cast " .. PoolingAbility:Name() end else if HR.CastPooling(PoolingAbility) then return "Stealthed Cast " .. PoolingAbility:Name() end end end end HR.Cast(S.PoolEnergy) return "Stealthed Pooling" end -- actions+=/call_action_list,name=stealth_cds,if=energy.deficit<=variable.stealth_threshold if Player:EnergyPredicted() >= StealthEnergyRequired then ShouldReturn = Stealth_CDs() if ShouldReturn then return "Stealth CDs: " .. ShouldReturn end end -- actions+=/call_action_list,name=finish,if=variable.effective_combo_points>=cp_max_spend -- # Finish at maximum or close to maximum combo point value -- actions+=/call_action_list,name=finish,if=combo_points.deficit<=1+buff.the_rotten.up|fight_remains<=1&variable.effective_combo_points>=3 -- # Finish at 4+ against 4 targets (outside stealth) -- actions+=/call_action_list,name=finish,if=spell_targets.shuriken_storm>=(4-talent.seal_fate)&variable.effective_combo_points>=4 if EffectiveComboPoints >= Rogue.CPMaxSpend() or (ComboPointsDeficit <= (1 + num(Player:BuffUp(S.TheRottenBuff))) or (HL.BossFilteredFightRemains("<", 2) and EffectiveComboPoints >= 3)) or (MeleeEnemies10yCount >= (4 - num(S.SealFate:IsAvailable())) and EffectiveComboPoints >= 4) then ShouldReturn = Finish() if ShouldReturn then return "Finish: " .. ShouldReturn end else -- NOTE: Duplicated stealth_cds line from above since both this and build have the same energy threshold if condition -- If we aren't finishing in between, we'll be suggesting to pool something and re-process with StealthEnergyRequired -- # Consider using a Stealth CD when reaching the energy threshold, called with params to register potential pooling -- actions+=/call_action_list,name=stealth_cds,if=energy.deficit<=variable.stealth_threshold ShouldReturn = Stealth_CDs(StealthEnergyRequired) if ShouldReturn then return "Stealth CDs: " .. ShouldReturn end -- # Use a builder when reaching the energy threshold -- actions+=/call_action_list,name=build,if=energy.deficit<=variable.stealth_threshold ShouldReturn = Build(StealthEnergyRequired) if ShouldReturn then return "Build: " .. ShouldReturn end end if HR.CDsON() then -- # Lowest priority in all of the APL because it causes a GCD -- actions+=/arcane_torrent,if=energy.deficit>=15+energy.regen if S.ArcaneTorrent:IsReady() and TargetInMeleeRange and Player:EnergyDeficitPredicted() > 15 + Player:EnergyRegen() then if HR.Cast(S.ArcaneTorrent, Settings.Commons.GCDasOffGCD.Racials) then return "Cast Arcane Torrent" end end -- actions+=/arcane_pulse if S.ArcanePulse:IsReady() and TargetInMeleeRange then if HR.Cast(S.ArcanePulse, Settings.Commons.GCDasOffGCD.Racials) then return "Cast Arcane Pulse" end end -- actions+=/lights_judgment if S.LightsJudgment:IsReady() then if HR.Cast(S.LightsJudgment, Settings.Commons.GCDasOffGCD.Racials) then return "Cast Lights Judgment" end end -- actions+=/bag_of_tricks if S.BagofTricks:IsReady() then if HR.Cast(S.BagofTricks, Settings.Commons.GCDasOffGCD.Racials) then return "Cast Bag of Tricks" end end end -- Show what ever was first stored for pooling if PoolingFinisher then SetPoolingAbility(PoolingFinisher) end if PoolingAbility and TargetInMeleeRange then if type(PoolingAbility) == "table" and #PoolingAbility > 1 then if HR.CastQueuePooling(Player:EnergyTimeToX(PoolingEnergy), unpack(PoolingAbility)) then return "Macro pool towards ".. PoolingAbility[1]:Name() .. " at " .. PoolingEnergy end elseif PoolingAbility:IsCastable() then PoolingEnergy = mathmax(PoolingEnergy, PoolingAbility:Cost()) if HR.CastPooling(PoolingAbility, Player:EnergyTimeToX(PoolingEnergy)) then return "Pool towards: " .. PoolingAbility:Name() .. " at " .. PoolingEnergy end end end -- Shuriken Toss Out of Range if S.ShurikenToss:IsCastable() and Target:IsInRange(30) and not TargetInAoERange and not Player:StealthUp(true, true) and not Player:BuffUp(S.Sprint) and Player:EnergyDeficitPredicted() < 20 and (ComboPointsDeficit >= 1 or Player:EnergyTimeToMax() <= 1.2) then if HR.CastPooling(S.ShurikenToss) then return "Cast Shuriken Toss" end end end end local function Init () -- Nothing end HR.SetAPL(261, APL, Init) -- Last Update: 2023-05-19 -- # Executed before combat begins. Accepts non-harmful actions only. -- actions.precombat=apply_poison -- actions.precombat+=/flask -- actions.precombat+=/augmentation -- actions.precombat+=/food -- # Snapshot raid buffed stats before combat begins and pre-potting is done. -- actions.precombat+=/snapshot_stats -- actions.precombat+=/stealth -- actions.precombat+=/marked_for_death,precombat_seconds=15 -- actions.precombat+=/variable,name=algethar_puzzle_box_precombat_cast,value=3 -- actions.precombat+=/use_item,name=algethar_puzzle_box -- actions.precombat+=/slice_and_dice,precombat_seconds=1 -- # Executed every time the actor is available. -- # Restealth if possible (no vulnerable enemies in combat) -- actions=stealth -- # Interrupt on cooldown to allow simming interactions with that -- actions+=/kick -- # Used to determine whether cooldowns wait for SnD based on targets. -- actions+=/variable,name=snd_condition,value=buff.slice_and_dice.up|spell_targets.shuriken_storm>=cp_max_spend -- # Check to see if the next CP (in the event of a ShT proc) is Animacharged -- actions+=/variable,name=is_next_cp_animacharged,if=talent.echoing_reprimand.enabled,value=combo_points=1&buff.echoing_reprimand_2.up|combo_points=2&buff.echoing_reprimand_3.up|combo_points=3&buff.echoing_reprimand_4.up|combo_points=4&buff.echoing_reprimand_5.up -- # Account for ShT reaction time by ignoring low-CP animacharged matches in the 0.5s preceeding a potential ShT proc -- actions+=/variable,name=effective_combo_points,value=effective_combo_points -- actions+=/variable,name=effective_combo_points,if=talent.echoing_reprimand.enabled&effective_combo_points>combo_points&combo_points.deficit>2&time_to_sht.4.plus<0.5&!variable.is_next_cp_animacharged,value=combo_points -- # Check CDs at first -- actions+=/call_action_list,name=cds -- # Apply Slice and Dice at 4+ CP if it expires within the next GCD or is not up -- actions+=/slice_and_dice,if=spell_targets.shuriken_storm6&combo_points>=4 -- # Run fully switches to the Stealthed Rotation (by doing so, it forces pooling if nothing is available). -- actions+=/run_action_list,name=stealthed,if=stealthed.all -- # Only change rotation if we have priority_rotation set. -- actions+=/variable,name=priority_rotation,value=priority_rotation -- # Used to define when to use stealth CDs or builders -- actions+=/variable,name=stealth_threshold,value=25+talent.vigor.enabled*20+talent.master_of_shadows.enabled*20+talent.shadow_focus.enabled*25+talent.alacrity.enabled*20+25*(spell_targets.shuriken_storm>=4) -- # Consider using a Stealth CD when reaching the energy threshold -- actions+=/call_action_list,name=stealth_cds,if=energy.deficit<=variable.stealth_threshold -- actions+=/call_action_list,name=finish,if=variable.effective_combo_points>=cp_max_spend -- # Finish at maximum or close to maximum combo point value -- actions+=/call_action_list,name=finish,if=combo_points.deficit<=1+buff.the_rotten.up|fight_remains<=1&variable.effective_combo_points>=3 -- # Finish at 4+ against 4 targets (outside stealth) -- actions+=/call_action_list,name=finish,if=spell_targets.shuriken_storm>=(4-talent.seal_fate)&variable.effective_combo_points>=4 -- # Use a builder when reaching the energy threshold -- actions+=/call_action_list,name=build,if=energy.deficit<=variable.stealth_threshold -- # Lowest priority in all of the APL because it causes a GCD -- actions+=/arcane_torrent,if=energy.deficit>=15+energy.regen -- actions+=/arcane_pulse -- actions+=/lights_judgment -- actions+=/bag_of_tricks -- # Builders -- actions.build=shuriken_storm,if=spell_targets>=2+(talent.gloomblade&buff.lingering_shadow.remains>=6|buff.perforated_veins.up) -- # Build immediately unless the next CP is Animacharged and we won't cap energy waiting for it. -- actions.build+=/variable,name=anima_helper,value=!talent.echoing_reprimand.enabled|!(variable.is_next_cp_animacharged&(time_to_sht.3.plus<0.5|time_to_sht.4.plus<1)&energy<60) -- actions.build+=/gloomblade,if=variable.anima_helper -- actions.build+=/backstab,if=variable.anima_helper -- # Cooldowns -- actions.cds=variable,name=rotten_condition,value=!buff.premeditation.up&spell_targets.shuriken_storm=1|!talent.the_rotten|spell_targets.shuriken_storm>1 -- # Cooldowns Use Dance off-gcd before the first Shuriken Storm from Tornado comes in. -- actions.cds+=/shadow_dance,use_off_gcd=1,if=!buff.shadow_dance.up&buff.shuriken_tornado.up&buff.shuriken_tornado.remains<=3.5 -- # (Unless already up because we took Shadow Focus) use Symbols off-gcd before the first Shuriken Storm from Tornado comes in. -- actions.cds+=/symbols_of_death,use_off_gcd=1,if=buff.shuriken_tornado.up&buff.shuriken_tornado.remains<=3.5 -- # Vanish for Shadowstrike with Danse Macabre at adaquate stacks -- actions.cds+=/vanish,if=buff.danse_macabre.stack>3&combo_points<=2&(cooldown.secret_technique.remains>=30|!talent.secret_technique) -- # Cold Blood on 5 combo points when not playing Secret Technique -- actions.cds+=/cold_blood,if=!talent.secret_technique&combo_points>=5 -- actions.cds+=/flagellation,target_if=max:target.time_to_die,if=variable.snd_condition&combo_points>=5&target.time_to_die>10 -- # Pool for Tornado pre-SoD with ShD ready when not running SF. -- actions.cds+=/pool_resource,for_next=1,if=talent.shuriken_tornado.enabled&!talent.shadow_focus.enabled -- # Use Tornado pre SoD when we have the energy whether from pooling without SF or just generally. -- actions.cds+=/shuriken_tornado,if=spell_targets.shuriken_storm<=1&energy>=60&variable.snd_condition&cooldown.symbols_of_death.up&cooldown.shadow_dance.charges>=1&(!talent.flagellation.enabled&!cooldown.flagellation.up|buff.flagellation_buff.up|spell_targets.shuriken_storm>=5)&combo_points<=2&!buff.premeditation.up -- actions.cds+=/sepsis,if=variable.snd_condition&combo_points.deficit>=1&target.time_to_die>=16 -- # Use Symbols on cooldown (after first SnD) unless we are going to pop Tornado and do not have Shadow Focus. -- actions.cds+=/symbols_of_death,if=(buff.symbols_of_death.remains<=3&!cooldown.shadow_dance.ready|!set_bonus.tier30_2pc)&variable.rotten_condition&variable.snd_condition&(!talent.flagellation&(combo_points<=1|!talent.the_rotten)|cooldown.flagellation.remains>10|cooldown.flagellation.up&combo_points>=5) -- # If adds are up, snipe the one with lowest TTD. Use when dying faster than CP deficit or not stealthed without any CP. -- actions.cds+=/marked_for_death,line_cd=1.5,target_if=min:target.time_to_die,if=raid_event.adds.up&(target.time_to_die=cp_max_spend) -- # If no adds will die within the next 30s, use MfD on boss without any CP. -- actions.cds+=/marked_for_death,if=raid_event.adds.in>30-raid_event.adds.duration&combo_points.deficit>=cp_max_spend -- actions.cds+=/shadow_blades,if=variable.snd_condition&combo_points.deficit>=2&target.time_to_die>=10&(dot.sepsis.ticking|cooldown.sepsis.remains<=8|!talent.sepsis)|fight_remains<=20 -- actions.cds+=/echoing_reprimand,if=variable.snd_condition&combo_points.deficit>=3&(variable.priority_rotation|spell_targets.shuriken_storm<=4|talent.resounding_clarity)&(buff.shadow_dance.up|!talent.danse_macabre) -- # With SF, if not already done, use Tornado with SoD up. -- actions.cds+=/shuriken_tornado,if=variable.snd_condition&buff.symbols_of_death.up&combo_points<=2&(!buff.premeditation.up|spell_targets.shuriken_storm>4) -- actions.cds+=/shuriken_tornado,if=cooldown.shadow_dance.ready&!stealthed.all&spell_targets.shuriken_storm>=3&!talent.flagellation.enabled -- actions.cds+=/shadow_dance,if=!buff.shadow_dance.up&fight_remains<=8+talent.subterfuge.enabled -- actions.cds+=/thistle_tea,if=(cooldown.symbols_of_death.remains>=3|buff.symbols_of_death.up)&!buff.thistle_tea.up&(energy.deficit>=100&(combo_points.deficit>=2|spell_targets.shuriken_storm>=3)|cooldown.thistle_tea.charges_fractional>=2.75&buff.shadow_dance.up)|buff.shadow_dance.remains>=4&!buff.thistle_tea.up&spell_targets.shuriken_storm>=3|!buff.thistle_tea.up&fight_remains<=(6*cooldown.thistle_tea.charges) -- actions.cds+=/potion,if=buff.bloodlust.react|fight_remains<30|buff.symbols_of_death.up&(buff.shadow_blades.up|cooldown.shadow_blades.remains<=10) -- actions.cds+=/blood_fury,if=buff.symbols_of_death.up -- actions.cds+=/berserking,if=buff.symbols_of_death.up -- actions.cds+=/fireblood,if=buff.symbols_of_death.up -- actions.cds+=/ancestral_call,if=buff.symbols_of_death.up -- actions.cds+=/use_item,name=beacon_to_the_beyond,use_off_gcd=1,if=!stealthed.all&(buff.deeper_daggers.up|!talent.deeper_daggers)&(!raid_event.adds.up|!equipped.stormeaters_boon|trinket.stormeaters_boon.cooldown.remains>20) -- actions.cds+=/use_item,name=manic_grieftorch,use_off_gcd=1,if=!stealthed.all&(!raid_event.adds.up|!equipped.stormeaters_boon|trinket.stormeaters_boon.cooldown.remains>20) -- # Default fallback for usable items: Use with Symbols of Death. -- actions.cds+=/use_items,if=!stealthed.all|fight_remains<10 -- # Finishers While using Premeditation, avoid casting Slice and Dice when Shadow Dance is soon to be used, except for Kyrian -- actions.finish=variable,name=secret_condition,value=buff.shadow_dance.up&(buff.danse_macabre.stack>=3|!talent.danse_macabre)&(!buff.premeditation.up|spell_targets.shuriken_storm!=2) -- actions.finish+=/variable,name=premed_snd_condition,value=talent.premeditation.enabled&spell_targets.shuriken_storm<5 -- actions.finish+=/slice_and_dice,if=!variable.premed_snd_condition&spell_targets.shuriken_storm<6&!buff.shadow_dance.up&buff.slice_and_dice.remains=2) -- # Keep up Rupture if it is about to run out. -- actions.finish+=/rupture,if=(!variable.skip_rupture|variable.priority_rotation)&target.time_to_die-remains>6&refreshable -- # Refresh Rupture early for Finality -- actions.finish+=/rupture,if=!variable.skip_rupture&buff.finality_rupture.up&cooldown.shadow_dance.remains<12&cooldown.shadow_dance.charges_fractional<=1&spell_targets.shuriken_storm=1&(talent.dark_brew|talent.danse_macabre) -- # Sync Cold Blood with Secret Technique when possible -- actions.finish+=/cold_blood,if=variable.secret_condition&cooldown.secret_technique.ready -- actions.finish+=/secret_technique,if=variable.secret_condition&(!talent.cold_blood|cooldown.cold_blood.remains>buff.shadow_dance.remains-2) -- # Multidotting targets that will live for the duration of Rupture, refresh during pandemic. -- actions.finish+=/rupture,cycle_targets=1,if=!variable.skip_rupture&!variable.priority_rotation&spell_targets.shuriken_storm>=2&target.time_to_die>=(2*combo_points)&refreshable -- # Refresh Rupture early if it will expire during Symbols. Do that refresh if SoD gets ready in the next 5s. -- actions.finish+=/rupture,if=!variable.skip_rupture&remainscooldown.symbols_of_death.remains+5 -- actions.finish+=/black_powder,if=!variable.priority_rotation&spell_targets>=3|!used_for_danse&buff.shadow_dance.up&spell_targets.shuriken_storm=2&talent.danse_macabre -- actions.finish+=/eviscerate -- # Stealth Cooldowns Helper Variable -- actions.stealth_cds=variable,name=shd_threshold,value=cooldown.shadow_dance.charges_fractional>=0.75+talent.shadow_dance -- actions.stealth_cds+=/variable,name=rotten_threshold,value=!buff.the_rotten.up|spell_targets.shuriken_storm>1|combo_points<=2&buff.the_rotten.up&!set_bonus.tier30_2pc -- actions.stealth_cds+=/vanish,if=(!talent.danse_macabre|spell_targets.shuriken_storm>=3)&!variable.shd_threshold&combo_points.deficit>1&(cooldown.flagellation.remains>=60|!talent.flagellation|fight_remains<=(30*cooldown.vanish.charges)) -- # Pool for Shadowmeld + Shadowstrike unless we are about to cap on Dance charges. Only when Find Weakness is about to run out. -- actions.stealth_cds+=/pool_resource,for_next=1,extra_amount=40,if=race.night_elf -- actions.stealth_cds+=/shadowmeld,if=energy>=40&energy.deficit>=10&!variable.shd_threshold&combo_points.deficit>4 -- # CP thresholds for entering Shadow Dance Default to start dance with 0 or 1 combo point -- actions.stealth_cds+=/variable,name=shd_combo_points,value=combo_points<=1 -- # Use stealth cooldowns with high combo points when playing shuriken tornado or with high target counts -- actions.stealth_cds+=/variable,name=shd_combo_points,value=combo_points.deficit<=1,if=spell_targets.shuriken_storm>(4-2*talent.shuriken_tornado.enabled)|variable.priority_rotation&spell_targets.shuriken_storm>=4 -- # Use stealth cooldowns on any combo point on 4 targets -- actions.stealth_cds+=/variable,name=shd_combo_points,value=1,if=spell_targets.shuriken_storm=(4-talent.seal_fate) -- # Dance during Symbols or above threshold. -- actions.stealth_cds+=/shadow_dance,if=(variable.shd_combo_points&(!talent.shadow_dance&buff.symbols_of_death.remains>=(2.2-talent.flagellation.enabled)|variable.shd_threshold)|talent.shadow_dance&cooldown.secret_technique.remains<=9&(spell_targets.shuriken_storm<=3|talent.danse_macabre)|buff.flagellation.up|buff.flagellation_persist.remains>=6|spell_targets.shuriken_storm>=4&cooldown.symbols_of_death.remains>10)&variable.rotten_threshold -- # Burn Dances charges if before the fight ends if SoD won't be ready in time. -- actions.stealth_cds+=/shadow_dance,if=variable.shd_combo_points&fight_remains=cp_max_spend -- # Finish earlier with Shuriken tornado up. -- actions.stealthed+=/call_action_list,name=finish,if=buff.shuriken_tornado.up&combo_points.deficit<=2 -- # Also safe to finish at 4+ CP with exactly 4 targets. (Same as outside stealth.) -- actions.stealthed+=/call_action_list,name=finish,if=spell_targets.shuriken_storm>=4-talent.seal_fate&variable.effective_combo_points>=4 -- # Finish at lower combo points if you are talented in DS, SS or Seal Fate -- actions.stealthed+=/call_action_list,name=finish,if=combo_points.deficit<=1+(talent.seal_fate|talent.deeper_stratagem|talent.secret_stratagem) -- # Use Gloomblade or Backstab when close to hitting max PV stacks -- actions.stealthed+=/gloomblade,if=buff.perforated_veins.stack>=5&spell_targets.shuriken_storm<3 -- actions.stealthed+=/backstab,if=buff.perforated_veins.stack>=5&spell_targets.shuriken_storm<3 -- actions.stealthed+=/shadowstrike,if=stealthed.sepsis&spell_targets.shuriken_storm<4 -- actions.stealthed+=/shuriken_storm,if=spell_targets>=3+buff.the_rotten.up&(!buff.premeditation.up|spell_targets>=7&!variable.priority_rotation) -- # Shadowstrike to refresh Find Weakness and to ensure we can carry over a full FW into the next SoD if possible. -- actions.stealthed+=/shadowstrike,if=debuff.find_weakness.remains<=1|cooldown.symbols_of_death.remains<18&debuff.find_weakness.remains