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.

1653 lines
86 KiB

5 years ago
-- DeathKnightUnholy.lua
-- June 2018
local addon, ns = ...
local Hekili = _G[ addon ]
local class = Hekili.Class
local state = Hekili.State
local roundUp = ns.roundUp
local FindUnitBuffByID = ns.FindUnitBuffByID
local PTR = ns.PTR
-- Conduits
-- [x] Convocation of the Dead
-- [-] Embrace Death
-- [x] Eternal Hunger
-- [x] Lingering Plague
if UnitClassBase( "player" ) == "DEATHKNIGHT" then
local spec = Hekili:NewSpecialization( 252 )
spec:RegisterResource( Enum.PowerType.Runes, {
rune_regen = {
last = function ()
return state.query_time
end,
interval = function( time, val )
local r = state.runes
if val == 6 then return -1 end
return r.expiry[ val + 1 ] - time
end,
stop = function( x )
return x == 6
end,
value = 1,
},
}, setmetatable( {
expiry = { 0, 0, 0, 0, 0, 0 },
cooldown = 10,
regen = 0,
max = 6,
forecast = {},
fcount = 0,
times = {},
values = {},
resource = "runes",
reset = function()
local t = state.runes
for i = 1, 6 do
local start, duration, ready = GetRuneCooldown( i )
start = start or 0
duration = duration or ( 10 * state.haste )
start = roundUp( start, 2 )
t.expiry[ i ] = ready and 0 or start + duration
t.cooldown = duration
end
table.sort( t.expiry )
t.actual = nil
end,
gain = function( amount )
local t = state.runes
for i = 1, amount do
t.expiry[ 7 - i ] = 0
end
table.sort( t.expiry )
t.actual = nil
end,
spend = function( amount )
local t = state.runes
for i = 1, amount do
if t.expiry[ 4 ] > state.query_time then
t.expiry[ 1 ] = t.expiry[ 4 ] + t.cooldown
else
t.expiry[ 1 ] = state.query_time + t.cooldown
end
table.sort( t.expiry )
end
if amount > 0 then
state.gain( amount * 10, "runic_power" )
if state.set_bonus.tier20_4pc == 1 then
state.cooldown.army_of_the_dead.expires = max( 0, state.cooldown.army_of_the_dead.expires - 1 )
end
end
t.actual = nil
end,
timeTo = function( x )
return state:TimeToResource( state.runes, x )
end,
}, {
__index = function( t, k, v )
if k == "actual" then
local amount = 0
for i = 1, 6 do
if t.expiry[ i ] <= state.query_time then
amount = amount + 1
end
end
return amount
elseif k == "current" then
-- If this is a modeled resource, use our lookup system.
if t.forecast and t.fcount > 0 then
local q = state.query_time
local index, slice
if t.values[ q ] then return t.values[ q ] end
for i = 1, t.fcount do
local v = t.forecast[ i ]
if v.t <= q then
index = i
slice = v
else
break
end
end
-- We have a slice.
if index and slice then
t.values[ q ] = max( 0, min( t.max, slice.v ) )
return t.values[ q ]
end
end
return t.actual
elseif k == "deficit" then
return t.max - t.current
elseif k == "time_to_next" then
return t[ "time_to_" .. t.current + 1 ]
elseif k == "time_to_max" then
return t.current == 6 and 0 or max( 0, t.expiry[6] - state.query_time )
elseif k == "add" then
return t.gain
else
local amount = k:match( "time_to_(%d+)" )
amount = amount and tonumber( amount )
if amount then return state:TimeToResource( t, amount ) end
end
end
} ) )
spec:RegisterResource( Enum.PowerType.RunicPower, {
swarming_mist = {
aura = "swarming_mist",
last = function ()
local app = state.debuff.swarming_mist.applied
local t = state.query_time
return app + floor( ( t - app ) / class.auras.swarming_mist.tick_time ) * class.auras.swarming_mist.tick_time
end,
interval = function () return class.auras.swarming_mist.tick_time end,
value = function () return min( 15, state.true_active_enemies * 3 ) end,
},
} )
spec:RegisterStateFunction( "apply_festermight", function( n )
if azerite.festermight.enabled then
if buff.festermight.up then
addStack( "festermight", buff.festermight.remains, n )
else
applyBuff( "festermight", nil, n )
end
end
end )
local spendHook = function( amt, resource, noHook )
if amt > 0 and resource == "runes" and active_dot.shackle_the_unworthy > 0 then
reduceCooldown( "shackle_the_unworthy", 4 * amt )
end
end
spec:RegisterHook( "spend", spendHook )
-- Talents
spec:RegisterTalents( {
infected_claws = 22024, -- 207272
all_will_serve = 22025, -- 194916
clawing_shadows = 22026, -- 207311
bursting_sores = 22027, -- 207264
ebon_fever = 22028, -- 207269
unholy_blight = 22029, -- 115989
grip_of_the_dead = 22516, -- 273952
deaths_reach = 22518, -- 276079
asphyxiate = 22520, -- 108194
pestilent_pustules = 22522, -- 194917
harbinger_of_doom = 22524, -- 276023
soul_reaper = 22526, -- 343294
spell_eater = 22528, -- 207321
wraith_walk = 22529, -- 212552
death_pact = 23373, -- 48743
pestilence = 22532, -- 277234
unholy_pact = 22534, -- 319230
defile = 22536, -- 152280
army_of_the_damned = 22030, -- 276837
summon_gargoyle = 22110, -- 49206
unholy_assault = 22538, -- 207289
} )
-- PvP Talents
spec:RegisterPvpTalents( {
dark_simulacrum = 41, -- 77606
deaths_echo = 5428, -- 356367
dome_of_ancient_shadow = 5367, -- 328718
doomburst = 5436, -- 356512
life_and_death = 40, -- 288855
necromancers_bargain = 3746, -- 288848
necrotic_aura = 3437, -- 199642
necrotic_wounds = 149, -- 356520
raise_abomination = 3747, -- 288853
reanimation = 152, -- 210128
spellwarden = 5423, -- 356332
strangulate = 5430, -- 47476
} )
-- Auras
spec:RegisterAuras( {
antimagic_shell = {
id = 48707,
duration = function () return ( talent.spell_eater.enabled and 10 or 5 ) + ( conduit.reinforced_shell.mod * 0.001 ) end,
max_stack = 1,
},
antimagic_zone = {
id = 145629,
duration = 8,
max_stack = 1,
},
army_of_the_dead = {
id = 42650,
duration = 4,
max_stack = 1,
},
asphyxiate = {
id = 108194,
duration = 4,
max_stack = 1,
},
chains_of_ice = {
id = 45524,
duration = 8,
max_stack = 1,
},
dark_command = {
id = 56222,
duration = 3,
max_stack = 1,
},
dark_succor = {
id = 101568,
duration = 20,
},
dark_transformation = {
id = 63560,
duration = function () return 15 + ( conduit.eternal_hunger.mod * 0.001 ) end,
generate = function( t )
local name, _, count, _, duration, expires, caster, _, _, spellID, _, _, _, _, timeMod, v1, v2, v3 = FindUnitBuffByID( "pet", 63560 )
if name then
t.name = t.name or name or class.abilities.dark_transformation.name
t.count = count > 0 and count or 1
t.expires = expires
t.duration = duration
t.applied = expires - duration
t.caster = "player"
return
end
t.name = t.name or class.abilities.dark_transformation.name
t.count = 0
t.expires = 0
t.duration = class.auras.dark_transformation.duration
t.applied = 0
t.caster = "nobody"
end,
},
death_and_decay = {
id = 188290,
duration = 10,
max_stack = 1,
},
death_pact = {
id = 48743,
duration = 15,
max_stack = 1,
},
deaths_advance = {
id = 48265,
duration = 10,
max_stack = 1,
},
defile = {
id = 152280,
duration = 10,
},
festering_wound = {
id = 194310,
duration = 30,
max_stack = 6,
--[[ meta = {
stack = function ()
-- Designed to work with Unholy Frenzy, time until 4th Festering Wound would be applied.
local actual = debuff.festering_wound.up and debuff.festering_wound.count or 0
if buff.unholy_frenzy.down or debuff.festering_wound.down then
return actual
end
local slot_time = query_time
local swing, speed = state.swings.mainhand, state.swings.mainhand_speed
local last = swing + ( speed * floor( slot_time - swing ) / swing )
local window = min( buff.unholy_frenzy.expires, query_time ) - last
local bonus = floor( window / speed )
return min( 6, actual + bonus )
end
} ]]
},
frostbolt = {
id = 317792,
duration = 4,
max_stack = 1,
},
gnaw = {
id = 91800,
duration = 0.5,
max_stack = 1,
},
grip_of_the_dead = {
id = 273977,
duration = 3600,
max_stack = 1,
},
icebound_fortitude = {
id = 48792,
duration = 8,
max_stack = 1,
},
lichborne = {
id = 49039,
duration = 10,
max_stack = 1,
},
on_a_pale_horse = {
id = 51986,
},
path_of_frost = {
id = 3714,
duration = 600,
max_stack = 1,k
},
runic_corruption = {
id = 51460,
duration = function () return 3 * haste end,
max_stack = 1,
},
soul_reaper = {
id = 343294,
duration = 5,
type = "Magic",
max_stack = 1,
},
sudden_doom = {
id = 81340,
duration = 10,
max_stack = function () return talent.harbinger_of_doom.enabled and 2 or 1 end,
},
unholy_assault = {
id = 207289,
duration = 12,
max_stack = 1,
},
unholy_blight_buff = {
id = 115989,
duration = 6,
max_stack = 1,
dot = "buff",
},
unholy_blight = {
id = 115994,
duration = 14,
tick_time = function () return 2 * haste end,
max_stack = 4,
copy = { "unholy_blight_debuff", "unholy_blight_dot" }
},
unholy_pact = {
id = 319230,
duration = 15,
max_stack = 1,
},
unholy_strength = {
id = 53365,
duration = 15,
max_stack = 1,
},
virulent_plague = {
id = 191587,
duration = function () return 27 * ( talent.ebon_fever.enabled and 0.5 or 1 ) end,
tick_time = function () return 3 * ( talent.ebon_fever.enabled and 0.5 or 1 ) end,
type = "Disease",
max_stack = 1,
},
wraith_walk = {
id = 212552,
duration = 4,
type = "Magic",
max_stack = 1,
},
-- PvP Talents
crypt_fever = {
id = 288849,
duration = 4,
max_stack = 1,
},
doomburst = {
id = 356518,
duration = 3,
max_stack = 2,
},
necrotic_wound = {
id = 223929,
duration = 18,
max_stack = 1,
},
-- Azerite Powers
cold_hearted = {
id = 288426,
duration = 8,
max_stack = 1
},
festermight = {
id = 274373,
duration = 20,
max_stack = 99,
},
helchains = {
id = 286979,
duration = 15,
max_stack = 1
}
} )
spec:RegisterStateTable( "death_and_decay",
setmetatable( { onReset = function( self ) end },
{ __index = function( t, k )
if k == "ticking" then
return buff.death_and_decay.up
elseif k == "remains" then
return buff.death_and_decay.remains
end
return false
end } ) )
spec:RegisterStateTable( "defile",
setmetatable( { onReset = function( self ) end },
{ __index = function( t, k )
if k == "ticking" then
return buff.death_and_decay.up
elseif k == "remains" then
return buff.death_and_decay.remains
end
return false
end } ) )
spec:RegisterStateExpr( "dnd_ticking", function ()
return death_and_decay.ticking
end )
spec:RegisterStateExpr( "dnd_remains", function ()
return death_and_decay.remains
end )
spec:RegisterStateExpr( "spreading_wounds", function ()
if talent.infected_claws.enabled and buff.dark_transformation.up then return false end -- Ghoul is dumping wounds for us, don't bother.
return azerite.festermight.enabled and settings.cycle and settings.festermight_cycle and cooldown.death_and_decay.remains < 9 and active_dot.festering_wound < spell_targets.festering_strike
end )
spec:RegisterStateFunction( "time_to_wounds", function( x )
if debuff.festering_wound.stack >= x then return 0 end
return 3600
--[[ No timeable wounds mechanic in SL?
if buff.unholy_frenzy.down then return 3600 end
local deficit = x - debuff.festering_wound.stack
local swing, speed = state.swings.mainhand, state.swings.mainhand_speed
local last = swing + ( speed * floor( query_time - swing ) / swing )
local fw = last + ( speed * deficit ) - query_time
if fw > buff.unholy_frenzy.remains then return 3600 end
return fw ]]
end )
spec:RegisterHook( "step", function ( time )
if Hekili.ActiveDebug then Hekili:Debug( "Rune Regeneration Time: 1=%.2f, 2=%.2f, 3=%.2f, 4=%.2f, 5=%.2f, 6=%.2f\n", runes.time_to_1, runes.time_to_2, runes.time_to_3, runes.time_to_4, runes.time_to_5, runes.time_to_6 ) end
end )
spec:RegisterGear( "tier19", 138355, 138361, 138364, 138349, 138352, 138358 )
spec:RegisterGear( "tier20", 147124, 147126, 147122, 147121, 147123, 147125 )
spec:RegisterAura( "master_of_ghouls", {
id = 246995,
duration = 3,
max_stack = 1
} )
spec:RegisterGear( "tier21", 152115, 152117, 152113, 152112, 152114, 152116 )
spec:RegisterAura( "coils_of_devastation", {
id = 253367,
duration = 4,
max_stack = 1
} )
spec:RegisterGear( "acherus_drapes", 132376 )
spec:RegisterGear( "cold_heart", 151796 ) -- chilled_heart stacks NYI
spec:RegisterAura( "cold_heart_item", {
id = 235599,
duration = 3600,
max_stack = 20
} )
spec:RegisterGear( "consorts_cold_core", 144293 )
spec:RegisterGear( "death_march", 144280 )
-- spec:RegisterGear( "death_screamers", 151797 )
spec:RegisterGear( "draugr_girdle_of_the_everlasting_king", 132441 )
spec:RegisterGear( "koltiras_newfound_will", 132366 )
spec:RegisterGear( "lanathels_lament", 133974 )
spec:RegisterGear( "perseverance_of_the_ebon_martyr", 132459 )
spec:RegisterGear( "rethus_incessant_courage", 146667 )
spec:RegisterGear( "seal_of_necrofantasia", 137223 )
spec:RegisterGear( "shackles_of_bryndaor", 132365 ) -- NYI
spec:RegisterGear( "soul_of_the_deathlord", 151740 )
spec:RegisterGear( "soulflayers_corruption", 151795 )
spec:RegisterGear( "the_instructors_fourth_lesson", 132448 )
spec:RegisterGear( "toravons_whiteout_bindings", 132458 )
spec:RegisterGear( "uvanimor_the_unbeautiful", 137037 )
spec:RegisterPet( "ghoul", 26125, "raise_dead", 3600 )
spec:RegisterTotem( "gargoyle", 458967 )
spec:RegisterTotem( "abomination", 298667 )
spec:RegisterPet( "apoc_ghoul", 24207, "apocalypse", 15 )
spec:RegisterPet( "army_ghoul", 24207, "army_of_the_dead", 30 )
local ForceVirulentPlagueRefresh = setfenv( function ()
target.updated = true
Hekili:ForceUpdate( "VIRULENT_PLAGUE_REFRESH" )
end, state )
local After = C_Timer.After
spec:RegisterHook( "COMBAT_LOG_EVENT_UNFILTERED", function( event, _, subtype, _, sourceGUID, sourceName, _, _, destGUID, destName, destFlags, _, spellID )
if sourceGUID == GUID and subtype == "SPELL_CAST_SUCCESS" and spellID == 77575 then
After( state.latency, ForceVirulentPlagueRefresh )
After( state.latency * 2, ForceVirulentPlagueRefresh )
end
end )
local any_dnd_set, wound_spender_set = false, false
local ExpireRunicCorruption = setfenv( function()
local debugstr
if Hekili.ActiveDebug then debugstr = format( "Runic Corruption expired; updating regen from %.2f to %.2f at %.2f + %.2f.", rune.cooldown, rune.cooldown * 2, offset, delay ) end
rune.cooldown = rune.cooldown * 2
for i = 1, 6 do
local exp = rune.expiry[ i ] - query_time
if exp > 0 then
rune.expiry[ i ] = rune.expiry[ i ] + exp
if Hekili.ActiveDebug then debugstr = format( "%s\n - rune %d extended by %.2f [%.2f].", debugstr, i, exp, rune.expiry[ i ] - query_time ) end
end
end
table.sort( rune.expiry )
rune.actual = nil
if Hekili.ActiveDebug then debugstr = format( "%s\n - %d, %.2f %.2f %.2f %.2f %.2f %.2f.", debugstr, rune.current, rune.expiry[1] - query_time, rune.expiry[2] - query_time, rune.expiry[3] - query_time, rune.expiry[4] - query_time, rune.expiry[5] - query_time, rune.expiry[6] - query_time ) end
forecastResources( "runes" )
if Hekili.ActiveDebug then debugstr = format( "%s\n - %d, %.2f %.2f %.2f %.2f %.2f %.2f.", debugstr, rune.current, rune.expiry[1] - query_time, rune.expiry[2] - query_time, rune.expiry[3] - query_time, rune.expiry[4] - query_time, rune.expiry[5] - query_time, rune.expiry[6] - query_time ) end
if debugstr then Hekili:Debug( debugstr ) end
end, state )
spec:RegisterHook( "reset_precast", function ()
if buff.runic_corruption.up then
state:QueueAuraExpiration( "runic_corruption", ExpireRunicCorruption, buff.runic_corruption.expires )
end
local expires = action.summon_gargoyle.lastCast + 35
if expires > now then
summonPet( "gargoyle", expires - now )
end
local control_expires = action.control_undead.lastCast + 300
if control_expires > now and pet.up and not pet.ghoul.up then
summonPet( "controlled_undead", control_expires - now )
end
local apoc_expires = action.apocalypse.lastCast + 15
if apoc_expires > now then
summonPet( "apoc_ghoul", apoc_expires - now )
end
local army_expires = action.army_of_the_dead.lastCast + 30
if army_expires > now then
summonPet( "army_ghoul", army_expires - now )
end
if talent.all_will_serve.enabled and pet.ghoul.up then
summonPet( "skeleton" )
end
rawset( cooldown, "army_of_the_dead", nil )
rawset( cooldown, "raise_abomination", nil )
if pvptalent.raise_abomination.enabled then
cooldown.army_of_the_dead = cooldown.raise_abomination
else
cooldown.raise_abomination = cooldown.army_of_the_dead
end
if state:IsKnown( "deaths_due" ) then
class.abilities.any_dnd = class.abilities.deaths_due
cooldown.any_dnd = cooldown.deaths_due
setCooldown( "death_and_decay", cooldown.deaths_due.remains )
elseif state:IsKnown( "defile" ) then
class.abilities.any_dnd = class.abilities.defile
cooldown.any_dnd = cooldown.defile
setCooldown( "death_and_decay", cooldown.defile.remains )
else
class.abilities.any_dnd = class.abilities.death_and_decay
cooldown.any_dnd = cooldown.death_and_decay
end
if not any_dnd_set then
class.abilityList.any_dnd = "|T136144:0|t |cff00ccff[Any]|r " .. class.abilities.death_and_decay.name
any_dnd_set = true
end
if state:IsKnown( "clawing_shadows" ) then
class.abilities.wound_spender = class.abilities.clawing_shadows
cooldown.wound_spender = cooldown.clawing_shadows
else
class.abilities.wound_spender = class.abilities.scourge_strike
cooldown.wound_spender = cooldown.scourge_strike
end
if not wound_spender_set then
class.abilityList.wound_spender = "|T237530:0|t |cff00ccff[Wound Spender]|r"
wound_spender_set = true
end
if state:IsKnown( "deaths_due" ) and cooldown.deaths_due.remains then setCooldown( "death_and_decay", cooldown.deaths_due.remains )
elseif talent.defile.enabled and cooldown.defile.remains then setCooldown( "death_and_decay", cooldown.defile.remains ) end
-- Reset CDs on any Rune abilities that do not have an actual cooldown.
for action in pairs( class.abilityList ) do
local data = class.abilities[ action ]
if data.cooldown == 0 and data.spendType == "runes" then
setCooldown( action, 0 )
end
end
end )
local mt_runeforges = {
__index = function( t, k )
return false
end,
}
-- Not actively supporting this since we just respond to the player precasting AOTD as they see fit.
spec:RegisterStateTable( "death_knight", setmetatable( {
disable_aotd = false,
delay = 6,
runeforge = setmetatable( {}, mt_runeforges )
}, {
__index = function( t, k )
if k == "fwounded_targets" then return state.active_dot.festering_wound end
return 0
end,
} ) )
local runeforges = {
[6243] = "hysteria",
[3370] = "razorice",
[6241] = "sanguination",
[6242] = "spellwarding",
[6245] = "apocalypse",
[3368] = "fallen_crusader",
[3847] = "stoneskin_gargoyle",
[6244] = "unending_thirst"
}
local function ResetRuneforges()
table.wipe( state.death_knight.runeforge )
end
local function UpdateRuneforge( slot, item )
if ( slot == 16 or slot == 17 ) then
local link = GetInventoryItemLink( "player", slot )
local enchant = link:match( "item:%d+:(%d+)" )
if enchant then
enchant = tonumber( enchant )
local name = runeforges[ enchant ]
if name then
state.death_knight.runeforge[ name ] = true
if name == "razorice" and slot == 16 then
state.death_knight.runeforge.razorice_mh = true
elseif name == "razorice" and slot == 17 then
state.death_knight.runeforge.razorice_oh = true
end
end
end
end
end
Hekili:RegisterGearHook( ResetRuneforges, UpdateRuneforge )
-- Abilities
spec:RegisterAbilities( {
antimagic_shell = {
id = 48707,
cast = 0,
cooldown = 60,
gcd = "off",
toggle = "defensives",
startsCombat = false,
texture = 136120,
handler = function ()
applyBuff( "antimagic_shell" )
end,
},
antimagic_zone = {
id = 51052,
cast = 0,
cooldown = 180,
gcd = "spell",
toggle = "defensives",
startsCombat = false,
texture = 237510,
handler = function ()
applyBuff( "antimagic_zone" )
end,
},
apocalypse = {
id = 275699,
cast = 0,
cooldown = function () return ( essence.vision_of_perfection.enabled and 0.87 or 1 ) * ( ( pvptalent.necromancers_bargain.enabled and 45 or 90 ) - ( level > 48 and 15 or 0 ) ) end,
gcd = "spell",
toggle = function () return not talent.army_of_the_damned.enabled and "cooldowns" or nil end,
startsCombat = true,
texture = 1392565,
handler = function ()
summonPet( "apoc_ghoul", 15 )
if pvptalent.necrotic_wounds.enabled and debuff.festering_wound.up and debuff.necrotic_wound.down then
applyDebuff( "target", "necrotic_wound" )
end
if debuff.festering_wound.stack > 4 then
applyDebuff( "target", "festering_wound", debuff.festering_wound.remains, debuff.festering_wound.remains - 4 )
apply_festermight( 4 )
if conduit.convocation_of_the_dead.enabled and cooldown.apocalypse.remains > 0 then
reduceCooldown( "apocalypse", 4 * conduit.convocation_of_the_dead.mod * 0.1 )
end
gain( 12, "runic_power" )
else
gain( 3 * debuff.festering_wound.stack, "runic_power" )
apply_festermight( debuff.festering_wound.stack )
if conduit.convocation_of_the_dead.enabled and cooldown.apocalypse.remains > 0 then
reduceCooldown( "apocalypse", debuff.festering_wound.stack * conduit.convocation_of_the_dead.mod * 0.1 )
end
removeDebuff( "target", "festering_wound" )
end
if level > 57 then gain( 2, "runes" ) end
if pvptalent.necromancers_bargain.enabled then applyDebuff( "target", "crypt_fever" ) end
end,
auras = {
frenzied_monstrosity = {
id = 334895,
duration = 15,
max_stack = 1,
},
frenzied_monstrosity_pet = {
id = 334896,
duration = 15,
max_stack = 1
}
}
},
army_of_the_dead = {
id = function () return pvptalent.raise_abomination.enabled and 288853 or 42650 end,
cast = 0,
cooldown = function () return pvptalent.raise_abomination.enabled and 120 or 480 end,
gcd = "spell",
spend = function () return pvptalent.raise_abomination.enabled and 0 or 3 end,
spendType = "runes",
toggle = "cooldowns",
-- nopvptalent = "raise_abomination",
startsCombat = false,
texture = function () return pvptalent.raise_abomination.enabled and 298667 or 237511 end,
handler = function ()
if pvptalent.raise_abomination.enabled then
summonPet( "abomination" )
else
applyBuff( "army_of_the_dead", 4 )
end
end,
copy = { 288853, 42650, "army_of_the_dead", "raise_abomination" }
},
asphyxiate = {
id = 108194,
cast = 0,
cooldown = 45,
gcd = "spell",
startsCombat = true,
texture = 538558,
toggle = "interrupts",
talent = "asphyxiate",
debuff = "casting",
readyTime = state.timeToInterrupt,
handler = function ()
applyDebuff( "target", "asphyxiate" )
end,
},
chains_of_ice = {
id = 45524,
cast = 0,
cooldown = 0,
gcd = "spell",
spend = 1,
spendType = "runes",
startsCombat = true,
texture = 135834,
handler = function ()
applyDebuff( "target", "chains_of_ice" )
removeBuff( "cold_heart_item" )
end,
},
clawing_shadows = {
id = 207311,
cast = 0,
cooldown = 0,
gcd = "spell",
spend = 1,
spendType = "runes",
startsCombat = true,
texture = 615099,
talent = "clawing_shadows",
handler = function ()
if debuff.festering_wound.up then
if debuff.festering_wound.stack > 1 then
applyDebuff( "target", "festering_wound", debuff.festering_wound.remains, debuff.festering_wound.stack - 1 )
else removeDebuff( "target", "festering_wound" ) end
if conduit.convocation_of_the_dead.enabled and cooldown.apocalypse.remains > 0 then
reduceCooldown( "apocalypse", conduit.convocation_of_the_dead.mod * 0.1 )
end
apply_festermight( 1 )
end
gain( 3, "runic_power" )
end,
bind = { "scourge_strike", "wound_spender" }
},
control_undead = {
id = 111673,
cast = 1.5,
cooldown = 0,
gcd = "spell",
spend = 1,
spendType = "runes",
startsCombat = true,
texture = 237273,
usable = function () return target.is_undead and target.level <= level + 1 end,
handler = function ()
dismissPet( "ghoul" )
summonPet( "controlled_undead", 300 )
end,
},
dark_command = {
id = 56222,
cast = 0,
cooldown = 8,
gcd = "off",
startsCombat = true,
texture = 136088,
handler = function ()
applyDebuff( "target", "dark_command" )
end,
},
dark_simulacrum = {
id = 77606,
cast = 0,
cooldown = 20,
gcd = "spell",
spend = 0,
spendType = "runic_power",
startsCombat = true,
texture = 135888,
pvptalent = "dark_simulacrum",
usable = function ()
if not target.is_player then return false, "target is not a player" end
return true
end,
handler = function ()
applyDebuff( "target", "dark_simulacrum" )
end,
},
dark_transformation = {
id = 63560,
cast = 0,
cooldown = 60,
gcd = "spell",
startsCombat = false,
texture = 342913,
usable = function () return pet.ghoul.alive end,
handler = function ()
applyBuff( "dark_transformation" )
if azerite.helchains.enabled then applyBuff( "helchains" ) end
if talent.unholy_pact.enabled then applyBuff( "unholy_pact" ) end
if legendary.frenzied_monstrosity.enabled then
applyBuff( "frenzied_monstrosity" )
applyBuff( "frenzied_monstrosity_pet" )
end
end,
auras = {
frenzied_monstrosity = {
id = 334895,
duration = 15,
max_stack = 1,
},
frenzied_monstrosity_pet = {
id = 334896,
duration = 15,
max_stack = 1
}
}
},
death_and_decay = {
id = 43265,
noOverride = 324128,
cast = 0,
cooldown = 30,
gcd = "spell",
spend = 1,
spendType = "runes",
startsCombat = true,
texture = 136144,
notalent = "defile",
handler = function ()
applyBuff( "death_and_decay", 10 )
if talent.grip_of_the_dead.enabled then applyDebuff( "target", "grip_of_the_dead" ) end
end,
bind = { "defile", "any_dnd" },
copy = "any_dnd"
},
death_coil = {
id = 47541,
cast = 0,
cooldown = 0,
gcd = "spell",
spend = function () return buff.sudden_doom.up and 0 or ( legendary.deadliest_coil.enabled and 30 or 40 ) end,
spendType = "runic_power",
startsCombat = true,
texture = 136145,
handler = function ()
if pvptalent.doomburst.enabled and buff.sudden_doom.up and debuff.festering_wound.up then
if debuff.festering_wound.stack > 2 then
applyDebuff( "target", "festering_wound", debuff.festering_wound.remains, debuff.festering_wound.stack - 2 )
applyDebuff( "target", "doomburst", debuff.doomburst.up and debuff.doomburst.remains or nil, 2 )
else
removeDebuff( "target", "festering_wound" )
applyDebuff( "target", "doomburst", debuff.doomburst.up and debuff.doomburst.remains or nil, debuff.doomburst.stack + 1 )
end
end
removeStack( "sudden_doom" )
if cooldown.dark_transformation.remains > 0 then setCooldown( "dark_transformation", max( 0, cooldown.dark_transformation.remains - 1 ) ) end
if legendary.deadliest_coil.enabled and buff.dark_transformation.up then buff.dark_transformation.expires = buff.dark_transformation.expires + 2 end
if legendary.deaths_certainty.enabled then
local spell = covenant.night_fae and "deaths_due" or ( talent.defile.enabled and "defile" or "death_and_decay" )
if cooldown[ spell ].remains > 0 then reduceCooldown( spell, 2 ) end
end
end,
},
death_grip = {
id = 49576,
cast = 0,
cooldown = 25,
gcd = "spell",
startsCombat = true,
texture = 237532,
handler = function ()
applyDebuff( "target", "death_grip" )
setDistance( 5 )
if conduit.unending_grip.enabled then applyDebuff( "target", "unending_grip" ) end
end,
},
death_pact = {
id = 48743,
cast = 0,
cooldown = 120,
gcd = "spell",
toggle = "defensives",
startsCombat = false,
texture = 136146,
talent = "death_pact",
handler = function ()
gain( health.max * 0.5, "health" )
applyDebuff( "player", "death_pact" )
end,
},
death_strike = {
id = 49998,
cast = 0,
cooldown = 0,
gcd = "spell",
spend = function () return buff.dark_succor.up and 0 or ( ( buff.transfusion.up and 0.5 or 1 ) * 35 ) end,
spendType = "runic_power",
startsCombat = true,
texture = 237517,
handler = function ()
removeBuff( "dark_succor" )
if legendary.deaths_certainty.enabled then
local spell = conduit.night_fae and "deaths_due" or ( talent.defile.enabled and "defile" or "death_and_decay" )
if cooldown[ spell ].remains > 0 then reduceCooldown( spell, 2 ) end
end
end,
},
deaths_advance = {
id = 48265,
cast = 0,
cooldown = 45,
gcd = "spell",
startsCombat = false,
texture = 237561,
handler = function ()
applyBuff( "deaths_advance" )
if conduit.fleeting_wind.enabled then applyBuff( "fleeting_wind" ) end
end,
},
defile = {
id = 152280,
cast = 0,
charges = function ()
if not pvptalent.deaths_echo.enabled then return end
return 2
end,
cooldown = 20,
recharge = function ()
if not pvptalent.unholy_command.enabled then return end
return 20
end,
gcd = "spell",
spend = 1,
spendType = "runes",
talent = "defile",
startsCombat = true,
texture = 1029008,
handler = function ()
applyBuff( "death_and_decay" )
setCooldown( "death_and_decay", 20 )
applyDebuff( "target", "defile", 1 )
end,
bind = { "defile", "any_dnd" },
},
epidemic = {
id = 207317,
cast = 0,
cooldown = 0,
gcd = "spell",
spend = function () return buff.sudden_doom.up and 0 or 30 end,
spendType = "runic_power",
startsCombat = true,
texture = 136066,
targets = {
count = function () return active_dot.virulent_plague end,
},
usable = function () return active_dot.virulent_plague > 0 end,
handler = function ()
removeBuff( "sudden_doom" )
end,
},
festering_strike = {
id = 85948,
cast = 0,
cooldown = 0,
gcd = "spell",
spend = 2,
spendType = "runes",
startsCombat = true,
texture = 879926,
aura = "festering_wound",
cycle = "festering_wound",
min_ttd = function () return min( cooldown.death_and_decay.remains + 3, 8 ) end, -- don't try to cycle onto targets that will die too fast to get consumed.
handler = function ()
applyDebuff( "target", "festering_wound", nil, debuff.festering_wound.stack + 2 )
end,
},
icebound_fortitude = {
id = 48792,
cast = 0,
cooldown = function () return 180 - ( azerite.cold_hearted.enabled and 15 or 0 ) + ( conduit.chilled_resilience.mod * 0.001 ) end,
gcd = "spell",
toggle = "defensives",
startsCombat = false,
texture = 237525,
handler = function ()
applyBuff( "icebound_fortitude" )
if azerite.cold_hearted.enabled then applyBuff( "cold_hearted" ) end
end,
},
lichborne = {
id = 49039,
cast = 0,
cooldown = 60,
gcd = "off",
toggle = "defensives",
startsCombat = false,
texture = 136187,
handler = function ()
applyBuff( "lichborne" )
if conduit.hardened_bones.enabled then applyBuff( "hardened_bones" ) end
end,
},
mind_freeze = {
id = 47528,
cast = 0,
cooldown = 15,
gcd = "spell",
spend = 0,
spendType = "runic_power",
startsCombat = true,
texture = 237527,
toggle = "interrupts",
debuff = "casting",
readyTime = state.timeToInterrupt,
handler = function ()
if conduit.spirit_drain.enabled then gain( conduit.spirit_drain.mod * 0.1, "runic_power" ) end
interrupt()
end,
},
outbreak = {
id = 77575,
cast = 0,
cooldown = 0,
gcd = "spell",
spend = 1,
spendType = "runes",
startsCombat = true,
texture = 348565,
cycle = "virulent_plague",
handler = function ()
applyDebuff( "target", "virulent_plague" )
active_dot.virulent_plague = active_enemies
end,
},
path_of_frost = {
id = 3714,
cast = 0,
cooldown = 0,
gcd = "spell",
spend = 1,
spendType = "runes",
startsCombat = false,
texture = 237528,
handler = function ()
applyBuff( "path_of_frost" )
end,
},
--[[ raise_ally = {
id = 61999,
cast = 0,
cooldown = 600,
gcd = "spell",
spend = 30,
spendType = "runic_power",
startsCombat = false,
texture = 136143,
handler = function ()
end,
}, ]]
raise_dead = {
id = function () return IsActiveSpell( 46584 ) and 46584 or 46585 end,
cast = 0,
cooldown = function () return level < 29 and 120 or 30 end,
gcd = "spell",
startsCombat = false,
texture = 1100170,
essential = true, -- new flag, will allow recasting even in precombat APL.
nomounted = true,
usable = function () return not pet.alive end,
handler = function ()
summonPet( "ghoul", level > 28 and 3600 or 30 )
if talent.all_will_serve.enabled then summonPet( "skeleton", level > 28 and 3600 or 30 ) end
end,
copy = { 46584, 46585 }
},
sacrificial_pact = {
id = 327574,
cast = 0,
cooldown = 120,
gcd = "spell",
spend = 20,
spendType = "runic_power",
toggle = "cooldowns",
startsCombat = true,
texture = 136133,
usable = function () return pet.alive, "requires an undead pet" end,
handler = function ()
dismissPet( "ghoul" )
gain( 0.25 * health.max, "health" )
end,
},
scourge_strike = {
id = 55090,
cast = 0,
cooldown = 0,
gcd = "spell",
spend = 1,
spendType = "runes",
startsCombat = true,
texture = 237530,
notalent = "clawing_shadows",
handler = function ()
gain( 3, "runic_power" )
if debuff.festering_wound.stack > 1 then
applyDebuff( "target", "festering_wound", debuff.festering_wound.remains, debuff.festering_wound.stack - 1 )
else removeDebuff( "target", "festering_wound" ) end
apply_festermight( 1 )
if conduit.lingering_plague.enabled and debuff.virulent_plague.up then
debuff.virulent_plague.expires = debuff.virulent_plague.expires + ( conduit.lingering_plague.mod * 0.001 )
end
end,
bind = { "clawing_shadows", "wound_spender" }
},
soul_reaper = {
id = 343294,
cast = 0,
cooldown = 6,
gcd = "spell",
spend = 1,
spendType = "runes",
startsCombat = true,
texture = 636333,
aura = "soul_reaper",
talent = "soul_reaper",
handler = function ()
applyDebuff( "target", "soul_reaper" )
end,
},
summon_gargoyle = {
id = 49206,
cast = 0,
cooldown = 180,
gcd = "spell",
toggle = "cooldowns",
startsCombat = true,
texture = 458967,
talent = "summon_gargoyle",
handler = function ()
summonPet( "gargoyle", 30 )
end,
},
transfusion = {
id = 288977,
cast = 0,
cooldown = 45,
gcd = "spell",
spend = -20,
spendType = "runic_power",
startsCombat = false,
texture = 237515,
pvptalent = "transfusion",
handler = function ()
applyBuff( "transfusion" )
end,
},
unholy_assault = {
id = 207289,
cast = 0,
cooldown = 75,
gcd = "spell",
toggle = "cooldowns",
startsCombat = true,
texture = 136224,
talent = "unholy_assault",
cycle = "festering_wound",
handler = function ()
applyDebuff( "target", "festering_wound", nil, min( 6, debuff.festering_wound.stack + 4 ) )
applyBuff( "unholy_frenzy" )
stat.haste = stat.haste + 0.1
end,
},
unholy_blight = {
id = 115989,
cast = 0,
cooldown = 45,
gcd = "spell",
spend = 1,
spendType = "runes",
startsCombat = true,
texture = 136132,
talent = "unholy_blight",
handler = function ()
applyBuff( "unholy_blight_buff" )
applyDebuff( "target", "unholy_blight" )
applyDebuff( "target", "virulent_plague" )
active_dot.virulent_plague = active_enemies
end,
},
wraith_walk = {
id = 212552,
cast = 4,
channeled = true,
cooldown = 60,
gcd = "spell",
startsCombat = false,
texture = 1100041,
talent = "wraith_walk",
start = function ()
applyBuff( "wraith_walk" )
end,
},
-- Stub.
any_dnd = {
name = function () return "|T136144:0|t |cff00ccff[Any]|r " .. ( class.abilities.death_and_decay and class.abilities.death_and_decay.name or "Death and Decay" ) end,
},
wound_spender = {
name = "|T237530:0|t |cff00ccff[Wound Spender]|r",
}
} )
spec:RegisterOptions( {
enabled = true,
aoe = 2,
nameplates = true,
nameplateRange = 8,
damage = true,
damageExpiration = 8,
cycle = true,
cycleDebuff = "festering_wound",
enhancedRecheck = true,
potion = "potion_of_spectral_strength",
package = "Unholy",
} )
--[[ spec:RegisterSetting( "festermight_cycle", false, {
name = "Festermight: Spread |T237530:0|t Wounds",
desc = function ()
return "If checked, the addon will encourage you to spread Festering Wounds to multiple targets before |T136144:0|t Death and Decay.\n\n" ..
"Requires |cFF" .. ( state.azerite.festermight.enabled and "00FF00" or "FF0000" ) .. "Festermight|r (Azerite)\n" ..
"Requires |cFF" .. ( state.settings.cycle and "00FF00" or "FF0000" ) .. "Recommend Target Swaps|r in |cFFFFD100Targeting|r section."
end,
type = "toggle",
width = "full"
} ) ]]
spec:RegisterPack( "Unholy", 20210708, [[devYUcqivQ6rQq6sab1Mqv(esXOqsDkKsRskf5vijnluvDlPuWUi8lGOHHkvhtfSmuvEMaQPHKW1uHABQuHVHKiJtLkQZPsfX6KsP3PcHK5ja3dvSpPuDqGalevIhQsLMiqOlQcr2isIsFejr1ivPIuNukf1kbsVuLksMjQKCtviKANQu6NsPqdvfIAPQqWtLIPQsXvvHqTvvieFfiiJvazVa(RunyLoSOftIhl0Kj6YqBgrFwqJwLCAfRgvs1RfOztQBts7wYVv1Wvrhhvsz5GEoktNY1ry7a13rQgpQuoVuY6rsumFKy)unWbGBaAKPHa3Yh357a3PsC)oloCNWDUFmvcOXADIanNzmygIanvQIanhX11RBb0CMT0FkbUbOH9eWic0Cz2jRTGeKHJDrOiIVkizJkHoT5RimjnqYg1iibAuigT1MlafGgzAiWT8XD(oWDQe3VZId3jCN7ubvcOHDIrGB57y(aAUgPelafGgjYIanGiM2LV3PQj8Y89iUUEDlhuqj0T89oZVV8XD(o4G6GE3RScrwBDqBd(ccKCDcMPILX81EFbXcebjiIKJgbjiIPDX8fejqFT33V0T8n(eL5RLWq0y(s)69nHOVi3oXOHsFT3x9ag9v)vOVy9eHx(AVVQPzi0xQZh7m0io99OhOv4G2g8fehwQOrPVnzeoKtCsTVh5mA(QGXKGH(kXu6B41tOz(QMbrFjFOVSu6liENIjCqBd(EeZMk0xqONOK(2CILeH(MkJESbz(Q(q0xsnYTrr3YxQtZxQGQ(YSmgK57umdtPVpPVhtvApIYxq8i34BHegm1(ML0x1SLVNqemwMVSxf9T(2aeJ(YgJiT5lMWbTn47rmBQqFPYImdHtf6BJbNGOVt5liOnEK8Di9T1t47vcg9TE7AQqFrnd91EFLVVzj9L(x0y((Grymp9L(tusMVdZxq8i34BHegm1ch02GV39kRqu6RAwT8LgYj8Y6qunNIrJVXVKJnFLAMV27BEEQB57u(Q8mMVKt4LX89lDlFPwJmMV3fe9LEYm03V81Gj7IwHdABWxqGuIsFZ6Tle6BBKWuGyg0xSmylFT3xgA(sC6lZGFfIqFpsNJevNit4G2g89iG6KB(2CJVGzcFbbTXJKV6pCI(YMkI(oMVqupiZ3V8n(fzQqOtdL(cZr2rWyzmHdABW3BAJGyBST(6lv2mAp03gdIvOD57j8JmFNYEFn4ubrZx9horbqJEygd4gGM8XodnItGBaU9aWnanyLkAucWfGMiCmeojqJet7QhSMWltqs)jkjk7wcdrJ5B7C8n2kQXowO6GmFPqXxjM2vpynHxMGK(tusu2TegIgZ32547X(sHIV37RLASmHcbKztf2zpezcSsfnk9LcfFH5i7iySmrkLmbYTHzmF55lmhzhbJLjsPKjGOAofZ3a447Hd(sHIVKt4L1HOAofZ3a447Hdanz0MVaAYQvxwsad4w(aUbObRurJsaUa0eHJHWjbAU3xWjCsfnko)xpvyhsutSF(0rOV88LAFviijfYegSBWSyKpunT5lbXPV88fsui5ddrHetPEqM1J)OfyLkAu6lpFZOnGXowO6GmFdGJVb2xku8nJ2ag7yHQdY8LJV85lTanz0MVaAKyAx94pAad42adCdqdwPIgLaCbOjchdHtc0CVVGt4KkAuC(VEQWoKOMy)8PJqGMmAZxan45ir1jcya3sfa3a0GvQOrjaxaAYOnFb0qImdHtf2zgCcIanr4yiCsGgjQqqskirMHWPc70FIskywgd6BaC8nW(YZ34)A5tVe55htDRtgkGOAofZ3a8nWanXwrn2TegIgd42dagWThdCdqdwPIgLaCbOjJ28fqdjYmeovyNzWjic0eHJHWjbAKOcbjPGezgcNkSt)jkPGzzmOVb47bGMyROg7wcdrJbC7bad427a4gGgSsfnkb4cqtgT5lGgsKziCQWoZGtqeOjchdHtc0ajkuyJk2TVtf(gGVu7B8FT8PxcjM2vplzxIXSLaIQ5umF5579(APgltirYrJcSsfnk9LcfFJ)RLp9sirYrJciQMtX8LNVwQXYesKC0OaRurJsFPqX34dgRSmrnHxwNmrF55B8FT8PxcjM2fRljqbevZPy(slqtSvuJDlHHOXaU9aGbClvc4gGgSsfnkb4cqtgT5lGg6prj7StSKieOrISiCoT5lGgqOlS81syiA(YONNmFti6RCyPIgL87RDnmFPpATVA08T1t4l7elPVqIczGK(tusMVtXmmL((K(sphBQqFjFOVGybIGeerYrJGeeX0UOH5lisGcGMiCmeojqd1(EVVm0SPczIyROg9LcfFLyAx9G1eEzcs6prjrz3syiAmFBNJVXwrn2XcvhK5lT(YZxjQqqskirMHWPc70FIskywgd6B7(gyF55lKOqHnQy3(EG9naFJ)RLp9sKvRUSKciQMtXamadOjFSRqazgWna3Ea4gGgSsfnkb4cqteogcNeOjJ2ag7yHQdY8nao(EmqtgT5lGMOoPpvyNDLYNodWaULpGBaAWkv0OeGlanr4yiCsGMmAdySJfQoiZxo(Eh(YZxjM2vpynHxMGK(tusu2TegIgZ3254BGbAYOnFb0e1j9Pc7SRu(0zagWTbg4gGgSsfnkb4cqteogcNeOXsnwMqHaYSPc7ShImbwPIgL(YZxQ9vIPD1dwt4LjiP)eLeLDlHHOX8LJVz0gWyhluDqMVuO4Ret7QhSMWltqs)jkjk7wcdrJ5B7C8nW(sRVuO4RLASmHcbKztf2zpezcSsfnk9LNVwQXYerDsFQWo7kLpDMaRurJsF55Ret7QhSMWltqs)jkjk7wcdrJ5B7C89aqtgT5lGg6prj7StSKieWaULkaUbObRurJsaUa0eHJHWjbAO2xfcssbJqkXQl)xvaXmA(sHIV37l4eoPIgfN)RNkSdjQj2pF6i0xA9LNVu7RcbjPqMWGDdMfJ8HQPnFjio9LNVqIcjFyikKyk1dYSE8hTaRurJsF55BgTbm2XcvhK5BaC8nW(sHIVz0gWyhluDqMVC8LpFPfOjJ28fqJet7Qh)rdya3EmWnanyLkAucWfGMiCmeojqdKOMy)8PJqHejN4y(gGVu77bU7lv9vIPD1dwt4LjiP)eLeLDlHHOX8Tn5BG9LwF55Ret7QhSMWltqs)jkjk7wcdrJ5Ba(Eh(YZ379fCcNurJIZ)1tf2He1e7NpDe6lfk(Qqqsky0tO6uHD1HzcItGMmAZxan45ir1jcya3Eha3a0GvQOrjaxaAIWXq4KanqIAI9ZNocfsKCIJ5Ba(Y3X(YZxjM2vpynHxMGK(tusu2TegIgZ3299yF5579(coHtQOrX5)6Pc7qIAI9ZNocbAYOnFb0GNJevNiGbClvc4gGgSsfnkb4cqteogcNeO5EFLyAx9G1eEzcs6prjrz3syiAmF5579(coHtQOrX5)6Pc7qIAI9ZNoc9LcfFjNWlRdr1CkMVb47X(sHIVWCKDemwMiLsMa52WmMV88fMJSJGXYePuYequnNI5Ba(EmqtgT5lGg8CKO6ebmGBVZa3a0KrB(cOH(tuYo7eljcbAWkv0OeGlagWT3ja3a0GvQOrjaxaAIWXq4Kan37l4eoPIgfN)RNkSdjQj2pF6ieOjJ28fqdEosuDIagGb0eIfcNypFe4gGBpaCdqdwPIgLaCbOHHrGM4)A5tVeSNq3HyEIqbevZPyanz0MVaAONJb0eHJHWjbASuJLjypHUdX8eHcSsfnk9LNVwcdrtyJk2TVFgTEGp23a89yF55l5eEzDiQMtX8TDFp2xE(g)xlF6LG9e6oeZtekGOAofZ3a8LAFdJsFBt(YDbv6yFP1xE(MrBaJDSq1bz(gahFdmGbClFa3a0GvQOrjaxaAIWXq4Kanu779(coHtQOrX5)6Pc7qIAI9ZNoc9LcfFviijfmcPeRU8FvbeZO5lT(YZxQ9vHGKuityWUbZIr(q10MVeeN(YZxirHKpmefsmL6bzwp(JwGvQOrPV88nJ2ag7yHQdY8nao(gyFPqX3mAdySJfQoiZx
5 years ago
end