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.

1619 lines
75 KiB

5 years ago
-- RogueSubtlety.lua
-- June 2018
local addon, ns = ...
local Hekili = _G[ addon ]
local class = Hekili.Class
local state = Hekili.State
local PTR = ns.PTR
-- Conduits
-- [x] deeper_daggers
-- [x] perforated_veins
-- [-] planned_execution
-- [-] stiletto_staccato
if UnitClassBase( "player" ) == "ROGUE" then
local spec = Hekili:NewSpecialization( 261 )
spec:RegisterResource( Enum.PowerType.Energy, {
shadow_techniques = {
last = function () return state.query_time end,
interval = function () return state.time_to_sht[5] end,
value = 8,
stop = function () return state.time_to_sht[5] == 0 or state.time_to_sht[5] == 3600 end,
},
vendetta_regen = {
aura = "vendetta_regen",
last = function ()
local app = state.buff.vendetta_regen.applied
local t = state.query_time
return app + floor( t - app )
end,
interval = 1,
value = 20,
},
} )
spec:RegisterResource( Enum.PowerType.ComboPoints, {
shadow_techniques = {
last = function () return state.query_time end,
interval = function () return state.time_to_sht[5] end,
value = 1,
stop = function () return state.time_to_sht[5] == 0 or state.time_to_sht[5] == 3600 end,
},
shuriken_tornado = {
aura = "shuriken_tornado",
last = function ()
local app = state.buff.shuriken_tornado.applied
local t = state.query_time
return app + floor( t - app )
end,
stop = function( x ) return state.buff.shuriken_tornado.remains == 0 end,
interval = 0.95,
value = function () return state.active_enemies + ( state.buff.shadow_blades.up and 1 or 0 ) end,
},
} )
-- Talents
spec:RegisterTalents( {
weaponmaster = 19233, -- 193537
premeditation = 19234, -- 343160
gloomblade = 19235, -- 200758
nightstalker = 22331, -- 14062
subterfuge = 22332, -- 108208
shadow_focus = 22333, -- 108209
vigor = 19239, -- 14983
deeper_stratagem = 19240, -- 193531
marked_for_death = 19241, -- 137619
soothing_darkness = 22128, -- 200759
cheat_death = 22122, -- 31230
elusiveness = 22123, -- 79008
shot_in_the_dark = 23078, -- 257505
night_terrors = 23036, -- 277953
prey_on_the_weak = 22115, -- 131511
dark_shadow = 22335, -- 245687
alacrity = 19249, -- 193539
enveloping_shadows = 22336, -- 238104
master_of_shadows = 22132, -- 196976
secret_technique = 23183, -- 280719
shuriken_tornado = 21188, -- 277925
} )
-- PvP Talents
spec:RegisterPvpTalents( {
dagger_in_the_dark = 846, -- 198675
death_from_above = 3462, -- 269513
dismantle = 5406, -- 207777
distracting_mirage = 5411, -- 354661
maneuverability = 3447, -- 197000
shadowy_duel = 153, -- 207736
silhouette = 856, -- 197899
smoke_bomb = 1209, -- 359053
thick_as_thieves = 5409, -- 221622
thiefs_bargain = 146, -- 354825
veil_of_midnight = 136, -- 198952
} )
-- Auras
spec:RegisterAuras( {
alacrity = {
id = 193538,
duration = 20,
max_stack = 5,
},
blind = {
id = 2094,
duration = 60,
max_stack = 1,
},
cheap_shot = {
id = 1833,
duration = 4,
max_stack = 1,
},
cloak_of_shadows = {
id = 31224,
duration = 5,
max_stack = 1,
},
death_from_above = {
id = 152150,
duration = 1,
},
crimson_vial = {
id = 185311,
duration = 4,
max_stack = 1,
},
crippling_poison = {
id = 3408,
duration = 3600,
max_stack = 1,
},
crippling_poison_dot = {
id = 3409,
duration = 12,
max_stack = 1,
},
evasion = {
id = 5277,
duration = 10,
max_stack = 1,
},
feint = {
id = 1966,
duration = 5,
max_stack = 1,
},
find_weakness = {
id = 316220,
duration = 18,
max_stack = 1,
},
fleet_footed = {
id = 31209,
},
instant_poison = {
id = 315584,
duration = 3600,
max_stack = 1,
},
kidney_shot = {
id = 408,
duration = 6,
max_stack = 1,
},
marked_for_death = {
id = 137619,
duration = 60,
max_stack = 1,
},
master_of_shadows = {
id = 196980,
duration = 3,
max_stack = 1,
},
premeditation = {
id = 343173,
duration = 3600,
max_stack = 1,
},
prey_on_the_weak = {
id = 255909,
duration = 6,
max_stack = 1,
},
relentless_strikes = {
id = 58423,
},
--[[ Share Assassination implementation to avoid errors.
rupture = {
id = 1943,
duration = function () return talent.deeper_stratagem.enabled and 28 or 24 end,
max_stack = 1,
}, ]]
shadow_blades = {
id = 121471,
duration = 20,
max_stack = 1,
},
shadow_dance = {
id = 185422,
duration = function () return talent.subterfuge.enabled and 9 or 8 end,
max_stack = 1,
},
shadows_grasp = {
id = 206760,
duration = 8,
type = "Magic",
max_stack = 1,
},
shadow_techniques = {
id = 196912,
},
shadowstep = {
id = 36554,
duration = 2,
max_stack = 1,
},
shot_in_the_dark = {
id = 257506,
duration = 3600,
max_stack = 1,
},
shroud_of_concealment = {
id = 114018,
duration = 15,
max_stack = 1,
},
shuriken_tornado = {
id = 277925,
duration = 4,
max_stack = 1,
},
slice_and_dice = {
id = 315496,
duration = 10,
max_stack = 1,
},
sprint = {
id = 2983,
duration = 8,
max_stack = 1,
},
stealth = {
id = function () return talent.subterfuge.enabled and 115191 or 1784 end,
duration = 3600,
max_stack = 1,
copy = { 115191, 1784 }
},
subterfuge = {
id = 115192,
duration = 3,
max_stack = 1,
},
symbols_of_death = {
id = 212283,
duration = 10,
max_stack = 1,
},
symbols_of_death_crit = {
id = 227151,
duration = 10,
max_stack = 1,
copy = "symbols_of_death_autocrit"
},
vanish = {
id = 11327,
duration = 3,
max_stack = 1,
},
wound_poison = {
id = 8679,
duration = 3600,
max_stack = 1,
},
lethal_poison = {
alias = { "instant_poison", "wound_poison", "slaughter_poison" },
aliasMode = "first",
aliasType = "buff",
duration = 3600
},
nonlethal_poison = {
alias = { "crippling_poison", "numbing_poison" },
aliasMode = "first",
aliasType = "buff",
duration = 3600
},
-- Azerite Powers
blade_in_the_shadows = {
id = 279754,
duration = 60,
max_stack = 10,
},
nights_vengeance = {
id = 273424,
duration = 8,
max_stack = 1,
},
perforate = {
id = 277720,
duration = 12,
max_stack = 1
},
replicating_shadows = {
id = 286131,
duration = 1,
max_stack = 50
},
the_first_dance = {
id = 278981,
duration = function () return buff.shadow_dance.duration end,
max_stack = 1,
},
-- Legendaries (Shadowlands)
deathly_shadows = {
id = 341202,
duration = 15,
max_stack = 1,
},
master_assassins_mark = {
id = 340094,
duration = 4,
max_stack = 1
},
the_rotten = {
id = 341134,
duration = 3,
max_stack = 1
}
} )
local true_stealth_change = 0
local emu_stealth_change = 0
spec:RegisterEvent( "UPDATE_STEALTH", function ()
true_stealth_change = GetTime()
end )
local stealth = {
rogue = { "stealth", "vanish", "shadow_dance", "subterfuge" },
mantle = { "stealth", "vanish" },
sepsis = { "sepsis_buff" },
all = { "stealth", "vanish", "shadow_dance", "subterfuge", "shadowmeld", "sepsis_buff" }
}
spec:RegisterStateTable( "stealthed", setmetatable( {}, {
__index = function( t, k )
if k == "rogue" then
return buff.stealth.up or buff.vanish.up or buff.shadow_dance.up or buff.subterfuge.up
elseif k == "rogue_remains" then
return max( buff.stealth.remains, buff.vanish.remains, buff.shadow_dance.remains, buff.subterfuge.remains )
elseif k == "mantle" then
return buff.stealth.up or buff.vanish.up
elseif k == "mantle_remains" then
return max( buff.stealth.remains, buff.vanish.remains )
elseif k == "sepsis" then
return buff.sepsis_buff.up
elseif k == "sepsis_remains" then
return buff.sepsis_buff.remains
elseif k == "all" then
return buff.stealth.up or buff.vanish.up or buff.shadow_dance.up or buff.subterfuge.up or buff.shadowmeld.up or buff.sepsis_buff.up
elseif k == "remains" or k == "all_remains" then
return max( buff.stealth.remains, buff.vanish.remains, buff.shadow_dance.remains, buff.subterfuge.remains, buff.shadowmeld.remains, buff.sepsis_buff.remains )
end
return false
end
} ) )
local last_mh = 0
local last_oh = 0
local last_shadow_techniques = 0
local swings_since_sht = 0
spec:RegisterEvent( "COMBAT_LOG_EVENT_UNFILTERED", function()
local event, _, subtype, _, sourceGUID, sourceName, _, _, destGUID, destName, destFlags, _, spellID, spellName, _, amount, interrupt, a, b, c, d, offhand, multistrike = CombatLogGetCurrentEventInfo()
if sourceGUID == state.GUID then
if subtype == "SPELL_ENERGIZE" and spellID == 196911 then
last_shadow_techniques = GetTime()
swings_since_sht = 0
end
if subtype:sub( 1, 5 ) == "SWING" and not multistrike then
if subtype == "SWING_MISSED" then
offhand = spellName
end
local now = GetTime()
if now > last_shadow_techniques + 3 then
swings_since_sht = swings_since_sht + 1
end
if offhand then last_mh = GetTime()
else last_mh = GetTime() end
end
end
end )
local sht = {}
spec:RegisterStateTable( "time_to_sht", setmetatable( {}, {
__index = function( t, k )
local n = tonumber( k )
n = n - ( n % 1 )
if not n or n > 5 then return 3600 end
if n <= swings_since_sht then return 0 end
local mh_speed = swings.mainhand_speed
local mh_next = ( swings.mainhand > now - 3 ) and ( swings.mainhand + mh_speed ) or now + ( mh_speed * 0.5 )
local oh_speed = swings.offhand_speed
local oh_next = ( swings.offhand > now - 3 ) and ( swings.offhand + oh_speed ) or now
table.wipe( sht )
if mh_speed and mh_speed > 0 then
sht[1] = mh_next + ( 1 * mh_speed )
sht[2] = mh_next + ( 2 * mh_speed )
sht[3] = mh_next + ( 3 * mh_speed )
sht[4] = mh_next + ( 4 * mh_speed )
end
if oh_speed and oh_speed > 0 then
sht[5] = oh_next + ( 1 * oh_speed )
sht[6] = oh_next + ( 2 * oh_speed )
sht[7] = oh_next + ( 3 * oh_speed )
sht[8] = oh_next + ( 4 * oh_speed )
end
local i = 1
while( sht[i] ) do
if sht[i] < last_shadow_techniques + 3 then
table.remove( sht, i )
else
i = i + 1
end
end
if #sht > 0 and n - swings_since_sht < #sht then
table.sort( sht )
return max( 0, sht[ n - swings_since_sht ] - query_time )
else
return 3600
end
end
} ) )
spec:RegisterStateExpr( "bleeds", function ()
return ( debuff.garrote.up and 1 or 0 ) + ( debuff.rupture.up and 1 or 0 )
end )
spec:RegisterStateExpr( "cp_max_spend", function ()
return combo_points.max
end )
-- Legendary from Legion, shows up in APL still.
spec:RegisterGear( "cinidaria_the_symbiote", 133976 )
spec:RegisterGear( "denial_of_the_halfgiants", 137100 )
local function comboSpender( amt, resource )
if resource == "combo_points" then
if amt > 0 then
gain( 6 * amt, "energy" )
end
if talent.alacrity.enabled and amt >= 5 then
addStack( "alacrity", 20, 1 )
end
if talent.secret_technique.enabled then
cooldown.secret_technique.expires = max( 0, cooldown.secret_technique.expires - amt )
end
reduceCooldown( "shadow_dance", amt * ( talent.enveloping_shadows.enabled and 1.5 or 1 ) )
if legendary.obedience.enabled and buff.flagellation_buff.up then
reduceCooldown( "flagellation", amt )
end
end
end
spec:RegisterHook( "spend", comboSpender )
-- spec:RegisterHook( "spendResources", comboSpender )
spec:RegisterStateExpr( "mantle_duration", function ()
return legendary.mark_of_the_master_assassin.enabled and 4 or 0
end )
spec:RegisterStateExpr( "master_assassin_remains", function ()
if not legendary.mark_of_the_master_assassin.enabled then return 0 end
if stealthed.mantle then return cooldown.global_cooldown.remains + 4
elseif buff.master_assassins_mark.up then return buff.master_assassins_mark.remains end
return 0
end )
spec:RegisterStateExpr( "priority_rotation", function ()
return settings.priority_rotation
end )
-- We need to break stealth when we start combat from an ability.
spec:RegisterHook( "runHandler", function( ability )
local a = class.abilities[ ability ]
if stealthed.mantle and ( not a or a.startsCombat ) then
if talent.subterfuge.enabled and stealthed.mantle then
applyBuff( "subterfuge" )
end
if legendary.mark_of_the_master_assassin.enabled and stealthed.mantle then
applyBuff( "master_assassins_mark", 4 )
end
if buff.stealth.up then
setCooldown( "stealth", 2 )
end
removeBuff( "stealth" )
removeBuff( "vanish" )
removeBuff( "shadowmeld" )
end
end )
local ExpireSepsis = setfenv( function ()
applyBuff( "sepsis_buff" )
end, state )
spec:RegisterHook( "reset_precast", function( amt, resource )
if debuff.sepsis.up then
state:QueueAuraExpiration( "sepsis", ExpireSepsis, debuff.sepsis.expires )
end
end )
spec:RegisterCycle( function ()
if active_enemies == 1 then return end
if this_action == "marked_for_death" and target.time_to_die > 3 + Hekili:GetLowestTTD() then return "cycle" end
end )
spec:RegisterGear( "insignia_of_ravenholdt", 137049 )
spec:RegisterGear( "mantle_of_the_master_assassin", 144236 )
spec:RegisterAura( "master_assassins_initiative", {
id = 235027,
duration = 5
} )
spec:RegisterStateExpr( "mantle_duration", function()
if stealthed.mantle then return cooldown.global_cooldown.remains + buff.master_assassins_initiative.duration
elseif buff.master_assassins_initiative.up then return buff.master_assassins_initiative.remains end
return 0
end )
spec:RegisterGear( "shadow_satyrs_walk", 137032 )
spec:RegisterStateExpr( "ssw_refund_offset", function()
return target.distance
end )
spec:RegisterGear( "soul_of_the_shadowblade", 150936 )
spec:RegisterGear( "the_dreadlords_deceit", 137021 )
spec:RegisterAura( "the_dreadlords_deceit", {
id = 228224,
duration = 3600,
max_stack = 20,
copy = 208693
} )
spec:RegisterGear( "the_first_of_the_dead", 151818 )
spec:RegisterAura( "the_first_of_the_dead", {
id = 248210,
duration = 2
} )
spec:RegisterGear( "will_of_valeera", 137069 )
spec:RegisterAura( "will_of_valeera", {
id = 208403,
duration = 5
} )
-- Tier Sets
spec:RegisterGear( "tier21", 152163, 152165, 152161, 152160, 152162, 152164 )
spec:RegisterGear( "tier20", 147172, 147174, 147170, 147169, 147171, 147173 )
spec:RegisterGear( "tier19", 138332, 138338, 138371, 138326, 138329, 138335 )
-- Abilities
spec:RegisterAbilities( {
backstab = {
id = 53,
cast = 0,
cooldown = 0,
gcd = "spell",
spend = function () return 35 * ( ( talent.shadow_focus.enabled and ( buff.shadow_dance.up or buff.stealth.up ) ) and 0.8 or 1 ) end,
spendType = "energy",
startsCombat = true,
texture = 132090,
notalent = "gloomblade",
handler = function ()
applyDebuff( "target", "shadows_grasp", 8 )
if azerite.perforate.enabled and buff.perforate.up then
-- We'll assume we're attacking from behind if we've already put up Perforate once.
addStack( "perforate", nil, 1 )
gainChargeTime( "shadow_blades", 0.5 )
end
gain( ( buff.shadow_blades.up and 2 or 1 ) + ( buff.the_rotten.up and 4 or 0 ), "combo_points" )
removeBuff( "the_rotten" )
removeBuff( "symbols_of_death_crit" )
removeBuff( "perforated_veins" )
end,
},
black_powder = {
id = 319175,
cast = 0,
cooldown = 0,
gcd = "spell",
spend = function () return 35 * ( ( talent.shadow_focus.enabled and ( buff.shadow_dance.up or buff.stealth.up ) ) and 0.8 or 1 ) end,
spendType = "energy",
startsCombat = true,
texture = 608955,
usable = function () return combo_points.current > 0, "requires combo_points" end,
handler = function ()
if talent.alacrity.enabled and combo_points.current > 4 then addStack( "alacrity", nil, 1 ) end
if combo_points.current == animacharged_cp then removeBuff( "echoing_reprimand" ) end
if buff.finality_black_powder.up then removeBuff( "finality_black_powder" )
elseif legendary.finality.enabled then applyBuff( "finality_black_powder" ) end
spend( min( talent.deeper_stratagem.enabled and 6 or 5, combo_points.current ), "combo_points" )
if conduit.deeper_daggers.enabled then applyBuff( "deeper_daggers" ) end
end,
auras = {
finality_black_powder = {
id = 340603,
duration = 30,
max_stack = 1
}
}
},
blind = {
id = 2094,
cast = 0,
cooldown = function () return 120 - ( talent.blinding_powder.enabled and 30 or 0 ) end,
gcd = "spell",
startsCombat = true,
texture = 136175,
handler = function ()
applyDebuff( "target", "blind", 60)
end,
},
cheap_shot = {
id = 1833,
cast = 0,
cooldown = 0,
gcd = "spell",
spend = function ()
if buff.shot_in_the_dark.up then return 0 end
return 40 * ( ( talent.shadow_focus.enabled and ( buff.shadow_dance.up or buff.stealth.up ) ) and 0.8 or 1 ) * ( 1 - conduit.rushed_setup.mod * 0.01 )
end,
spendType = "energy",
startsCombat = true,
texture = 132092,
cycle = function ()
if talent.prey_on_the_weak.enabled then return "prey_on_the_weak" end
end,
usable = function ()
if boss then return false, "cheap_shot assumed unusable in boss fights" end
return stealthed.all, "not stealthed"
end,
nodebuff = "cheap_shot",
handler = function ()
applyDebuff( "target", "find_weakness" )
if talent.prey_on_the_weak.enabled then
applyDebuff( "target", "prey_on_the_weak" )
end
if talent.subterfuge.enabled then
applyBuff( "subterfuge" )
end
applyDebuff( "target", "cheap_shot" )
removeBuff( "shot_in_the_dark" )
if buff.sepsis_buff.up then removeBuff( "sepsis_buff" ) end
gain( buff.shadow_blades.up and 2 or 1, "combo_points" )
removeBuff( "symbols_of_death_crit" )
end,
},
cloak_of_shadows = {
id = 31224,
cast = 0,
cooldown = 120,
gcd = "off",
toggle = "cooldowns",
startsCombat = false,
texture = 136177,
handler = function ()
applyBuff( "cloak_of_shadows", 5 )
end,
},
crimson_vial = {
id = 185311,
cast = 0,
cooldown = 30,
gcd = "spell",
toggle = "cooldowns",
spend = function () return ( 20 - conduit.nimble_fingers.mod ) * ( ( talent.shadow_focus.enabled and ( buff.shadow_dance.up or buff.stealth.up ) ) and 0.8 or 1 ) end,
spendType = "energy",
startsCombat = false,
texture = 1373904,
handler = function ()
applyBuff( "crimson_vial", 6 )
end,
},
crippling_poison = {
id = 3408,
cast = 1.5,
cooldown = 0,
gcd = "spell",
essential = true,
startsCombat = false,
texture = 132274,
readyTime = function () return buff.nonlethal_poison.remains - 120 end,
handler = function ()
applyBuff( "crippling_poison" )
end,
},
distract = {
id = 1725,
cast = 0,
cooldown = 30,
gcd = "spell",
spend = function () return 30 * ( ( talent.shadow_focus.enabled and ( buff.shadow_dance.up or buff.stealth.up ) ) and 0.8 or 1 ) * ( 1 - conduit.rushed_setup.mod * 0.01 ) end,
spendType = "energy",
startsCombat = false,
texture = 132289,
handler = function ()
end,
},
evasion = {
id = 5277,
cast = 0,
cooldown = 120,
gcd = "off",
toggle = "cooldowns",
startsCombat = false,
texture = 136205,
handler = function ()
applyBuff( "evasion", 10 )
end,
},
eviscerate = {
id = 196819,
cast = 0,
cooldown = 0,
gcd = "spell",
spend = function () return 35 * ( ( talent.shadow_focus.enabled and ( buff.shadow_dance.up or buff.stealth.up ) ) and 0.8 or 1 ) end,
spendType = "energy",
startsCombat = true,
texture = 132292,
usable = function () return combo_points.current > 0 end,
handler = function ()
if talent.alacrity.enabled and combo_points.current > 4 then
addStack( "alacrity", 20, 1 )
end
removeBuff( "nights_vengeance" )
if buff.finality_eviscerate.up then removeBuff( "finality_eviscerate" )
elseif legendary.finality.enabled then applyBuff( "finality_eviscerate" ) end
if combo_points.current == animacharged_cp then removeBuff( "echoing_reprimand" ) end
spend( min( talent.deeper_stratagem.enabled and 6 or 5, combo_points.current ), "combo_points" )
if conduit.deeper_daggers.enabled then applyBuff( "deeper_daggers" ) end
end,
auras = {
-- Conduit
deeper_daggers = {
id = 341550,
duration = 5,
max_stack = 1
},
finality_eviscerate = {
id = 340600,
duration = 30,
max_stack = 1
}
}
},
feint = {
id = 1966,
cast = 0,
cooldown = 15,
gcd = "spell",
spend = function () return ( 35 - conduit.nimble_fingers.mod ) * ( ( talent.shadow_focus.enabled and ( buff.shadow_dance.up or buff.stealth.up ) ) and 0.8 or 1 ) end,
spendType = "energy",
startsCombat = false,
texture = 132294,
handler = function ()
applyBuff( "feint", 5 )
end,
},
gloomblade = {
id = 200758,
cast = 0,
cooldown = 0,
gcd = "spell",
spend = function () return 35 * ( ( talent.shadow_focus.enabled and ( buff.shadow_dance.up or buff.stealth.up ) ) and 0.8 or 1 ) end,
spendType = "energy",
talent = "gloomblade",
startsCombat = true,
texture = 1035040,
handler = function ()
applyDebuff( "target", "shadows_grasp", 8 )
if buff.stealth.up then
removeBuff( "stealth" )
end
gain( ( buff.shadow_blades.up and 2 or 1 ) + ( buff.the_rotten.up and 4 or 0 ), "combo_points" )
removeBuff( "the_rotten" )
removeBuff( "symbols_of_death_crit" )
end,
},
instant_poison = {
id = 315584,
cast = 1.5,
cooldown = 0,
gcd = "spell",
essential = true,
startsCombat = false,
texture = 132273,
readyTime = function () return buff.lethal_poison.remains - 120 end,
handler = function ()
applyBuff( "instant_poison" )
end,
},
kick = {
id = 1766,
cast = 0,
cooldown = 15,
gcd = "off",
toggle = "interrupts",
interrupt = true,
startsCombat = true,
texture = 132219,
debuff = "casting",
readyTime = state.timeToInterrupt,
handler = function ()
interrupt()
if conduit.prepared_for_all.enabled and cooldown.cloak_of_shadows.remains > 0 then
reduceCooldown( "cloak_of_shadows", 2 * conduit.prepared_for_all.mod )
end
end,
},
kidney_shot = {
id = 408,
cast = 0,
cooldown = 20,
gcd = "spell",
spend = function () return 25 * ( ( talent.shadow_focus.enabled and ( buff.shadow_dance.up or buff.stealth.up ) ) and 0.8 or 1 ) * ( 1 - conduit.rushed_setup.mod * 0.01 ) end,
spendType = "energy",
toggle = "cooldowns",
startsCombat = true,
texture = 132298,
usable = function () return combo_points.current > 0 end,
handler = function ()
if talent.alacrity.enabled and combo_points.current > 4 then
addStack( "alacrity", 20, 1 )
end
local combo = min( talent.deeper_stratagem.enabled and 6 or 5, combo_points.current )
applyDebuff( "target", "kidney_shot", 2 + 1 * ( combo - 1 ) )
if talent.prey_on_the_weak.enabled then applyDebuff( "target", "prey_on_the_weak" ) end
if combo_points.current == animacharged_cp then removeBuff( "echoing_reprimand" ) end
spend( min( talent.deeper_stratagem.enabled and 6 or 5, combo_points.current ), "combo_points" )
end,
},
marked_for_death = {
id = 137619,
cast = 0,
cooldown = 60,
gcd = "off",
talent = "marked_for_death",
startsCombat = false,
texture = 236364,
usable = function ()
return combo_points.current <= settings.mfd_points, "combo_point (" .. combo_points.current .. ") > user preference (" .. settings.mfd_points .. ")"
end,
handler = function ()
gain( 5, "combo_points")
applyDebuff( "target", "marked_for_death", 60 )
end,
},
numbing_poison = {
id = 5761,
cast = 1.5,
cooldown = 0,
gcd = "spell",
essential = true,
startsCombat = false,
texture = 136066,
readyTime = function () return buff.nonlethal_poison.remains - 120 end,
handler = function ()
applyBuff( "numbing_poison" )
end,
},
pick_lock = {
id = 1804,
cast = 1.5,
cooldown = 0,
gcd = "spell",
startsCombat = false,
texture = 136058,
handler = function ()
end,
},
pick_pocket = {
id = 921,
cast = 0,
cooldown = 0.5,
gcd = "spell",
startsCombat = false,
texture = 133644,
handler = function ()
end,
},
rupture = {
id = 1943,
cast = 0,
cooldown = 0,
gcd = "spell",
spend = function () return 25 * ( ( talent.shadow_focus.enabled and ( buff.shadow_dance.up or buff.stealth.up ) ) and 0.8 or 1 ) end,
spendType = "energy",
startsCombat = true,
texture = 132302,
handler = function ()
if talent.alacrity.enabled and combo_points.current >= 5 then addStack( "alacrity", nil, 1 ) end
applyDebuff( "target", "rupture", 4 + ( 4 * combo_points.current ) )
if buff.finality_rupture.up then removeBuff( "finality_rupture" )
elseif legendary.finality.enabled then applyBuff( "finality_rupture" ) end
if combo_points.current == animacharged_cp then removeBuff( "echoing_reprimand" ) end
spend( min( talent.deeper_stratagem.enabled and 6 or 5, combo_points.current ), "combo_points" )
end,
auras = {
finality_rupture = {
id = 340601,
duration = 30,
max_stack = 1
}
}
},
sap = {
id = 6770,
cast = 0,
cooldown = 0,
gcd = "spell",
spend = function () return 35 * ( ( talent.shadow_focus.enabled and ( buff.shadow_dance.up or buff.stealth.up ) ) and 0.8 or 1 ) * ( 1 - conduit.rushed_setup.mod * 0.01 ) end,
spendType = "energy",
startsCombat = false,
texture = 132310,
handler = function ()
applyDebuff( "target", "sap", 60 )
end,
},
secret_technique = {
id = 280719,
cast = 0,
cooldown = function () return 45 - min( talent.deeper_stratagem.enabled and 6 or 5, combo_points.current ) end,
gcd = "spell",
spend = function () return 30 * ( ( talent.shadow_focus.enabled and ( buff.shadow_dance.up or buff.stealth.up ) ) and 0.8 or 1 ) end,
spendType = "energy",
startsCombat = true,
texture = 132305,
usable = function () return combo_points.current > 0, "requires combo_points" end,
handler = function ()
if talent.alacrity.enabled and combo_points.current > 4 then addStack( "alacrity", nil, 1 ) end
if combo_points.current == animacharged_cp then removeBuff( "echoing_reprimand" ) end
spend( min( talent.deeper_stratagem.enabled and 6 or 5, combo_points.current ), "combo_points" )
end,
},
shadow_blades = {
id = 121471,
cast = 0,
cooldown = function () return ( essence.vision_of_perfection.enabled and 0.87 or 1 ) * 180 end,
gcd = "off",
toggle = "cooldowns",
startsCombat = false,
texture = 376022,
handler = function ()
applyBuff( "shadow_blades" )
end,
},
shadow_dance = {
id = 185313,
cast = 0,
charges = function () return talent.enveloping_shadows.enabled and 2 or nil end,
cooldown = 60,
recharge = function () return talent.enveloping_shadows.enabled and 60 or nil end,
gcd = "off",
startsCombat = false,
texture = 236279,
nobuff = "shadow_dance",
usable = function () return not stealthed.all, "not used in stealth" end,
handler = function ()
applyBuff( "shadow_dance" )
if talent.shot_in_the_dark.enabled then applyBuff( "shot_in_the_dark" ) end
if talent.master_of_shadows.enabled then applyBuff( "master_of_shadows", 3 ) end
if azerite.the_first_dance.enabled then
gain( 2, "combo_points" )
applyBuff( "the_first_dance" )
end
end,
},
shadowstep = {
id = 36554,
cast = 0,
charges = 2,
cooldown = function () return 30 * ( 1 - conduit.quick_decisions.mod * 0.01 ) end,
recharge = function () return 30 * ( 1 - conduit.quick_decisions.mod * 0.01 ) end,
gcd = "off",
startsCombat = false,
texture = 132303,
handler = function ()
applyBuff( "shadowstep" )
end,
},
shadowstrike = {
id = 185438,
cast = 0,
cooldown = 0,
gcd = "spell",
spend = function () return ( azerite.blade_in_the_shadows.enabled and 38 or 40 ) * ( ( talent.shadow_focus.enabled and ( buff.shadow_dance.up or buff.stealth.up ) ) and 0.8 or 1 ) end,
spendType = "energy",
cycle = function () return talent.find_weakness.enabled and "find_weakness" or nil end,
startsCombat = true,
texture = 1373912,
usable = function () return stealthed.all or buff.sepsis_buff.up, "requires stealth or sepsis_buff" end,
handler = function ()
gain( ( buff.shadow_blades.up and 3 or 2 ) + ( buff.the_rotten.up and 4 or 0 ), "combo_points" )
removeBuff( "the_rotten" )
removeBuff( "symbols_of_death_crit" )
if azerite.blade_in_the_shadows.enabled then addStack( "blade_in_the_shadows", nil, 1 ) end
if buff.premeditation.up then
if buff.slice_and_dice.up then
gain( 2, "combo_points" )
if buff.slice_and_dice.remains < 10 then buff.slice_and_dice.expires = query_time + 10 end
else
applyBuff( "slice_and_dice", 10 )
end
removeBuff( "premeditation" )
end
if conduit.perforated_veins.enabled then
addStack( "perforated_veins", nil, 1 )
end
removeBuff( "sepsis_buff" )
applyDebuff( "target", "find_weakness" )
end,
auras = {
-- Conduit
perforated_veins = {
id = 341572,
duration = 12,
max_stack = 3
},
}
},
shiv = {
id = 5938,
cast = 0,
cooldown = 25,
gcd = "spell",
spend = function ()
if legendary.tiny_toxic_blade.enabled then return 0 end
return 20 * ( ( talent.shadow_focus.enabled and ( buff.shadow_dance.up or buff.stealth.up ) ) and 0.8 or 1 )
end,
spendType = "energy",
startsCombat = true,
texture = 135428,
handler = function ()
gain( 1, "combo_points" )
removeBuff( "symbols_of_death_crit" )
applyDebuff( "target", "crippling_poison_shiv" )
end,
auras = {
crippling_poison_shiv = {
id = 319504,
duration = 9,
max_stack = 1,
},
}
},
shroud_of_concealment = {
id = 114018,
cast = 0,
cooldown = 360,
gcd = "spell",
toggle = "cooldowns",
startsCombat = false,
texture = 635350,
handler = function ()
applyBuff( "shroud_of_concealment" )
if conduit.fade_to_nothing.enabled then applyBuff( "fade_to_nothing" ) end
end,
},
shuriken_storm = {
id = 197835,
cast = 0,
cooldown = 0,
gcd = "spell",
spend = function () return 35 * ( ( talent.shadow_focus.enabled and ( buff.shadow_dance.up or buff.stealth.up ) ) and 0.8 or 1 ) end,
spendType = "energy",
startsCombat = true,
texture = 1375677,
handler = function ()
gain( active_enemies + ( buff.shadow_blades.up and 1 or 0 ), "combo_points" )
removeBuff( "symbols_of_death_crit" )
end,
},
shuriken_tornado = {
id = 277925,
cast = 0,
cooldown = 60,
gcd = "spell",
spend = function () return 60 * ( ( talent.shadow_focus.enabled and ( buff.shadow_dance.up or buff.stealth.up ) ) and 0.8 or 1 ) end,
spendType = "energy",
toggle = "cooldowns",
talent = "shuriken_tornado",
startsCombat = true,
texture = 236282,
handler = function ()
applyBuff( "shuriken_tornado" )
end,
},
shuriken_toss = {
id = 114014,
cast = 0,
cooldown = 0,
gcd = "spell",
spend = function () return 40 * ( ( talent.shadow_focus.enabled and ( buff.shadow_dance.up or buff.stealth.up ) ) and 0.8 or 1 ) end,
spendType = "energy",
startsCombat = true,
texture = 135431,
handler = function ()
gain( active_enemies + ( buff.shadow_blades.up and 1 or 0 ), "combo_points" )
removeBuff( "symbols_of_death_crit" )
end,
},
slice_and_dice = {
id = 315496,
cast = 0,
cooldown = 0,
gcd = "spell",
spend = 25,
spendType = "energy",
startsCombat = false,
texture = 132306,
usable = function()
return combo_points.current > 0, "requires combo_points"
end,
handler = function ()
if talent.alacrity.enabled and combo_points.current > 4 then
addStack( "alacrity", 20, 1 )
end
local combo = min( talent.deeper_stratagem.enabled and 6 or 5, combo_points.current )
applyBuff( "slice_and_dice", 6 + 6 * combo )
spend( combo, "combo_points" )
end,
},
sprint = {
id = 2983,
cast = 0,
cooldown = 120,
gcd = "off",
startsCombat = false,
texture = 132307,
handler = function ()
applyBuff( "sprint" )
end,
},
stealth = {
id = function () return talent.subterfuge.enabled and 115191 or 1784 end,
known = 1784,
cast = 0,
cooldown = 2,
gcd = "off",
startsCombat = false,
texture = 132320,
usable = function ()
if time > 0 then return false, "cannot use in combat" end
if buff.stealth.up then return false, "cannot use in stealth" end
if buff.vanish.up then return false, "cannot use while vanished" end
return true
end,
readyTime = function () return buff.shadow_dance.remains end,
handler = function ()
applyBuff( "stealth" )
if talent.shot_in_the_dark.enabled then applyBuff( "shot_in_the_dark" ) end
if talent.premeditation.enabled then applyBuff( "premeditation" ) end
emu_stealth_change = query_time
if conduit.cloaked_in_shadows.enabled then applyBuff( "cloaked_in_shadows" ) end
if conduit.fade_to_nothing.enabled then applyBuff( "fade_to_nothing" ) end
end,
copy = { 1784, 115191 }
},
symbols_of_death = {
id = 212283,
cast = 0,
charges = 1,
cooldown = 30,
recharge = 30,
gcd = "off",
spend = -40,
spendType = "energy",
startsCombat = false,
texture = 252272,
handler = function ()
applyBuff( "symbols_of_death" )
applyBuff( "symbols_of_death_crit" )
if legendary.the_rotten.enabled then applyBuff( "the_rotten" ) end
end,
},
tricks_of_the_trade = {
id = 57934,
cast = 0,
cooldown = 30,
gcd = "spell",
startsCombat = false,
texture = 236283,
usable = function () return group, "requires an ally" end,
handler = function ()
applyBuff( "tricks_of_the_trade" )
end,
},
vanish = {
id = 1856,
cast = 0,
cooldown = 120,
gcd = "off",
toggle = "cooldowns",
startsCombat = false,
texture = 132331,
disabled = function ()
return not settings.solo_vanish and not ( boss and group ), "can only vanish in a boss encounter or with a group"
end,
handler = function ()
applyBuff( "vanish", 3 )
applyBuff( "stealth" )
emu_stealth_change = query_time
if legendary.deathly_shadows.enabled then
gain( 5, "combo_points" )
applyBuff( "deathly_shadows" )
end
if legendary.invigorating_shadowdust.enabled then
for name, cd in pairs( cooldown ) do
if cd.remains > 0 then reduceCooldown( name, 20 ) end
end
end
if conduit.cloaked_in_shadows.enabled then applyBuff( "cloaked_in_shadows" ) end
if conduit.fade_to_nothing.enabled then applyBuff( "fade_to_nothing" ) end
end,
},
wound_poison = {
id = 8679,
cast = 1.5,
cooldown = 0,
gcd = "spell",
startsCombat = false,
texture = 134194,
readyTime = function () return buff.lethal_poison.remains - 120 end,
handler = function ()
applyBuff( "wound_poison" )
end,
},
} )
-- Override this for rechecking.
spec:RegisterAbility( "shadowmeld", {
id = 58984,
cast = 0,
cooldown = 120,
gcd = "off",
usable = function () return boss and group end,
handler = function ()
applyBuff( "shadowmeld" )
end,
} )
spec:RegisterOptions( {
enabled = true,
aoe = 2,
nameplates = true,
nameplateRange = 8,
damage = true,
damageExpiration = 6,
potion = "phantom_fire",
package = "Subtlety",
} )
spec:RegisterSetting( "mfd_points", 3, {
name = "|T236340:0|t Marked for Death Combo Points",
desc = "The addon will only recommend |T236364:0|t Marked for Death when you have the specified number of combo points or fewer.",
type = "range",
min = 0,
max = 5,
step = 1,
width = "full"
} )
spec:RegisterSetting( "priority_rotation", false, {
name = "Use Priority Rotation (Funnel Damage)",
desc = "If checked, the default priority will recommend building combo points with |T1375677:0|t Shuriken Storm and spending on single-target finishers.",
type = "toggle",
width = "full"
})
spec:RegisterSetting( "solo_vanish", true, {
name = "Allow |T132331:0|t Vanish when Solo",
desc = "If unchecked, the addon will not recommend |T132331:0|t Vanish when you are alone (to avoid resetting combat).",
type = "toggle",
width = "full"
} )
spec:RegisterSetting( "allow_shadowmeld", nil, {
name = "Allow |T132089:0|t Shadowmeld",
desc = "If checked, |T132089:0|t Shadowmeld can be recommended for Night Elves when its conditions are met. Your stealth-based abilities can be used in Shadowmeld, even if your action bar does not change. " ..
"Shadowmeld can only be recommended in boss fights or when you are in a group (to avoid resetting combat).",
type = "toggle",
width = "full",
get = function () return not Hekili.DB.profile.specs[ 261 ].abilities.shadowmeld.disabled end,
set = function ( _, val )
Hekili.DB.profile.specs[ 261 ].abilities.shadowmeld.disabled = not val
end,
} )
spec:RegisterPack( "Subtlety", 20210709, [[deLB(bqiuL6ruPytkrFIkjJsLItPsQvrvj5vuv1SOsCluPYUuXVqLyyOk5yOkwgKkpdvsMgvQ6AuP02usL(gvsX4uPu6CQukwNkL08uj5EOI9rvX)qLuLoiQuvlesvpKQsnruPCrLuLAJujvFevsLrIkPQojvLuRes5LkPkYmvPuDtuPk7ujLFQKQWqPQelvjv8uuAQuv5QOskBLkPKVQsjgRsQQZQKQO2Re)vsdMYHfTyO6Xk1Kj5YiBgIpdjJgkNwQvRKQKxRsmBsDBQYUb(TIHtfhNkPulh0Zv10fUok2Us47OQgpvQCELK1JkPkMVkv7N4cpf)kSQmOYAOJxOJhE5A41T5GoEHo37w0vyJvouH1j3xsuuHfKEuHLLbp0uSQW6KR0tQk(vy)HbUPclweo)TYfUGQdmg8ZE84Y3Em6m6bSHjsWLV92CPWIZ06Wxdk4fwvguzn0Xl0XdVCn862CqhVqN7D)TPWMmb2alSSTNVlSyTsrGcEHvr)UWYYGhAkwj26mOyibn0y0Re724IyOJxOJhbnbnFJLau0FRcACNyCVzbj2Ie2jUMompvDG9a7yvforg9aeJXrSFeRdX6xSNcXWjKbsIXNeJ5jX64iOXDI57XdVbKyEm6OD0Ky7uRR5o6bu19hIrGa20lwmIbjfZMeZzcceDQfds8h4LJGg3jg3lVqI56A6X2WejeRbbbHmoHynqS94HNHynIy8jXwVy(qmvReRdXqgOylgDgTMQ)OxqG4uy19hFXVc7huQdmsv8RSgpf)kSeiX1KQG(cBUJEaf2hlvd)pG9fQWQOFdBNOhqH1xJigBqPoW4YIe0pMyjKeJXXfXyEsmwSun8)a2xiXIrmCcqiDigcC8elWiXCY)7fKy4dG5flbkXC9gOe7wO8cG(3fXOfeqSgrm(KyjKeldX8s3jMV9fXUHbOP)fJ5BakX4E5heumU))5)n46c7g2bb7SWEJy4miiNpOuhyhghXUFxmCgeKZIe0p2HXrSRfBPyE5heSM)N)3GkK8Yg8IXrmEvIYAOR4xHLajUMuf0xyv0VHTt0dOW66nOFmXYqm37Vy(2xeJFhydtig3yDrm36Vy87atmUX6Iyjqj26kg)oWeJBSILibbfZ1kb9JvyZD0dOWUtTUM7Ohqv3Fuy3WoiyNfwcbH2rVGQ7XdFQotdIxmF4i22P6LUR(oeqj297IHZGGCEmgyFHa1yGGunhghXwk2E8WNQZ0G4pkcP3Di2vCedDID)UyVdP11iHOO4ppgdSVqG6hd0tmF4iM7fBPyeccTJEbv3Jh(uDMgeVy(Wrm3l297IThp8P6mni(JIq6DhIDfhX4rmUtSBelsnbIJIihcw)aMrII8oeiX1KsSLIHZGGCwKG(XomoIDDHv3FubPhvyrAq)yLOSgxv8RWsGextQc6lSByheSZc7huQdmsDEY57xSLI9oKwxJeIII)8ymW(cbQFmqpXUsm3xyZD0dOW(yPA4)bSVqLOSM7l(vyjqIRjvb9f2nSdc2zHnsnbIdOrHfFK6le8qGextkXwkgKbqidefDIgSQgJ76DfxNk6qGextkXwk27qADnsikk(ZJXa7leO(Xa9e7kXCBHn3rpGc7J1lkrzn3w8RWsGextQc6lS5o6buyFSun8)a2xOc7E1wt1iHOO4lRXtHDd7GGDwy5TylsyN4A6W8u1b2dSJvv4ez0dqSLIPiCgeKdsduv(uEbq)FGKx2GxSReJhXwk27qADnsikk(ZJXa7leO(Xa9e7koIXvITuSiHOO4eThvJPQAsmUtmi5Ln4fZhXw3cRI(nSDIEafwUMJyXigxjwKquu8IDdyeZb2Z1IDHihXyCeZ1BGsSBHYla6FXWxj2E1w3auIXILQH)hW(cDkrzT1T4xHLajUMuf0xyZD0dOW(yPA4)bSVqfwf9By7e9akSU(afZb2dSJvIbNiJEaUigZtIXILQH)hW(cj2SGGIXgd0tm(DGj2TW9elrLn4dXyCelgXCVyrcrrXl2afRreZ1VfX6xmida0auIniiIDZaelbRel9ggqi2GiwKquu8xxy3WoiyNf2fjStCnDyEQ6a7b2XQkCIm6bi2sXUrmfHZGGCqAGQYNYla6)dK8Yg8IDLy8i297IfPMaXHpLodWl)GGhcK4Asj2sXEhsRRrcrrXFEmgyFHa1pgONyxXrm3l21LOSMRP4xHLajUMuf0xy3WoiyNf23H06AKquu8I5dhX4kX8xSBedNbb5eyuforqGdJJy3VlgKbqidefDYlzc7V(dJUIatuEeioeiX1KsSRfBPy3igodcY5x5Hp6VoivfLbwnzIzd74W4i297IXBXWzqqooqYJuDKrpGdJJy3Vl27qADnsikkEX8HJyUvSRlS5o6buyFmgyFHa1pgOxjkRDBl(vyjqIRjvb9f2Ch9akSpwQg(Fa7luHvr)g2orpGcllwQg(Fa7lKyXigKqG0JjMR3aLy3cLxa0)ILaLyXigbEgijgFsSDceBNq4kXMfeuSumegTwmx)weRbXiwGrIbi3fIXoCtSgrmN5)gxtNc7g2bb7SWQiCgeKdsduv(uEbq)FGKx2GxSR4igpID)Uy7z0QHp48R8Wh9xhKQIYa7ajVSbVyxjgp3wXwkMIWzqqoinqv5t5fa9)bsEzdEXUsS9mA1WhC(vE4J(RdsvrzGDGKx2GVeL1Unf)kSeiX1KQG(c7g2bb7SWIZGGCCiiYaZGu1fud(Zh5(Iy(Wrm3k2sX2dqX0XXHGidmdsvxqn4pWeCrmF4igpCvHn3rpGclk9mE46urLOSgp8Q4xHn3rpGc7JLQH)hW(cvyjqIRjvb9LOSgp8u8RWsGextQc6lSByheSZclVflsikko9xXN)fBPy7XdFQotdI)OiKE3Hy(WrmEeBPy4miiNhBIAdQbgvvj8YHXrSLIracIA1jApQgt198smFed1wD8s3vyZD0dOWUXO0P(ytuIsuyvesYOJIFL14P4xHn3rpGc7LEFPWsGextQc6lrzn0v8RWsGextQc6lSJtH9POWM7OhqHDrc7extf2fPMHkS4miiNx3BQMavv1B6W4i297I9oKwxJeIII)8ymW(cbQFmqpX8HJyRBHvr)g2orpGclx7jLyXiMIcc61asm(yuGrqX2ZOvdFWlg)SdXqgOySaUjgE(KsSbiwKquu8Nc7IewbPhvyFGQUhGQJEaLOSgxv8RWsGextQc6lSJtH9POWM7OhqHDrc7extf2fPMHkSoWEGDSQcNiJEaITuS3H06AKquu8NhJb2xiq9Jb6jMpCedDfwf9By7e9akSRha9kX2yjafjgCIm6biwJigFsmSCbjMdShyhRQWjYOhGypfILaLyEm6OD0KyrcrrXlgJZPWUiHvq6rfwMNQoWEGDSQcNiJEaLOSM7l(vyjqIRjvb9f2XPW(uuyZD0dOWUiHDIRPc7IuZqfw05wX8xSi1eiolAud8qGextkX8vIHoEjM)IfPMaXXl)GG1bP(yPA4)hcK4AsjMVsm0XlX8xSi1eiopwQg(vKzZ8hcK4AsjMVsm05wX8xSi1eioPo3WowDiqIRjLy(kXqhVeZFXqNBfZxj2nI9oKwxJeIII)8ymW(cbQFmqpX8HJyUxSRlSk63W2j6buy5ApPelgXuesdiX4JraXIrmMNe7dk1bMy(MBVydumCMwRi4xyxKWki9Oc7huQdSAGbPhB0QsuwZTf)kSeiX1KQG(cRI(nSDIEafwFJr7lI5BU9ILHyin8JcBUJEaf2DQ11Ch9aQ6(JcRU)OcspQWUvFjkRTUf)kSeiX1KQG(cRI(nSDIEaf21HbigcJwVsSNFhBm6flgXcmsm2GsDGrkXwNjYOhGy3GVsm10auI9JlI1HyidCtVyoZOBakXAeXatG1auI1Vy5IS1jUMU(uyZD0dOWcza1Ch9aQ6(Jc7g2bb7SW(bL6aJuNuRlS6(Jki9Oc7huQdmsvIYAUMIFfwcK4AsvqFHn3rpGc7R7nvtGQQ6nvyv0VHTt0dOWY9DC0ReJv3BsSeOeJB9MeldXqN)I5BFrmfdSbOelWiXqA4h
5 years ago
end