You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1049 lines
69 KiB

--- ============================ HEADER ============================
--- ======= LOCALIZE =======
-- 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
local IntToBool = HL.Utils.IntToBool
local ValueIsInArray = HL.Utils.ValueIsInArray
-- HeroRotation
local HR = HeroRotation
local AoEON = HR.AoEON
local CDsON = HR.CDsON
local Cast = HR.Cast
local CastPooling = HR.CastPooling
local CastLeftNameplate = HR.CastLeftNameplate
-- Num/Bool Helper Functions
local num = HR.Commons.Everyone.num
local bool = HR.Commons.Everyone.bool
-- Lua
local pairs = pairs
local mathfloor = math.floor
local mathmax = math.max
--- ============================ CONTENT ============================
--- ======= APL LOCALS =======
-- Commons
local Everyone = HR.Commons.Everyone
local Rogue = HR.Commons.Rogue
-- GUI Settings
local Settings = {
General = HR.GUISettings.General,
Commons = HR.GUISettings.APL.Rogue.Commons,
Commons2 = HR.GUISettings.APL.Rogue.Commons2,
Assassination = HR.GUISettings.APL.Rogue.Assassination
}
-- Spells
local S = Spell.Rogue.Assassination
-- Items
local I = Item.Rogue.Assassination
local OnUseExcludeTrinkets = {
I.AlgetharPuzzleBox,
}
-- Enemies
local MeleeRange, AoERange, TargetInMeleeRange, TargetInAoERange
local Enemies30y, MeleeEnemies10y, MeleeEnemies10yCount, MeleeEnemies5y
-- Rotation Variables
local ShouldReturn
local BleedTickTime, ExsanguinatedBleedTickTime = 2 * Player:SpellHaste(), 1 * Player:SpellHaste()
local ComboPoints, ComboPointsDeficit
local RuptureThreshold, CrimsonTempestThreshold, RuptureDMGThreshold, GarroteDMGThreshold, RuptureDurationThreshold, RuptureTickTime, GarroteTickTime
local PriorityRotation
local ExsanguinateSyncRemains, PoisonedBleeds, EnergyRegenCombined, EnergyTimeToMaxCombined, EnergyRegenSaturated, SingleTarget
local TrinketSyncSlot = 0
-- Covenant and Legendaries
local Equipment = Player:GetEquipment()
local TrinketItem1 = Equipment[13] and Item(Equipment[13]) or Item(0)
local TrinketItem2 = Equipment[14] and Item(Equipment[14]) or Item(0)
local function SetTrinketVariables ()
-- actions.precombat+=/variable,name=trinket_sync_slot,value=1,if=trinket.1.has_stat.any_dps&(!trinket.2.has_stat.any_dps|trinket.1.cooldown.duration>=trinket.2.cooldown.duration)
-- actions.precombat+=/variable,name=trinket_sync_slot,value=2,if=trinket.2.has_stat.any_dps&(!trinket.1.has_stat.any_dps|trinket.2.cooldown.duration>trinket.1.cooldown.duration)
if TrinketItem1:HasStatAnyDps() and (not TrinketItem2:HasStatAnyDps() or TrinketItem1:Cooldown() >= TrinketItem2:Cooldown()) then
TrinketSyncSlot = 1
elseif TrinketItem2:HasStatAnyDps() and (not TrinketItem1:HasStatAnyDps() or TrinketItem2:Cooldown() > TrinketItem1:Cooldown()) then
TrinketSyncSlot = 2
else
TrinketSyncSlot = 0
end
end
SetTrinketVariables()
HL:RegisterForEvent(function()
Equipment = Player:GetEquipment()
TrinketItem1 = Equipment[13] and Item(Equipment[13]) or Item(0)
TrinketItem2 = Equipment[14] and Item(Equipment[14]) or Item(0)
SetTrinketVariables()
end, "PLAYER_EQUIPMENT_CHANGED" )
-- Interrupts
local Interrupts = {
{ S.Blind, "Cast Blind (Interrupt)", function () return true end },
{ S.KidneyShot, "Cast Kidney Shot (Interrupt)", function () return ComboPoints > 0 end }
}
-- Spells Damage
S.Envenom:RegisterDamageFormula(
-- Envenom DMG Formula:
-- AP * CP * Env_APCoef * Aura_M * ToxicB_M * DS_M * Mastery_M * Versa_M
function ()
return
-- Attack Power
Player:AttackPowerDamageMod() *
-- Combo Points
ComboPoints *
-- Envenom AP Coef
0.22 *
-- Aura Multiplier (SpellID: 137037)
1.0 *
-- Shiv Multiplier
(Target:DebuffUp(S.ShivDebuff) and 1.3 or 1) *
-- Deeper Stratagem Multiplier
(S.DeeperStratagem:IsAvailable() and 1.05 or 1) *
-- Mastery Finisher Multiplier
(1 + Player:MasteryPct()/100) *
-- Versatility Damage Multiplier
(1 + Player:VersatilityDmgPct()/100)
end
)
S.Mutilate:RegisterDamageFormula(
function ()
return
-- Attack Power (MH Factor + OH Factor)
(Player:AttackPowerDamageMod() + Player:AttackPowerDamageMod(true)) *
-- Mutilate Coefficient
0.485 *
-- Aura Multiplier (SpellID: 137037)
1.0 *
-- Versatility Damage Multiplier
(1 + Player:VersatilityDmgPct()/100)
end
)
-- Master Assassin Remains Check
local function MasterAssassinRemains ()
-- Currently stealthed (i.e. Aura)
if Player:BuffRemains(S.MasterAssassinBuff) == 9999 then
return Player:GCDRemains() + 3
end
-- Broke stealth recently (i.e. Buff)
return Player:BuffRemains(S.MasterAssassinBuff)
end
-- Improved Garrote Remains Check
local function ImprovedGarroteRemains ()
-- Currently stealthed (i.e. Aura)
if Player:BuffUp(S.ImprovedGarroteAura) then
return Player:GCDRemains() + 3
end
-- Broke stealth recently (i.e. Buff)
return Player:BuffRemains(S.ImprovedGarroteBuff)
end
local function ComputeImprovedGarrotePMultiplier ()
if S.ImprovedGarrote:IsAvailable() and (Player:BuffUp(S.ImprovedGarroteAura, nil, true)
or Player:BuffUp(S.ImprovedGarroteBuff, nil, true) or Player:BuffUp(S.SepsisBuff, nil, true)) then
return 1.5
end
return 1
end
S.Garrote:RegisterPMultiplier(ComputeImprovedGarrotePMultiplier)
--- ======= HELPERS =======
-- Check if the Priority Rotation variable should be set
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
end
end
return false
end
-- actions+=/variable,name=exsang_sync_remains,op=setif,condition=cooldown.deathmark.remains>cooldown.exsanguinate.remains&cooldown.deathmark.remains<fight_remains,value=cooldown.deathmark.remains,value_else=cooldown.exsanguinate.remains
local function ExsangSyncRemains()
if S.Deathmark:CooldownRemains() > S.Exsanguinate:CooldownRemains()
and (HL.BossFightRemainsIsNotValid() or HL.BossFilteredFightRemains(">", S.Deathmark:CooldownRemains())) then
return S.Deathmark:CooldownRemains()
end
return S.Exsanguinate:CooldownRemains()
end
-- Custom Override for Handling 4pc Pandemics
local function IsDebuffRefreshable(TargetUnit, Spell, PandemicThreshold)
local PandemicThreshold = PandemicThreshold or Spell:PandemicThreshold()
--if Tier284pcEquipped and TargetUnit:DebuffUp(S.Vendetta) then
-- PandemicThreshold = PandemicThreshold * 0.5
--end
return TargetUnit:DebuffRefreshable(Spell, PandemicThreshold)
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
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
CastLeftNameplate(BestUnit, DoTSpell)
end
end
end
-- Target If handler
-- Mode is "min", "max", or "first"
-- ModeEval the target_if condition (function with a target as param)
-- IfEval the condition on the resulting target (function with a target as param)
local function CheckTargetIfTarget(Mode, ModeEvaluation, IfEvaluation)
-- First mode: Only check target if necessary
local TargetsModeValue = ModeEvaluation(Target)
if Mode == "first" and TargetsModeValue ~= 0 then
return Target
end
local BestUnit, BestValue = nil, 0
local function RunTargetIfCycler(Enemies)
for _, CycleUnit in pairs(Enemies) do
local ValueForUnit = ModeEvaluation(CycleUnit)
if not BestUnit and Mode == "first" then
if ValueForUnit ~= 0 then
BestUnit, BestValue = CycleUnit, ValueForUnit
end
elseif Mode == "min" then
if not BestUnit or ValueForUnit < BestValue then
BestUnit, BestValue = CycleUnit, ValueForUnit
end
elseif Mode == "max" then
if not BestUnit or ValueForUnit > BestValue then
BestUnit, BestValue = CycleUnit, ValueForUnit
end
end
-- Same mode value, prefer longer TTD
if BestUnit and ValueForUnit == BestValue and CycleUnit:TimeToDie() > BestUnit:TimeToDie() then
BestUnit, BestValue = CycleUnit, ValueForUnit
end
end
end
-- Prefer melee cycle units over ranged
RunTargetIfCycler(MeleeEnemies5y)
if Settings.Commons.RangedMultiDoT then
RunTargetIfCycler(MeleeEnemies10y)
end
-- Prefer current target if equal mode value results to prevent "flickering"
if BestUnit and BestValue == TargetsModeValue and IfEvaluation(Target) then
return Target
end
if BestUnit and IfEvaluation(BestUnit) then
return BestUnit
end
return nil
end
-- # Determine if we should be be casting our pre-Exsanguinate Rupture with Echoing Reprimand CP
local function ExsanguinateRuptureCP ()
-- actions.precombat+=/variable,name=exsanguinate_rupture_cp,value=cp_max_spend<?(talent.resounding_clarity*7)
return S.ResoundingClarity:IsAvailable() and 7 or Rogue.CPMaxSpend()
end
local function CheckWillWasteCooldown(ThisCooldownLength, OtherCooldownRemains, EffectDuration)
local FightRemains = Target:TimeToDie()
if not HL.BossFightRemainsIsNotValid() then
FightRemains = HL.BossFightRemains()
elseif FightRemains < EffectDuration then
return false -- Bail out if we are not in a boss encounter and fighting a low-HP target
end
-- e.g. if=floor((fight_remains-30)%cooldown)>floor((fight_remains-30-cooldown.vendetta.remains)%cooldown)
if mathfloor((FightRemains - EffectDuration) / ThisCooldownLength) >
mathfloor((FightRemains - EffectDuration - OtherCooldownRemains) / ThisCooldownLength) then
return true
end
return false
end
-- Serrated Bone Spike Cycle Targets
-- actions.direct+=/serrated_bone_spike,target_if=min:target.time_to_die+(dot.serrated_bone_spike_dot.ticking*600),if=variable.use_filler&!dot.serrated_bone_spike_dot.ticking
local function EvaluateSBSTargetIfConditionCondition(TargetUnit)
if TargetUnit:DebuffUp(S.SerratedBoneSpikeDebuff) then
return 1000000 -- Random big number
end
return TargetUnit:TimeToDie()
end
local function EvaluateSBSCondition(TargetUnit)
return not TargetUnit:DebuffUp(S.SerratedBoneSpikeDebuff)
end
--- ======= ACTION LISTS =======
local function Racials ()
-- actions.cds+=/blood_fury,if=debuff.vendetta.up
if S.BloodFury:IsCastable() then
if Cast(S.BloodFury, Settings.Commons.OffGCDasOffGCD.Racials) then return "Cast Blood Fury" end
end
-- actions.cds+=/berserking,if=debuff.vendetta.up
if S.Berserking:IsCastable() then
if Cast(S.Berserking, Settings.Commons.OffGCDasOffGCD.Racials) then return "Cast Berserking" end
end
-- actions.cds+=/fireblood,if=debuff.vendetta.up
if S.Fireblood:IsCastable() then
if Cast(S.Fireblood, Settings.Commons.OffGCDasOffGCD.Racials) then return "Cast Fireblood" end
end
-- actions.cds+=/ancestral_call,if=debuff.vendetta.up
if S.AncestralCall:IsCastable() then
if Cast(S.AncestralCall, Settings.Commons.OffGCDasOffGCD.Racials) then return "Cast Ancestral Call" end
end
return false
end
-- # Vanish Handling
local function Vanish ()
if S.Vanish:IsCastable() and not Player:IsTanking(Target) then
if S.ImprovedGarrote:IsAvailable() and S.Garrote:CooldownUp() and not Rogue.Exsanguinated(Target, S.Garrote)
and (Target:PMultiplier(S.Garrote) <= 1 or IsDebuffRefreshable(Target, S.Garrote)) then
-- actions.vanish+=/vanish,if=talent.improved_garrote&cooldown.garrote.up&!exsanguinated.garrote&(dot.garrote.pmultiplier<=1|dot.garrote.refreshable)&(debuff.deathmark.up|cooldown.deathmark.remains<4)&combo_points.deficit>=(spell_targets.fan_of_knives>?4)
if (S.Deathmark:AnyDebuffUp() or S.Deathmark:CooldownRemains() < 4) and ComboPointsDeficit >= math.min(MeleeEnemies10yCount, 4) then
-- actions.cds+=/pool_resource,for_next=1,extra_amount=45
if Settings.Commons.ShowPooling and Player:EnergyPredicted() < 45 then
if Cast(S.PoolEnergy) then return "Pool for Vanish (Garrote Deathmark)" end
end
if Cast(S.Vanish, Settings.Commons.OffGCDasOffGCD.Vanish) then return "Cast Vanish (Garrote Deathmark)" end
end
-- actions.vanish+=/vanish,if=talent.improved_garrote&cooldown.garrote.up&!exsanguinated.garrote&(dot.garrote.pmultiplier<=1|dot.garrote.refreshable)&spell_targets.fan_of_knives>(3-talent.indiscriminate_carnage)&(!talent.indiscriminate_carnage|cooldown.indiscriminate_carnage.ready)
if MeleeEnemies10yCount > (3 - BoolToInt(S.IndiscriminateCarnage:IsAvailable())) and (not S.IndiscriminateCarnage:IsAvailable() or S.IndiscriminateCarnage:CooldownUp()) then
-- actions.cds+=/pool_resource,for_next=1,extra_amount=45
if Settings.Commons.ShowPooling and Player:EnergyPredicted() < 45 then
if Cast(S.PoolEnergy) then return "Pool for Vanish (Garrote)" end
end
if Cast(S.Vanish, Settings.Commons.OffGCDasOffGCD.Vanish) then return "Cast Vanish (Garrote)" end
end
end
-- actions.vanish+=/vanish,if=!talent.improved_garrote&talent.master_assassin&!dot.rupture.refreshable&dot.garrote.remains>3&debuff.deathmark.up&(debuff.shiv.up|debuff.deathmark.remains<4|dot.sepsis.ticking)&dot.sepsis.remains<3
if not S.ImprovedGarrote:IsAvailable() and S.MasterAssassin:IsAvailable() and not IsDebuffRefreshable(Target, S.Rupture) and Target:DebuffRemains(S.Garrote) > 3
and Target:DebuffUp(S.Deathmark) and (Target:DebuffUp(S.ShivDebuff) or Target:DebuffRemains(S.Deathmark) < 4 or Target:DebuffUp(S.Sepsis)) and Target:DebuffRemains(S.Sepsis) < 3 then
if Cast(S.Vanish, Settings.Commons.OffGCDasOffGCD.Vanish) then return "Cast Vanish (Master Assassin)" end
end
end
if S.ShadowDance:IsCastable() then
-- actions.vanish+=/shadow_dance,if=talent.improved_garrote&cooldown.garrote.up&!exsanguinated.garrote&(dot.garrote.pmultiplier<=1|dot.garrote.refreshable)&(debuff.deathmark.up|cooldown.deathmark.remains<12|cooldown.deathmark.remains>60)&combo_points.deficit>=(spell_targets.fan_of_knives>?4)
if S.ImprovedGarrote:IsAvailable() and S.Garrote:CooldownUp() and not Rogue.Exsanguinated(Target, S.Garrote) and Target:PMultiplier(S.Garrote) <= 1
and (S.Deathmark:AnyDebuffUp() or S.Deathmark:CooldownRemains() < 12 or S.Deathmark:CooldownRemains() > 60) and ComboPointsDeficit >= math.min(MeleeEnemies10yCount, 4) then
if Cast(S.ShadowDance, Settings.Commons.OffGCDasOffGCD.ShadowDance) then return "Cast Shadow Dance (Garrote)" end
end
-- actions.vanish+=/shadow_dance,if=!talent.improved_garrote&talent.master_assassin&!dot.rupture.refreshable&dot.garrote.remains>3&(debuff.deathmark.up|cooldown.deathmark.remains>60)&(debuff.shiv.up|debuff.deathmark.remains<4|dot.sepsis.ticking)&dot.sepsis.remains<3
if not S.ImprovedGarrote:IsAvailable() and S.MasterAssassin:IsAvailable() and not IsDebuffRefreshable(Target, S.Rupture)
and Target:DebuffRemains(S.Garrote) > 3 and (Target:DebuffUp(S.Deathmark) or S.Deathmark:CooldownRemains() > 60)
and (Target:DebuffUp(S.ShivDebuff) or Target:DebuffRemains(S.Deathmark) < 4 or Target:DebuffUp(S.Sepsis)) and Target:DebuffRemains(S.Sepsis) < 3 then
if Cast(S.ShadowDance, Settings.Commons.OffGCDasOffGCD.ShadowDance) then return "Cast Shadow Dance (Master Assassin)" end
end
end
end
-- # Cooldowns
local function CDs ()
if S.MarkedforDeath:IsCastable() then
-- actions.cds+=/marked_for_death,target_if=min:target.time_to_die,if=raid_event.adds.up&(target.time_to_die<combo_points.deficit*1.5|combo_points.deficit>=cp_max_spend)
if Target:FilteredTimeToDie("<", Player:ComboPointsDeficit() * 1.5) then
if Cast(S.MarkedforDeath, Settings.Commons.OffGCDasOffGCD.MarkedforDeath) then return "Cast Marked for Death" end
end
-- actions.cds+=/marked_for_death,if=raid_event.adds.in>30-raid_event.adds.duration&combo_points.deficit>=cp_max_spend
if ComboPointsDeficit >= Rogue.CPMaxSpend() then
if not Settings.Commons.STMfDAsDPSCD then
HR.CastSuggested(S.MarkedforDeath)
elseif HR.CDsON() then
if Cast(S.MarkedforDeath, Settings.Commons.OffGCDasOffGCD.MarkedforDeath) then return "Cast Marked for Death" end
end
end
end
if not TargetInAoERange then
return
end
if not Player:StealthUp(true, false) and HR.CDsON() then
-- actions.cds+=/sepsis,if=!stealthed.rogue&!stealthed.improved_garrote&(!talent.improved_garrote&dot.garrote.ticking|talent.improved_garrote&cooldown.garrote.up)&(target.time_to_die>10|fight_remains<10)
if S.Sepsis:IsReady() and ImprovedGarroteRemains() == 0 and (S.ImprovedGarrote:IsAvailable() and S.Garrote:CooldownUp() or Target:DebuffUp(S.Garrote))
and (Target:FilteredTimeToDie(">", 10) or HL.BossFilteredFightRemains("<=", 10)) then
if Cast(S.Sepsis, nil, Settings.Commons.CovenantDisplayStyle) then return "Cast Sepsis" end
end
-- actions.cds+=/use_item,name=algethar_puzzle_box,use_off_gcd=1,if=(!talent.exsanguinate|cooldown.exsanguinate.remains>15|exsanguinated.rupture|exsanguinated.garrote)&dot.rupture.ticking&cooldown.deathmark.remains<2|fight_remains<=22
-- actions.cds+=/use_items,slots=trinket1,if=(variable.trinket_sync_slot=1&(debuff.deathmark.up|fight_remains<=20)|(variable.trinket_sync_slot=2&(!trinket.2.cooldown.ready|!debuff.deathmark.up&cooldown.deathmark.remains>20))|!variable.trinket_sync_slot)
-- actions.cds+=/use_items,slots=trinket2,if=(variable.trinket_sync_slot=2&(debuff.deathmark.up|fight_remains<=20)|(variable.trinket_sync_slot=1&(!trinket.1.cooldown.ready|!debuff.deathmark.up&cooldown.deathmark.remains>20))|!variable.trinket_sync_slot)
if Settings.Commons.UseTrinkets then
if I.AlgetharPuzzleBox:IsEquippedAndReady() and (((not S.Exsanguinate:IsAvailable() or S.Exsanguinate:CooldownRemains() > 15
or Rogue.Exsanguinated(Target, S.Rupture) or Rogue.Exsanguinated(Target, S.Garrote)) and Target:DebuffUp(S.Rupture)
and S.Deathmark:CooldownRemains() <= 2) or HL.BossFilteredFightRemains("<", 22)) then
if HR.Cast(I.AlgetharPuzzleBox, nil, Settings.Commons.TrinketDisplayStyle) then return "Algethar Puzzle Box"; end
end
if TrinketItem1:IsReady() and not Player:IsItemBlacklisted(TrinketItem1) and not ValueIsInArray(OnUseExcludeTrinkets, TrinketItem1:ID())
and (TrinketSyncSlot == 1 and (S.Deathmark:AnyDebuffUp() or HL.BossFilteredFightRemains("<", 20))
or (TrinketSyncSlot == 2 and (not TrinketItem2:IsReady() or not S.Deathmark:AnyDebuffUp() and S.Deathmark:CooldownRemains() > 20)) or TrinketSyncSlot == 0) then
if Cast(TrinketItem1, nil, Settings.Commons.TrinketDisplayStyle) then return "Trinket 1"; end
elseif TrinketItem2:IsReady() and not Player:IsItemBlacklisted(TrinketItem2) and not ValueIsInArray(OnUseExcludeTrinkets, TrinketItem2:ID())
and (TrinketSyncSlot == 2 and (S.Deathmark:AnyDebuffUp() or HL.BossFilteredFightRemains("<", 20))
or (TrinketSyncSlot == 1 and (not TrinketItem1:IsReady() or not S.Deathmark:AnyDebuffUp() and S.Deathmark:CooldownRemains() > 20)) or TrinketSyncSlot == 0) then
if Cast(TrinketItem2, nil, Settings.Commons.TrinketDisplayStyle) then return "Trinket 2"; end
end
end
if S.Deathmark:IsCastable() then
-- actions.cds+=/variable,name=deathmark_exsanguinate_condition,value=!talent.exsanguinate|cooldown.exsanguinate.remains>15|exsanguinated.rupture|exsanguinated.garrote
-- actions.cds+=/variable,name=deathmark_ma_condition,value=!talent.master_assassin.enabled|dot.garrote.ticking
-- actions.cds+=/variable,name=deathmark_condition,value=!stealthed.rogue&dot.rupture.ticking&!debuff.deathmark.up&variable.deathmark_exsanguinate_condition&variable.deathmark_ma_condition
-- actions.cds+=/deathmark,if=variable.deathmark_condition
if Target:DebuffUp(S.Rupture) and not S.Deathmark:AnyDebuffUp()
and (not S.Exsanguinate:IsAvailable() or S.Exsanguinate:CooldownRemains() > 15 or Rogue.Exsanguinated(Target, S.Rupture) or Rogue.Exsanguinated(Target, S.Garrote))
and (not S.MasterAssassin:IsAvailable() or Target:DebuffUp(S.Garrote)) then
if Cast(S.Deathmark, Settings.Assassination.OffGCDasOffGCD.Deathmark) then return "Cast Deathmark" end
end
end
-- actions.cds+=/kingsbane,if=(debuff.shiv.up|cooldown.shiv.remains<6)&buff.envenom.up&(cooldown.deathmark.remains>=50|dot.deathmark.ticking)
if S.Kingsbane:IsReady() and (Target:DebuffUp(S.ShivDebuff) or S.Shiv:CooldownRemains() < 6) and Player:BuffUp(S.Envenom)
and (S.Deathmark:CooldownRemains() >= 50 or Target:DebuffUp(S.Deathmark)) then
if Cast(S.Kingsbane, Settings.Assassination.GCDasOffGCD.Kingsbane) then return "Cast Kingsbane" end
end
-- actions.cds+=/variable,name=exsanguinate_condition,value=talent.exsanguinate&!stealthed.rogue&!stealthed.improved_garrote&!dot.deathmark.ticking&target.time_to_die>variable.exsang_sync_remains+4&variable.exsang_sync_remains<4
-- actions.cds+=/echoing_reprimand,if=talent.exsanguinate&talent.resounding_clarity&(variable.exsanguinate_condition&combo_points<=2&variable.exsang_sync_remains<=2&!dot.garrote.refreshable&dot.rupture.remains>9.6|variable.exsang_sync_remains>40)
-- actions.cds+=/exsanguinate,if=variable.exsanguinate_condition&(!dot.garrote.refreshable&dot.rupture.remains>4+4*variable.exsanguinate_rupture_cp|dot.rupture.remains*0.5>target.time_to_die)
if S.Exsanguinate:IsAvailable() then
if ImprovedGarroteRemains() == 0 and Target:DebuffDown(S.Deathmark) and Target:FilteredTimeToDie(">", ExsanguinateSyncRemains + 4) and ExsanguinateSyncRemains < 4 then
if S.ResoundingClarity:IsAvailable() and S.EchoingReprimand:IsReady()
and Player:ComboPoints() <= 2 and ExsanguinateSyncRemains <= 2
and not IsDebuffRefreshable(Target, S.Garrote) and Target:DebuffRemains(S.Rupture) > 9.6 then
if Cast(S.EchoingReprimand, nil, Settings.Commons.CovenantDisplayStyle, not TargetInMeleeRange) then return "Cast Echoing Reprimand (Exsang Sync)" end
end
if S.Exsanguinate:IsReady() and not IsDebuffRefreshable(Target, S.Garrote) and Target:DebuffRemains(S.Rupture) > 4 + 4 * ExsanguinateRuptureCP()
or Target:FilteredTimeToDie("<", Target:DebuffRemains(S.Rupture)*0.5) then
if Cast(S.Exsanguinate, Settings.Assassination.GCDasOffGCD.Exsanguinate) then return "Cast Exsanguinate" end
end
end
end
end
-- actions.cds+=/shiv,if=talent.kingsbane&!debuff.shiv.up&dot.kingsbane.ticking&dot.garrote.ticking&dot.rupture.ticking&(!talent.crimson_tempest.enabled|variable.single_target|dot.crimson_tempest.ticking)
-- actions.cds+=/shiv,if=talent.arterial_precision&!debuff.shiv.up&dot.garrote.ticking&dot.rupture.ticking&(debuff.deathmark.up|cooldown.shiv.charges_fractional>max_charges-0.5&cooldown.deathmark.remains>10)
-- actions.cds+=/shiv,if=talent.sepsis&!talent.kingsbane&!talent.arterial_precision&!debuff.shiv.up&dot.garrote.ticking&dot.rupture.ticking&((cooldown.sepsis.ready|cooldown.sepsis.remains>12)+(cooldown.deathmark.ready|cooldown.deathmark.remains>12)=2)
-- actions.cds+=/shiv,if=!talent.kingsbane&!talent.arterial_precision&!talent.sepsis&!debuff.shiv.up&dot.garrote.ticking&dot.rupture.ticking&(!talent.crimson_tempest.enabled|variable.single_target|dot.crimson_tempest.ticking)&(!talent.exsanguinate|variable.exsang_sync_remains>2)
if S.Shiv:IsCastable()
and not Target:DebuffUp(S.ShivDebuff) and Target:DebuffUp(S.Garrote) and Target:DebuffUp(S.Rupture) then
if S.Kingsbane:IsAvailable() then
if Target:DebuffUp(S.Kingsbane) and (not S.CrimsonTempest:IsAvailable() or SingleTarget or Target:DebuffUp(S.CrimsonTempest)) then
if Cast(S.Shiv, Settings.Assassination.GCDasOffGCD.Shiv) then return "Cast Shiv (Kingsbane)" end
end
end
if S.ArterialPrecision:IsAvailable() then
if S.Deathmark:AnyDebuffUp() or S.Shiv:ChargesFractional() > (S.Shiv:MaxCharges() - 0.5) and S.Deathmark:CooldownRemains() > 10 then
if Cast(S.Shiv, Settings.Assassination.GCDasOffGCD.Shiv) then return "Cast Shiv (Arterial Precision)" end
end
end
if not S.ArterialPrecision:IsAvailable() and not S.ArterialPrecision:IsAvailable() then
if S.Sepsis:IsAvailable() then
if (BoolToInt(S.Sepsis:CooldownUp() or S.Sepsis:CooldownRemains() > 12) + BoolToInt(S.Deathmark:CooldownUp() or S.Deathmark:CooldownRemains() > 12) == 2) then
if Cast(S.Shiv, Settings.Assassination.GCDasOffGCD.Shiv) then return "Cast Shiv (Sepsis)" end
end
else
if (not S.CrimsonTempest:IsAvailable() or SingleTarget or Target:DebuffUp(S.CrimsonTempest)) and (not S.Exsanguinate:IsAvailable() or ExsanguinateSyncRemains > 2) then
if Cast(S.Shiv, Settings.Assassination.GCDasOffGCD.Shiv) then return "Cast Shiv" end
end
end
end
end
-- actions.cds+=/thistle_tea,if=!buff.thistle_tea.up&(energy.deficit>=100|charges=3&(dot.kingsbane.ticking|debuff.deathmark.up)|fight_remains<charges*6)
if S.ThistleTea:IsCastable() and not Player:BuffUp(S.ThistleTea)
and (Player:EnergyDeficit() >= 100 + EnergyRegenCombined or S.ThistleTea:Charges() == 3 and (Target:DebuffUp(S.Kingsbane) or S.Deathmark:AnyDebuffUp())
or HL.BossFilteredFightRemains("<", S.ThistleTea:Charges() * 6)) then
if HR.Cast(S.ThistleTea, Settings.Commons.OffGCDasOffGCD.ThistleTea) then return "Cast Thistle Tea" end
end
if not HR.CDsON() then
return
end
-- actions.cds+=/indiscriminate_carnage,if=(spell_targets.fan_of_knives>desired_targets|spell_targets.fan_of_knives>1&raid_event.adds.in>60)&(!talent.improved_garrote|cooldown.vanish.remains>45)
if S.IndiscriminateCarnage:IsReady() and MeleeEnemies10yCount > 1 and (not S.ImprovedGarrote:IsAvailable() or S.Vanish:CooldownRemains() > 45) then
if Cast(S.IndiscriminateCarnage, Settings.Assassination.OffGCDasOffGCD.IndiscriminateCarnage) then return "Cast Indiscriminate Carnage" end
end
-- actions.cds=potion,if=buff.bloodlust.react|target.time_to_die<=60|debuff.vendetta.up&cooldown.vanish.remains<5
-- Racials
if S.Deathmark:AnyDebuffUp() and (not ShouldReturn or Settings.Commons.OffGCDasOffGCD.Racials) then
if ShouldReturn then
Racials()
else
ShouldReturn = Racials()
end
end
-- actions.cds+=/call_action_list,name=vanish,if=!stealthed.all&master_assassin_remains=0
if not Player:StealthUp(true, true) and ImprovedGarroteRemains() <= 0 and MasterAssassinRemains() <= 0 then
if ShouldReturn then
Vanish()
else
ShouldReturn = Vanish()
end
end
-- actions.cds+=/cold_blood,if=combo_points>=4
if S.ColdBlood:IsReady() and Player:DebuffDown(S.ColdBlood) and ComboPoints >= 4
and (Settings.Commons.OffGCDasOffGCD.ColdBlood or not ShouldReturn) then
if Cast(S.ColdBlood, Settings.Commons.OffGCDasOffGCD.ColdBlood) then return "Cast Cold Blood" end
end
return ShouldReturn
end
-- # Stealthed
local function Stealthed ()
-- actions.stealthed=indiscriminate_carnage,if=spell_targets.fan_of_knives>desired_targets|spell_targets.fan_of_knives>1&raid_event.adds.in>60
if S.IndiscriminateCarnage:IsReady() and MeleeEnemies10yCount > 1 then
if Cast(S.IndiscriminateCarnage, Settings.Assassination.OffGCDasOffGCD.IndiscriminateCarnage) then return "Cast Indiscriminate Carnage" end
end
if S.Garrote:IsCastable() and ImprovedGarroteRemains() > 0 then
-- actions.stealthed+=/garrote,target_if=min:remains,if=stealthed.improved_garrote&!will_lose_exsanguinate&(remains<12%exsanguinated_rate|pmultiplier<=1)&target.time_to_die-remains>2
local function GarroteTargetIfFunc(TargetUnit)
return TargetUnit:DebuffRemains(S.Garrote)
end
local function GarroteIfFunc(TargetUnit)
return not Rogue.WillLoseExsanguinate(TargetUnit, S.Garrote) and
(TargetUnit:PMultiplier(S.Garrote) <= 1 or TargetUnit:DebuffRemains(S.Garrote) < (12 / Rogue.ExsanguinatedRate(TargetUnit, S.Garrote)))
and (TargetUnit:FilteredTimeToDie(">", 2, -TargetUnit:DebuffRemains(S.Garrote)) or TargetUnit:TimeToDieIsNotValid())
and Rogue.CanDoTUnit(TargetUnit, GarroteDMGThreshold)
end
if HR.AoEON() then
local TargetIfUnit = CheckTargetIfTarget("min", GarroteTargetIfFunc, GarroteIfFunc)
if TargetIfUnit and TargetIfUnit:GUID() ~= Target:GUID() then
CastLeftNameplate(TargetIfUnit, S.Garrote)
end
end
if GarroteIfFunc(Target) then
-- actions.stealthed+=/pool_resource,for_next=1
if CastPooling(S.Garrote, nil, not TargetInMeleeRange) then return "Cast Garrote (Improved Garrote)" end
end
-- actions.stealthed+=/garrote,if=talent.exsanguinate.enabled&stealthed.improved_garrote&active_enemies=1&!will_lose_exsanguinate&(remains<18%exsanguinated_rate|pmultiplier<=1)&variable.exsang_sync_remains<18&improved_garrote_remains<1.3
if S.Exsanguinate:IsAvailable() and MeleeEnemies10yCount == 1 and not Rogue.WillLoseExsanguinate(Target, S.Garrote)
and (Target:DebuffRemains(S.Garrote) < (18 / Rogue.ExsanguinatedRate(Target, S.Garrote)) or Target:PMultiplier(S.Garrote) <= 1)
and ExsanguinateSyncRemains < 18 and ImprovedGarroteRemains() < 1.3 then
-- actions.stealthed+=/pool_resource,for_next=1
if CastPooling(S.Garrote, nil, not TargetInMeleeRange) then return "Pool for Garrote (Exsanguinate Refresh)" end
end
end
end
-- # Damage over time abilities
local function Dot ()
local SkipCycleGarrote, SkipCycleRupture, SkipRupture = false, false, false
if PriorityRotation then
-- actions.dot=variable,name=skip_cycle_garrote,value=priority_rotation&(dot.garrote.remains<cooldown.garrote.duration|variable.regen_saturated)
SkipCycleGarrote = MeleeEnemies10yCount > 3 and (Target:DebuffRemains(S.Garrote) < 6 or EnergyRegenSaturated)
-- actions.dot+=/variable,name=skip_cycle_rupture,value=priority_rotation&(debuff.shiv.up&spell_targets.fan_of_knives>2|variable.regen_saturated)
SkipCycleRupture = (Target:DebuffUp(S.ShivDebuff) and MeleeEnemies10yCount > 2) or EnergyRegenSaturated
end
-- actions.dot+=/variable,name=skip_rupture,value=0
-- SkipRupture = false
if HR.CDsON() and S.Exsanguinate:IsAvailable() and ExsanguinateSyncRemains < 2 then
-- actions.dot+=/garrote,if=talent.exsanguinate.enabled&!will_lose_exsanguinate&dot.garrote.pmultiplier<=1&cooldown.exsanguinate.remains<2&spell_targets.fan_of_knives=1&raid_event.adds.in>6&dot.garrote.remains*0.5<target.time_to_die
if S.Garrote:IsCastable() and TargetInMeleeRange and MeleeEnemies10yCount == 1
and not Rogue.WillLoseExsanguinate(Target, S.Garrote) and Target:PMultiplier(S.Garrote) <= 1
and Target:FilteredTimeToDie(">", Target:DebuffRemains(S.Garrote)*0.5) then
if CastPooling(S.Garrote) then return "Cast Garrote (Pre-Exsanguinate)" end
end
-- actions.dot+=/rupture,if=talent.exsanguinate.enabled&!will_lose_exsanguinate&dot.rupture.pmultiplier<=1&cooldown.exsanguinate.remains<1&effective_combo_points>=variable.exsanguinate_rupture_cp&dot.rupture.remains*0.5<target.time_to_die
if S.Rupture:IsReady() and TargetInMeleeRange and Target:PMultiplier(S.Rupture) <= 1 and not Rogue.WillLoseExsanguinate(Target, S.Rupture)
and (ComboPoints >= ExsanguinateRuptureCP() and ExsanguinateSyncRemains < 1 and Target:FilteredTimeToDie(">", Target:DebuffRemains(S.Rupture)*0.5)) then
if Cast(S.Rupture) then return "Cast Rupture (Pre-Exsanguinate)" end
end
end
-- actions.dot+=/garrote,if=refreshable&combo_points.deficit>=1&(pmultiplier<=1|remains<=tick_time&spell_targets.fan_of_knives>=3)&(!will_lose_exsanguinate|remains<=tick_time*2&spell_targets.fan_of_knives>=3)&(target.time_to_die-remains)>4&master_assassin_remains=0
-- actions.dot+=/garrote,cycle_targets=1,if=!variable.skip_cycle_garrote&target!=self.target&refreshable&combo_points.deficit>=1&(pmultiplier<=1|remains<=tick_time&spell_targets.fan_of_knives>=3)&(!will_lose_exsanguinate|remains<=tick_time*2&spell_targets.fan_of_knives>=3)&(target.time_to_die-remains)>12&master_assassin_remains=0
if S.Garrote:IsCastable() and ComboPointsDeficit >= 1 then
local function Evaluate_Garrote_Target(TargetUnit)
GarroteTickTime = Rogue.Exsanguinated(TargetUnit, S.Garrote) and ExsanguinatedBleedTickTime or BleedTickTime
return IsDebuffRefreshable(TargetUnit, S.Garrote) and MasterAssassinRemains() <= 0
and (TargetUnit:PMultiplier(S.Garrote) <= 1 or (MeleeEnemies10yCount >= 3 and TargetUnit:DebuffRemains(S.Garrote) <= GarroteTickTime))
and (not Rogue.WillLoseExsanguinate(TargetUnit, S.Garrote) or TargetUnit:DebuffRemains(S.Garrote) <= GarroteTickTime * (1 + BoolToInt(MeleeEnemies10yCount >= 3)))
end
if Evaluate_Garrote_Target(Target) and Rogue.CanDoTUnit(Target, GarroteDMGThreshold)
and (Target:FilteredTimeToDie(">", 4, -Target:DebuffRemains(S.Garrote)) or Target:TimeToDieIsNotValid()) then
-- actions.dot+=/pool_resource,for_next=1
if CastPooling(S.Garrote, nil, not TargetInMeleeRange) then return "Pool for Garrote (ST)" end
end
if HR.AoEON() and not SkipCycleGarrote then
SuggestCycleDoT(S.Garrote, Evaluate_Garrote_Target, 12, MeleeEnemies5y)
end
end
-- actions.dot+=/crimson_tempest,target_if=min:remains,if=spell_targets>=2&effective_combo_points>=4&energy.regen_combined>20&(!cooldown.deathmark.ready|dot.rupture.ticking)&remains<(2+3*(spell_targets>=4))
if HR.AoEON() and S.CrimsonTempest:IsReady() and MeleeEnemies10yCount >= 2 and ComboPoints >= 4
and EnergyRegenCombined > 20 and (not S.Deathmark:CooldownUp() or Target:DebuffUp(S.Rupture)) then
for _, CycleUnit in pairs(MeleeEnemies10y) do
if CycleUnit:DebuffRemains(S.CrimsonTempest) < (2 + 3 * BoolToInt(MeleeEnemies10yCount >= 4)) then
if Cast(S.CrimsonTempest) then return "Cast Crimson Tempest (AoE)" end
end
end
end
-- actions.dot+=/rupture,if=!variable.skip_rupture&effective_combo_points>=4&refreshable&(pmultiplier<=1|remains<=tick_time&spell_targets.fan_of_knives>=3)&(!will_lose_exsanguinate|remains<=tick_time*2&spell_targets.fan_of_knives>=3)&target.time_to_die-remains>(4+(talent.dashing_scoundrel*5)+(talent.doomblade*5)+(variable.regen_saturated*6))
-- actions.dot+=/rupture,cycle_targets=1,if=!variable.skip_cycle_rupture&!variable.skip_rupture&target!=self.target&effective_combo_points>=4&refreshable&(pmultiplier<=1|remains<=tick_time&spell_targets.fan_of_knives>=3)&(!will_lose_exsanguinate|remains<=tick_time*2&spell_targets.fan_of_knives>=3)&target.time_to_die-remains>(4+(talent.dashing_scoundrel*5)+(talent.doomblade*5)+(variable.regen_saturated*6))
if S.Rupture:IsReady() and ComboPoints >= 4 then
-- target.time_to_die-remains>(4+(talent.dashing_scoundrel*5)+(talent.doomblade*5)+(variable.regen_saturated*6))
RuptureDurationThreshold = 4 + BoolToInt(S.DashingScoundrel:IsAvailable()) * 5 + BoolToInt(S.Doomblade:IsAvailable()) * 5 + BoolToInt(EnergyRegenSaturated) * 6
local function Evaluate_Rupture_Target(TargetUnit)
RuptureTickTime = Rogue.Exsanguinated(TargetUnit, S.Rupture) and ExsanguinatedBleedTickTime or BleedTickTime
return IsDebuffRefreshable(TargetUnit, S.Rupture, RuptureThreshold)
and (TargetUnit:PMultiplier(S.Rupture) <= 1 or TargetUnit:DebuffRemains(S.Rupture) <= RuptureTickTime and MeleeEnemies10yCount >= 3)
and (not Rogue.WillLoseExsanguinate(TargetUnit, S.Rupture) or TargetUnit:DebuffRemains(S.Rupture) <= RuptureTickTime * (1 + BoolToInt(MeleeEnemies10yCount >= 3)))
and (TargetUnit:FilteredTimeToDie(">", RuptureDurationThreshold, -TargetUnit:DebuffRemains(S.Rupture)) or TargetUnit:TimeToDieIsNotValid())
end
if not SkipRupture and Evaluate_Rupture_Target(Target) and Rogue.CanDoTUnit(Target, RuptureDMGThreshold) then
if Cast(S.Rupture, nil, nil, not TargetInMeleeRange) then return "Cast Rupture (Refresh)" end
end
if not SkipRupture and not SkipCycleRupture and HR.AoEON() then
SuggestCycleDoT(S.Rupture, Evaluate_Rupture_Target, RuptureDurationThreshold, MeleeEnemies5y)
end
end
-- actions.dot+=/crimson_tempest,if=spell_targets>=2&effective_combo_points>=4&remains<2+3*(spell_targets>=4)
if HR.AoEON() and S.CrimsonTempest:IsReady() and MeleeEnemies10yCount >= 2 and ComboPoints >= 4 then
for _, CycleUnit in pairs(MeleeEnemies10y) do
-- Note: The APL does not do this due to target_if mechanics, just to determine if any targets are low on duration of the AoE Bleed
if CycleUnit:DebuffRemains(S.CrimsonTempest) < 2 + 3 * BoolToInt(MeleeEnemies10yCount >= 4) then
if Cast(S.CrimsonTempest) then return "Cast Crimson Tempest (AoE Fallback)" end
end
end
end
-- actions.dot+=/crimson_tempest,if=spell_targets=1&!talent.dashing_scoundrel&effective_combo_points>=(cp_max_spend-1)&refreshable&!will_lose_exsanguinate&!debuff.shiv.up&debuff.amplifying_poison.stack<15&(!talent.kingsbane|buff.envenom.up|!cooldown.kingsbane.up)&target.time_to_die-remains>4
if S.CrimsonTempest:IsReady() and TargetInAoERange and MeleeEnemies10yCount == 1
and not S.DashingScoundrel:IsAvailable() and ComboPoints >= (Rogue.CPMaxSpend() - 1) and IsDebuffRefreshable(Target, S.CrimsonTempest, CrimsonTempestThreshold)
and not Rogue.WillLoseExsanguinate(Target, S.CrimsonTempest) and not Target:DebuffUp(S.ShivDebuff) and Target:DebuffStack(S.AmplifyingPoisonDebuff) < 15
and (not S.Kingsbane:IsAvailable() or Player:BuffUp(S.Envenom) or not S.Kingsbane:CooldownUp())
and (Target:FilteredTimeToDie(">", 4, -Target:DebuffRemains(S.CrimsonTempest)) or Target:TimeToDieIsNotValid())
and Rogue.CanDoTUnit(Target, RuptureDMGThreshold) then
if Cast(S.CrimsonTempest) then return "Cast Crimson Tempest (ST)" end
end
return false
end
-- # Direct damage abilities
local function Direct ()
-- actions.direct=envenom,if=effective_combo_points>=4+talent.deeper_stratagem.enabled&(debuff.deathmark.up|debuff.shiv.up|debuff.amplifying_poison.stack>=10|energy.deficit<=25+energy.regen_combined|!variable.single_target|effective_combo_points>cp_max_spend)&(!talent.exsanguinate.enabled|variable.exsang_sync_remains>2|talent.resounding_clarity&(cooldown.echoing_reprimand.ready&combo_points>2|effective_combo_points>5))
if S.Envenom:IsReady() and ComboPoints >= 4 + BoolToInt(S.DeeperStratagem:IsAvailable())
and (Target:DebuffUp(S.Deathmark) or Target:DebuffUp(S.ShivDebuff) or Target:DebuffStack(S.AmplifyingPoisonDebuff) >= 10
or Player:EnergyDeficit() <= (25 + EnergyRegenCombined) or not SingleTarget or ComboPoints > Rogue.CPMaxSpend())
and (not S.Exsanguinate:IsAvailable() or ExsanguinateSyncRemains > 2 or not HR.CDsON()
or S.ResoundingClarity:IsAvailable() and (S.EchoingReprimand:IsReady() and Player:ComboPoints() > 2 or ComboPoints > 5)) then
if Cast(S.Envenom, nil, nil, not TargetInMeleeRange) then return "Cast Envenom" end
end
--- !!!! ---
-- actions.direct+=/variable,name=use_filler,value=combo_points.deficit>1|energy.deficit<=25+variable.energy_regen_combined|!variable.single_target
-- Note: This is used in all following fillers, so we just return false if not true and won't consider these.
if not (ComboPointsDeficit > 1 or Player:EnergyDeficit() <= 25 + EnergyRegenCombined or not SingleTarget) then
return false
end
--- !!!! ---
if S.SerratedBoneSpike:IsReady() then
-- actions.direct+=/serrated_bone_spike,if=variable.use_filler&!dot.serrated_bone_spike_dot.ticking
if not Target:DebuffUp(S.SerratedBoneSpikeDebuff) then
if Cast(S.SerratedBoneSpike, nil, Settings.Commons.CovenantDisplayStyle, not TargetInAoERange) then return "Cast Serrated Bone Spike" end
else
-- actions.direct+=/serrated_bone_spike,target_if=min:target.time_to_die+(dot.serrated_bone_spike_dot.ticking*600),if=variable.use_filler&!dot.serrated_bone_spike_dot.ticking
if HR.AoEON() then
if Everyone.CastTargetIf(S.SerratedBoneSpike, Enemies30y, "min", EvaluateSBSTargetIfConditionCondition, EvaluateSBSCondition) then
return "Cast Serrated Bone (AoE)"
end
end
-- actions.direct+=/serrated_bone_spike,if=variable.use_filler&master_assassin_remains<0.8&(fight_remains<=5|cooldown.serrated_bone_spike.max_charges-charges_fractional<=0.25)
-- actions.direct+=/serrated_bone_spike,if=variable.use_filler&master_assassin_remains<0.8&!variable.single_target&debuff.shiv.up
if MasterAssassinRemains() < 0.8 then
if (HL.BossFightRemains() <= 5 or (S.SerratedBoneSpike:MaxCharges() - S.SerratedBoneSpike:ChargesFractional() <= 0.25)) then
if Cast(S.SerratedBoneSpike, nil, Settings.Commons.SerratedBoneSpikeDumpDisplayStyle, not TargetInAoERange) then return "Cast Serrated Bone Spike (Dump Charge)" end
elseif not SingleTarget and Target:DebuffUp(S.ShivDebuff) then
if Cast(S.SerratedBoneSpike, nil, Settings.Commons.SerratedBoneSpikeDumpDisplayStyle, not TargetInAoERange) then return "Cast Serrated Bone Spike (Shiv)" end
end
end
end
end
-- actions.direct+=/echoing_reprimand,if=(!talent.exsanguinate|!talent.resounding_clarity)&variable.use_filler&cooldown.deathmark.remains>10|fight_remains<20
if CDsON() and S.EchoingReprimand:IsReady() and ((not S.Exsanguinate:IsAvailable() or not S.ResoundingClarity:IsAvailable() or ExsanguinateSyncRemains > 40)
and (not S.Deathmark:IsAvailable() or S.Deathmark:CooldownRemains() > 10) or HL.BossFilteredFightRemains("<=", 20)) then
if Cast(S.EchoingReprimand, nil, Settings.Commons.CovenantDisplayStyle, not TargetInMeleeRange) then return "Cast Echoing Reprimand" end
end
if S.FanofKnives:IsCastable() then
-- actions.direct+=/fan_of_knives,if=variable.use_filler&(!priority_rotation&spell_targets.fan_of_knives>=3+stealthed.rogue+talent.dragontempered_blades)
if HR.AoEON() and MeleeEnemies10yCount >= 1 and (not PriorityRotation
and MeleeEnemies10yCount >= 3 + BoolToInt(Player:StealthUp(true, false)) + BoolToInt(S.DragonTemperedBlades:IsAvailable())) then
if CastPooling(S.FanofKnives) then return "Cast Fan of Knives" end
end
-- actions.direct+=/fan_of_knives,target_if=!dot.deadly_poison_dot.ticking&(!priority_rotation|dot.garrote.ticking|dot.rupture.ticking),if=variable.use_filler&spell_targets.fan_of_knives>=3
if HR.AoEON() and Player:BuffUp(S.DeadlyPoison) and MeleeEnemies10yCount >= 3 then
for _, CycleUnit in pairs(MeleeEnemies10y) do
if not CycleUnit:DebuffUp(S.DeadlyPoisonDebuff, true) and (not PriorityRotation or CycleUnit:DebuffUp(S.Garrote) or CycleUnit:DebuffUp(S.Rupture)) then
if CastPooling(S.FanofKnives) then return "Cast Fan of Knives (DP Refresh)" end
end
end
end
end
-- actions.direct+=/ambush,if=variable.use_filler
if S.Ambush:IsCastable() and (Player:StealthUp(true, true) or Player:BuffUp(S.BlindsideBuff)) then
if CastPooling(S.Ambush, nil, not TargetInMeleeRange) then return "Cast Ambush" end
end
-- actions.direct+=/mutilate,target_if=!dot.deadly_poison_dot.ticking&!debuff.amplifying_poison.up,if=variable.use_filler&spell_targets.fan_of_knives=2
if S.Mutilate:IsCastable() and MeleeEnemies10yCount == 2 and Target:DebuffDown(S.DeadlyPoisonDebuff, true)
and Target:DebuffDown(S.AmplifyingPoisonDebuff, true) then
local TargetGUID = Target:GUID()
for _, CycleUnit in pairs(MeleeEnemies5y) do
-- Note: The APL does not do this due to target_if mechanics, but since we are cycling we should check to see if the unit has a bleed
if CycleUnit:GUID() ~= TargetGUID and (CycleUnit:DebuffUp(S.Garrote) or CycleUnit:DebuffUp(S.Rupture))
and not CycleUnit:DebuffUp(S.DeadlyPoisonDebuff, true) and not CycleUnit:DebuffUp(S.AmplifyingPoisonDebuff, true) then
CastLeftNameplate(CycleUnit, S.Mutilate)
break
end
end
end
-- actions.direct+=/mutilate,if=variable.use_filler
if S.Mutilate:IsCastable() then
if CastPooling(S.Mutilate, nil, not TargetInMeleeRange) then return "Cast Mutilate" end
end
return false
end
--- ======= MAIN =======
local function APL ()
-- Enemies Update
MeleeRange = S.AcrobaticStrikes:IsAvailable() and 8 or 5
AoERange = S.AcrobaticStrikes:IsAvailable() and 10 or 13
TargetInMeleeRange = Target:IsInMeleeRange(MeleeRange)
TargetInAoERange = Target:IsInMeleeRange(AoERange)
if AoEON() then
Enemies30y = Player:GetEnemiesInRange(30) -- Poisoned Knife & Serrated Bone Spike
MeleeEnemies10y = Player:GetEnemiesInMeleeRange(AoERange) -- Fan of Knives & Crimson Tempest
MeleeEnemies10yCount = #MeleeEnemies10y
MeleeEnemies5y = Player:GetEnemiesInMeleeRange(MeleeRange) -- Melee cycle
else
Enemies30y = {}
MeleeEnemies10y = {}
MeleeEnemies10yCount = 1
MeleeEnemies5y = {}
end
-- Rotation Variables Update
BleedTickTime, ExsanguinatedBleedTickTime = 2 * Player:SpellHaste(), 1 * Player:SpellHaste()
ComboPoints = Rogue.EffectiveComboPoints(Player:ComboPoints())
ComboPointsDeficit = Player:ComboPointsMax() - ComboPoints
RuptureThreshold = (4 + ComboPoints * 4) * 0.3
CrimsonTempestThreshold = (2 + ComboPoints * 2) * 0.3
RuptureDMGThreshold = S.Envenom:Damage() * Settings.Assassination.EnvenomDMGOffset; -- Used to check if Rupture is worth to be casted since it's a finisher.
GarroteDMGThreshold = S.Mutilate:Damage() * Settings.Assassination.MutilateDMGOffset; -- Used as TTD Not Valid fallback since it's a generator.
PriorityRotation = UsePriorityRotation()
-- 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
-- actions=stealth
if 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() then
-- Precombat CDs
if HR.CDsON() then
-- actions.precombat+=/marked_for_death,precombat_seconds=10,if=raid_event.adds.in>15
if S.MarkedforDeath:IsCastable() and Player:ComboPointsDeficit() >= Rogue.CPMaxSpend() and Everyone.TargetIsValid() then
if Cast(S.MarkedforDeath, Settings.Commons.OffGCDasOffGCD.MarkedforDeath) then return "Cast Marked for Death (OOC)" end
end
end
-- actions.precombat+=/slice_and_dice,precombat_seconds=1
if not Player:BuffUp(S.SliceandDice) then
if S.SliceandDice:IsReady() and ComboPoints >= 2 then
if Cast(S.SliceandDice) then return "Cast Slice and Dice" end
end
end
end
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
PoisonedBleeds = Rogue.PoisonedBleeds()
-- TODO: Make this match the updated code version
EnergyRegenCombined = Player:EnergyRegen() + PoisonedBleeds * 6 / (2 * Player:SpellHaste())
EnergyTimeToMaxCombined = Player:EnergyDeficit() / EnergyRegenCombined
-- actions+=/variable,name=regen_saturated,value=energy.regen_combined>35
EnergyRegenSaturated = EnergyRegenCombined > 35
ExsanguinateSyncRemains = ExsangSyncRemains()
-- actions+=/variable,name=single_target,value=spell_targets.fan_of_knives<2
SingleTarget = MeleeEnemies10yCount < 2
-- actions+=/call_action_list,name=stealthed,if=stealthed.rogue|stealthed.improved_garrote
if Player:StealthUp(true, false) or ImprovedGarroteRemains() > 0 then
ShouldReturn = Stealthed()
if ShouldReturn then return ShouldReturn .. " (Stealthed)" end
end
-- actions+=/call_action_list,name=cds
ShouldReturn = CDs()
if ShouldReturn then return ShouldReturn end
-- # Put SnD up initially for Cut to the Chase, refresh with Envenom if at low duration
-- actions+=/slice_and_dice,if=!buff.slice_and_dice.up&combo_points>=2|!talent.cut_to_the_chase&refreshable&combo_points>=4
if not Player:BuffUp(S.SliceandDice) then
if S.SliceandDice:IsReady() and Player:ComboPoints() >= 2
or not S.CutToTheChase:IsAvailable() and Player:ComboPoints() >= 4 and Player:BuffRemains(S.SliceandDice) < (1 + Player:ComboPoints()) * 1.8 then
if Cast(S.SliceandDice) then return "Cast Slice and Dice" end
end
elseif TargetInAoERange and S.CutToTheChase:IsAvailable() then
-- actions+=/envenom,if=talent.cut_to_the_chase&buff.slice_and_dice.up&buff.slice_and_dice.remains<5&combo_points>=4
if S.Envenom:IsReady() and Player:BuffRemains(S.SliceandDice) < 5 and Player:ComboPoints() >= 4 then
if Cast(S.Envenom, nil, nil, not TargetInMeleeRange) then return "Cast Envenom (CttC)" end
end
else
--- !!!! ---
-- Special fallback Poisoned Knife Out of Range [EnergyCap] or [PoisonRefresh]
-- Only if we are about to cap energy, not stealthed, and completely out of range
--- !!!! ---
if S.PoisonedKnife:IsCastable() and Target:IsInRange(30) and not Player:StealthUp(true, true)
and MeleeEnemies10yCount == 0 and Player:EnergyTimeToMax() <= Player:GCD() * 1.5 then
if Cast(S.PoisonedKnife) then return "Cast Poisoned Knife" end
end
end
-- actions+=/call_action_list,name=dot
ShouldReturn = Dot()
if ShouldReturn then return ShouldReturn end
-- actions+=/call_action_list,name=direct
ShouldReturn = Direct()
if ShouldReturn then return ShouldReturn end
-- Racials
if HR.CDsON() then
-- actions+=/arcane_torrent,if=energy.deficit>=15+variable.energy_regen_combined
if S.ArcaneTorrent:IsCastable() and TargetInMeleeRange and Player:EnergyDeficit() > 15 then
if Cast(S.ArcaneTorrent, Settings.Commons.GCDasOffGCD.Racials) then return "Cast Arcane Torrent" end
end
-- actions+=/arcane_pulse
if S.ArcanePulse:IsCastable() and TargetInMeleeRange then
if Cast(S.ArcanePulse, Settings.Commons.GCDasOffGCD.Racials) then return "Cast Arcane Pulse" end
end
-- actions+=/lights_judgment
if S.LightsJudgment:IsCastable() and TargetInMeleeRange then
if Cast(S.LightsJudgment, Settings.Commons.GCDasOffGCD.Racials) then return "Cast Lights Judgment" end
end
-- actions+=/bag_of_tricks
if S.BagofTricks:IsCastable() and TargetInMeleeRange then
if Cast(S.BagofTricks, Settings.Commons.GCDasOffGCD.Racials) then return "Cast Bag of Tricks" end
end
end
-- Trick to take in consideration the Recovery Setting
if S.Mutilate:IsCastable() and TargetInAoERange then
if Cast(S.PoolEnergy) then return "Normal Pooling" end
end
end
end
local function Init ()
S.Deathmark:RegisterAuraTracking()
S.Sepsis:RegisterAuraTracking()
end
HR.SetAPL(259, APL, Init)
--- ======= SIMC =======
-- Last Update: 2023-02-06
-- # 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+=/marked_for_death,precombat_seconds=10,if=raid_event.adds.in>15
-- # Determine which (if any) stat buff trinket we want to attempt to sync with Deathmark.
-- actions.precombat+=/variable,name=trinket_sync_slot,value=1,if=trinket.1.has_stat.any_dps&(!trinket.2.has_stat.any_dps|trinket.1.cooldown.duration>=trinket.2.cooldown.duration)
-- actions.precombat+=/variable,name=trinket_sync_slot,value=2,if=trinket.2.has_stat.any_dps&(!trinket.1.has_stat.any_dps|trinket.2.cooldown.duration>trinket.1.cooldown.duration)
-- # Determine if we should be be casting our pre-Exsanguinate Rupture with Echoing Reprimand CP
-- actions.precombat+=/variable,name=exsanguinate_rupture_cp,value=cp_max_spend<?(talent.resounding_clarity*7)
-- actions.precombat+=/stealth
-- 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
-- actions+=/variable,name=single_target,value=spell_targets.fan_of_knives<2
-- # Combined Energy Regen needed to saturate
-- actions+=/variable,name=regen_saturated,value=energy.regen_combined>35
-- # Next Exsanguinate cooldown time based on Deathmark syncing logic and remaining fight duration
-- actions+=/variable,name=exsang_sync_remains,op=setif,condition=cooldown.deathmark.remains>cooldown.exsanguinate.remains&cooldown.deathmark.remains<fight_remains,value=cooldown.deathmark.remains,value_else=cooldown.exsanguinate.remains
-- actions+=/call_action_list,name=stealthed,if=stealthed.rogue|stealthed.improved_garrote
-- actions+=/call_action_list,name=cds
-- # Put SnD up initially for Cut to the Chase, refresh with Envenom if at low duration
-- actions+=/slice_and_dice,if=!buff.slice_and_dice.up&combo_points>=2|!talent.cut_to_the_chase&refreshable&combo_points>=4
-- actions+=/envenom,if=talent.cut_to_the_chase&buff.slice_and_dice.up&buff.slice_and_dice.remains<5&combo_points>=4
-- actions+=/call_action_list,name=dot
-- actions+=/call_action_list,name=direct
-- actions+=/arcane_torrent,if=energy.deficit>=15+energy.regen_combined
-- actions+=/arcane_pulse
-- actions+=/lights_judgment
-- actions+=/bag_of_tricks
-- # Cooldowns
-- # If adds are up, snipe the one with lowest TTD. Use when dying faster than CP deficit or without any CP.
-- actions.cds=marked_for_death,line_cd=1.5,target_if=min:target.time_to_die,if=raid_event.adds.up&(!variable.single_target|target.time_to_die<30)&(target.time_to_die<combo_points.deficit*1.5|combo_points.deficit>=cp_max_spend)
-- # If no adds will die within the next 30s, use MfD for max CP.
-- actions.cds+=/marked_for_death,if=raid_event.adds.in>30-raid_event.adds.duration&combo_points.deficit>=cp_max_spend
-- # Sync Deathmark window with Exsanguinate if applicable
-- actions.cds+=/variable,name=deathmark_exsanguinate_condition,value=!talent.exsanguinate|cooldown.exsanguinate.remains>15|exsanguinated.rupture|exsanguinated.garrote
-- # Wait on Deathmark for Garrote with MA
-- actions.cds+=/variable,name=deathmark_ma_condition,value=!talent.master_assassin.enabled|dot.garrote.ticking
-- actions.cds+=/sepsis,if=!stealthed.rogue&!stealthed.improved_garrote&(!talent.improved_garrote&dot.garrote.ticking|talent.improved_garrote&cooldown.garrote.up)&(target.time_to_die>10|fight_remains<10)
-- # Deathmark to be used if not stealthed, Rupture is up, and all other talent conditions are satisfied
-- actions.cds+=/variable,name=deathmark_condition,value=!stealthed.rogue&dot.rupture.ticking&!debuff.deathmark.up&variable.deathmark_exsanguinate_condition&variable.deathmark_ma_condition
-- # Sync the priority stat buff trinket with Deathmark, otherwise use on cooldown
-- actions.cds+=/use_item,name=algethar_puzzle_box,use_off_gcd=1,if=(!talent.exsanguinate|cooldown.exsanguinate.remains>15|exsanguinated.rupture|exsanguinated.garrote)&dot.rupture.ticking&cooldown.deathmark.remains<2|fight_remains<=22
-- actions.cds+=/use_items,slots=trinket1,if=(variable.trinket_sync_slot=1&(debuff.deathmark.up|fight_remains<=20)|(variable.trinket_sync_slot=2&(!trinket.2.cooldown.ready|!debuff.deathmark.up&cooldown.deathmark.remains>20))|!variable.trinket_sync_slot)
-- actions.cds+=/use_items,slots=trinket2,if=(variable.trinket_sync_slot=2&(debuff.deathmark.up|fight_remains<=20)|(variable.trinket_sync_slot=1&(!trinket.1.cooldown.ready|!debuff.deathmark.up&cooldown.deathmark.remains>20))|!variable.trinket_sync_slot)
-- actions.cds+=/deathmark,if=variable.deathmark_condition
-- actions.cds+=/kingsbane,if=(debuff.shiv.up|cooldown.shiv.remains<6)&buff.envenom.up&(cooldown.deathmark.remains>=50|dot.deathmark.ticking)
-- # Exsanguinate when not stealthed and both Rupture and Garrote are up for long enough. Attempt to sync with Deathmark and also Echoing Reprimand if using Resounding Clarity.
-- actions.cds+=/variable,name=exsanguinate_condition,value=talent.exsanguinate&!stealthed.rogue&!stealthed.improved_garrote&!dot.deathmark.ticking&target.time_to_die>variable.exsang_sync_remains+4&variable.exsang_sync_remains<4
-- actions.cds+=/echoing_reprimand,if=talent.exsanguinate&talent.resounding_clarity&(variable.exsanguinate_condition&combo_points<=2&variable.exsang_sync_remains<=2&!dot.garrote.refreshable&dot.rupture.remains>9.6|variable.exsang_sync_remains>40)
-- actions.cds+=/exsanguinate,if=variable.exsanguinate_condition&(!dot.garrote.refreshable&dot.rupture.remains>4+4*variable.exsanguinate_rupture_cp|dot.rupture.remains*0.5>target.time_to_die)
-- # Shiv if DoTs are up; Always Shiv with Kingsbane, otherwise attempt to sync with Sepsis or Deathmark if we won't waste more than half Shiv's cooldown
-- actions.cds+=/shiv,if=talent.kingsbane&!debuff.shiv.up&dot.kingsbane.ticking&dot.garrote.ticking&dot.rupture.ticking&(!talent.crimson_tempest.enabled|variable.single_target|dot.crimson_tempest.ticking)
-- actions.cds+=/shiv,if=talent.arterial_precision&!debuff.shiv.up&dot.garrote.ticking&dot.rupture.ticking&(debuff.deathmark.up|cooldown.shiv.charges_fractional>max_charges-0.5&cooldown.deathmark.remains>10)
-- actions.cds+=/shiv,if=talent.sepsis&!talent.kingsbane&!talent.arterial_precision&!debuff.shiv.up&dot.garrote.ticking&dot.rupture.ticking&((cooldown.sepsis.ready|cooldown.sepsis.remains>12)+(cooldown.deathmark.ready|cooldown.deathmark.remains>12)=2)
-- actions.cds+=/shiv,if=!talent.kingsbane&!talent.arterial_precision&!talent.sepsis&!debuff.shiv.up&dot.garrote.ticking&dot.rupture.ticking&(!talent.crimson_tempest.enabled|variable.single_target|dot.crimson_tempest.ticking)
-- actions.cds+=/thistle_tea,if=!buff.thistle_tea.up&(energy.deficit>=100|charges=3&(dot.kingsbane.ticking|debuff.deathmark.up)|fight_remains<charges*6)
-- actions.cds+=/indiscriminate_carnage,if=(spell_targets.fan_of_knives>desired_targets|spell_targets.fan_of_knives>1&raid_event.adds.in>60)&(!talent.improved_garrote|cooldown.vanish.remains>45)
-- actions.cds+=/potion,if=buff.bloodlust.react|fight_remains<30|debuff.deathmark.up
-- actions.cds+=/blood_fury,if=debuff.deathmark.up
-- actions.cds+=/berserking,if=debuff.deathmark.up
-- actions.cds+=/fireblood,if=debuff.deathmark.up
-- actions.cds+=/ancestral_call,if=debuff.deathmark.up
-- actions.cds+=/call_action_list,name=vanish,if=!stealthed.all&master_assassin_remains=0
-- actions.cds+=/cold_blood,if=combo_points>=4
-- # Direct damage abilities
-- # Envenom at 4+ (5+ with DS) CP. Immediately on 2+ targets, with Deathmark, or with TB; otherwise wait for some energy. Also wait if Exsg combo is coming up.
-- actions.direct=envenom,if=effective_combo_points>=4+talent.deeper_stratagem.enabled&(debuff.deathmark.up|debuff.shiv.up|debuff.amplifying_poison.stack>=10|energy.deficit<=25+energy.regen_combined|!variable.single_target|effective_combo_points>cp_max_spend)&(!talent.exsanguinate.enabled|variable.exsang_sync_remains>2|talent.resounding_clarity&(cooldown.echoing_reprimand.ready&combo_points>2|effective_combo_points>5))
-- actions.direct+=/variable,name=use_filler,value=combo_points.deficit>1|energy.deficit<=25+energy.regen_combined|!variable.single_target
-- # Apply SBS to all targets without a debuff as priority, preferring targets dying sooner after the primary target
-- actions.direct+=/serrated_bone_spike,if=variable.use_filler&!dot.serrated_bone_spike_dot.ticking
-- actions.direct+=/serrated_bone_spike,target_if=min:target.time_to_die+(dot.serrated_bone_spike_dot.ticking*600),if=variable.use_filler&!dot.serrated_bone_spike_dot.ticking
-- # Keep from capping charges or burn at the end of fights
-- actions.direct+=/serrated_bone_spike,if=variable.use_filler&master_assassin_remains<0.8&(fight_remains<=5|cooldown.serrated_bone_spike.max_charges-charges_fractional<=0.25)
-- # When MA is not at high duration, sync with Shiv
-- actions.direct+=/serrated_bone_spike,if=variable.use_filler&master_assassin_remains<0.8&!variable.single_target&debuff.shiv.up
-- actions.direct+=/echoing_reprimand,if=(!talent.exsanguinate|!talent.resounding_clarity)&variable.use_filler&cooldown.deathmark.remains>10|fight_remains<20
-- # Fan of Knives at 3+ targets or 4+ with DTB
-- actions.direct+=/fan_of_knives,if=variable.use_filler&(!priority_rotation&spell_targets.fan_of_knives>=3+stealthed.rogue+talent.dragontempered_blades)
-- # Fan of Knives to apply poisons if inactive on any target (or any bleeding targets with priority rotation) at 3T
-- actions.direct+=/fan_of_knives,target_if=!dot.deadly_poison_dot.ticking&(!priority_rotation|dot.garrote.ticking|dot.rupture.ticking),if=variable.use_filler&spell_targets.fan_of_knives>=3
-- actions.direct+=/ambush,if=variable.use_filler
-- # Tab-Mutilate to apply Deadly Poison at 2 targets
-- actions.direct+=/mutilate,target_if=!dot.deadly_poison_dot.ticking&!debuff.amplifying_poison.up,if=variable.use_filler&spell_targets.fan_of_knives=2
-- actions.direct+=/mutilate,if=variable.use_filler
-- # Damage over time abilities
-- # Limit secondary Garrotes for priority rotation if we have 35 energy regen or Garrote will expire on the primary target
-- actions.dot=variable,name=skip_cycle_garrote,value=priority_rotation&(dot.garrote.remains<cooldown.garrote.duration|variable.regen_saturated)
-- # Limit secondary Ruptures for priority rotation if we have 35 energy regen or Shiv is up on 2T+
-- actions.dot+=/variable,name=skip_cycle_rupture,value=priority_rotation&(debuff.shiv.up&spell_targets.fan_of_knives>2|variable.regen_saturated)
-- # Limit Ruptures when appropriate, not currently used
-- actions.dot+=/variable,name=skip_rupture,value=0
-- # Special Garrote and Rupture setup prior to Exsanguinate cast
-- actions.dot+=/garrote,if=talent.exsanguinate.enabled&!will_lose_exsanguinate&dot.garrote.pmultiplier<=1&variable.exsang_sync_remains<2&spell_targets.fan_of_knives=1&raid_event.adds.in>6&dot.garrote.remains*0.5<target.time_to_die
-- actions.dot+=/rupture,if=talent.exsanguinate.enabled&!will_lose_exsanguinate&dot.rupture.pmultiplier<=1&variable.exsang_sync_remains<1&effective_combo_points>=variable.exsanguinate_rupture_cp&dot.rupture.remains*0.5<target.time_to_die
-- # Garrote upkeep, also tries to use it as a special generator for the last CP before a finisher
-- actions.dot+=/pool_resource,for_next=1
-- actions.dot+=/garrote,if=refreshable&combo_points.deficit>=1&(pmultiplier<=1|remains<=tick_time&spell_targets.fan_of_knives>=3)&(!will_lose_exsanguinate|remains<=tick_time*2&spell_targets.fan_of_knives>=3)&(target.time_to_die-remains)>4&master_assassin_remains=0
-- actions.dot+=/pool_resource,for_next=1
-- actions.dot+=/garrote,cycle_targets=1,if=!variable.skip_cycle_garrote&target!=self.target&refreshable&combo_points.deficit>=1&(pmultiplier<=1|remains<=tick_time&spell_targets.fan_of_knives>=3)&(!will_lose_exsanguinate|remains<=tick_time*2&spell_targets.fan_of_knives>=3)&(target.time_to_die-remains)>12&master_assassin_remains=0
-- # Crimson Tempest on multiple targets at 4+ CP when running out in 2-5s as long as we have enough regen and aren't setting up for Deathmark
-- actions.dot+=/crimson_tempest,target_if=min:remains,if=spell_targets>=2&effective_combo_points>=4&energy.regen_combined>20&(!cooldown.deathmark.ready|dot.rupture.ticking)&remains<(2+3*(spell_targets>=4))
-- # Keep up Rupture at 4+ on all targets (when living long enough and not snapshot)
-- actions.dot+=/rupture,if=!variable.skip_rupture&effective_combo_points>=4&refreshable&(pmultiplier<=1|remains<=tick_time&spell_targets.fan_of_knives>=3)&(!will_lose_exsanguinate|remains<=tick_time*2&spell_targets.fan_of_knives>=3)&target.time_to_die-remains>(4+(talent.dashing_scoundrel*5)+(talent.doomblade*5)+(variable.regen_saturated*6))
-- actions.dot+=/rupture,cycle_targets=1,if=!variable.skip_cycle_rupture&!variable.skip_rupture&target!=self.target&effective_combo_points>=4&refreshable&(pmultiplier<=1|remains<=tick_time&spell_targets.fan_of_knives>=3)&(!will_lose_exsanguinate|remains<=tick_time*2&spell_targets.fan_of_knives>=3)&target.time_to_die-remains>(4+(talent.dashing_scoundrel*5)+(talent.doomblade*5)+(variable.regen_saturated*6))
-- # Fallback AoE Crimson Tempest with the same logic as above, but ignoring the energy conditions if we aren't using Rupture
-- actions.dot+=/crimson_tempest,if=spell_targets>=2&effective_combo_points>=4&remains<2+3*(spell_targets>=4)
-- # Crimson Tempest on ST if in pandemic and nearly max energy and if Envenom won't do more damage due to TB/MA
-- actions.dot+=/crimson_tempest,if=spell_targets=1&!talent.dashing_scoundrel&effective_combo_points>=(cp_max_spend-1)&refreshable&!will_lose_exsanguinate&!debuff.shiv.up&debuff.amplifying_poison.stack<15&(!talent.kingsbane|buff.envenom.up|!cooldown.kingsbane.up)&target.time_to_die-remains>4
-- # Stealthed Actions
-- actions.stealthed=indiscriminate_carnage,if=spell_targets.fan_of_knives>desired_targets|spell_targets.fan_of_knives>1&raid_event.adds.in>60
-- # Improved Garrote: Apply or Refresh with buffed Garrotes
-- actions.stealthed+=/pool_resource,for_next=1
-- actions.stealthed+=/garrote,target_if=min:remains,if=stealthed.improved_garrote&!will_lose_exsanguinate&(remains<12%exsanguinated_rate|pmultiplier<=1)&target.time_to_die-remains>2
-- # Improved Garrote + Exsg on 1T: Refresh Garrote at the end of stealth to get max duration before Exsanguinate
-- actions.stealthed+=/pool_resource,for_next=1
-- actions.stealthed+=/garrote,if=talent.exsanguinate.enabled&stealthed.improved_garrote&active_enemies=1&!will_lose_exsanguinate&(remains<18%exsanguinated_rate|pmultiplier<=1)&variable.exsang_sync_remains<18&improved_garrote_remains<1.3
-- # Stealth Cooldowns
-- # Vanish Sync for Improved Garrote with Deathmark
-- actions.vanish=pool_resource,for_next=1,extra_amount=45
-- actions.vanish+=/vanish,if=talent.improved_garrote&cooldown.garrote.up&!exsanguinated.garrote&(dot.garrote.pmultiplier<=1|dot.garrote.refreshable)&(debuff.deathmark.up|cooldown.deathmark.remains<4)&combo_points.deficit>=(spell_targets.fan_of_knives>?4)
-- # Vanish for Indiscriminate Carnage or Improved Garrote at 2-3+ targets
-- actions.vanish+=/pool_resource,for_next=1,extra_amount=45
-- actions.vanish+=/vanish,if=talent.improved_garrote&cooldown.garrote.up&!exsanguinated.garrote&(dot.garrote.pmultiplier<=1|dot.garrote.refreshable)&spell_targets.fan_of_knives>(3-talent.indiscriminate_carnage)&(!talent.indiscriminate_carnage|cooldown.indiscriminate_carnage.ready)
-- # Vanish with Master Assassin: Rupture+Garrote not in refresh range, during Deathmark+Shiv. Sync with Sepsis final hit if possible.
-- actions.vanish+=/vanish,if=!talent.improved_garrote&talent.master_assassin&!dot.rupture.refreshable&dot.garrote.remains>3&debuff.deathmark.up&(debuff.shiv.up|debuff.deathmark.remains<4|dot.sepsis.ticking)&dot.sepsis.remains<3
-- actions.vanish+=/pool_resource,for_next=1,extra_amount=45
-- # Shadow Dance for Improved Garrote with Deathmark
-- actions.vanish+=/shadow_dance,if=talent.improved_garrote&cooldown.garrote.up&!exsanguinated.garrote&(dot.garrote.pmultiplier<=1|dot.garrote.refreshable)&(debuff.deathmark.up|cooldown.deathmark.remains<12|cooldown.deathmark.remains>60)&combo_points.deficit>=(spell_targets.fan_of_knives>?4)
-- # Shadow Dance with Master Assassin: Rupture+Garrote not in refresh range, during Deathmark+Shiv. Sync with Sepsis final hit if possible.
-- actions.vanish+=/shadow_dance,if=!talent.improved_garrote&talent.master_assassin&!dot.rupture.refreshable&dot.garrote.remains>3&(debuff.deathmark.up|cooldown.deathmark.remains>60)&(debuff.shiv.up|debuff.deathmark.remains<4|dot.sepsis.ticking)&dot.sepsis.remains<3