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.

621 lines
21 KiB

--------------------------------------------------------------------------------
-- Module Declaration
--
local mod, CL = BigWigs:NewBoss("Kel'Thuzad", 2450, 2440)
if not mod then return end
mod:RegisterEnableMob(175559, 176703, 176973, 176974, 176929) -- Kel'Thuzad, Frostbound Devoted, Unstoppable Abomination, Soul Reaver, Remnant of Kel'Thuzad
mod:SetEncounterID(2422)
mod:SetRespawnTime(30)
mod:SetStage(1)
--------------------------------------------------------------------------------
-- Locals
--
local mobCollector = {}
local glacialSpikeMarks = {}
local mindControlled = false
local inPhylactry = false
local darkEvocationCount = 1
local blizzardCount = 1
local soulFractureCount = 1
local oblivionsEchoCount = 1
local frostBlastCount = 1
local glacialWrathCount = 1
--------------------------------------------------------------------------------
-- Localization
--
local L = mod:GetLocale()
if L then
L.spikes = "Spikes" -- Short for Glacial Spikes
L.spike = "Spike"
L.silence = mod:SpellName(226452) -- Silence
L.miasma = "Miasma" -- Short for Sinister Miasma
L.custom_on_nameplate_fixate = "Fixate Nameplate Icon"
L.custom_on_nameplate_fixate_desc = "Show an icon on the nameplate of Frostbound Devoted that are fixed on you.\n\nRequires the use of Enemy Nameplates and a supported nameplate addon (KuiNameplates, Plater)."
L.custom_on_nameplate_fixate_icon = 210130
end
--------------------------------------------------------------------------------
-- Initialization
--
local glacialWrathMarker = mod:AddMarkerOption(false, "player", 1, 346459, 1, 2, 3, 4, 5) -- Glacial Wrath
local soulReaverMarker = mod:AddMarkerOption(false, "npc", 8, -23435, 8, 7, 6) -- Soul Reaver
function mod:GetOptions()
return {
"stages",
-- Stage One: Chains and Ice
354198, -- Howling Blizzard
352530, -- Dark Evocation
355389, -- Relentless Haunt
"custom_on_nameplate_fixate",
348071, -- Soul Fracture // Tank hit but spawns Soul Shards for DPS
{348978, "TANK"}, -- Soul Exhaustion
348428, -- Piercing Wail
{346459, "SAY", "SAY_COUNTDOWN"}, -- Glacial Wrath
glacialWrathMarker,
{346530, "ME_ONLY"}, -- Frozen Destruction
{347292, "SAY", "SAY_COUNTDOWN", "ME_ONLY_EMPHASIZE"}, -- Oblivion's Echo
{348760, "SAY", "SAY_COUNTDOWN", "FLASH", "ME_ONLY_EMPHASIZE"}, -- Frost Blast
-- Stage Two: The Phylactery Opens
354289, -- Sinister Miasma
352051, -- Necrotic Surge
352293, -- Vengeful Destruction
355137, -- Shadow Pool
352379, -- Freezing Blast
355055, -- Glacial Winds
355127, -- Foul Winds (Mythic)
352355, -- Undying Wrath
352141, -- Banshee's Cry (Soul Reaver)
soulReaverMarker,
-- Stage Three: The Final Stand
354639, -- Deep Freeze
352348, -- Onslaught of the Damned
},{
["stages"] = "general",
[354198] = mod:SpellName(-22884), -- Stage One: Chains and Ice
[354289] = mod:SpellName(-22885), -- Stage Two: The Phylactery Opens
[354639] = mod:SpellName(-23201) -- Stage Three: The Final Stand
},{
[355389] = CL.fixate, -- Relentless Haunt (Fixate)
[346459] = L.spikes, -- Glacial Wrath (Spikes)
[347292] = L.silence, -- Oblivion's Echo (Silence)
[348760] = CL.meteor, -- Frost Blast (Meteor)
[354289] = L.miasma, -- Necrotic Miasma (Miasma)
[352293] = self:SpellName(249436), -- Necrotic Destruction (Destruction)
}
end
function mod:OnBossEnable()
-- Stage One - Thirst for Blood
self:Log("SPELL_CAST_START", "HowlingBlizzardStart", 354198)
self:Log("SPELL_CAST_SUCCESS", "HowlingBlizzard", 354198)
self:Log("SPELL_AURA_APPLIED", "DarkEvocation", 352530) -- No _SUCCESS on the channel
self:Log("SPELL_AURA_APPLIED", "RelentlessHauntApplied", 355389)
self:Log("SPELL_AURA_REMOVED", "RelentlessHauntRemoved", 355389)
self:Log("SPELL_CAST_START", "SoulFractureStart", 348071)
self:Log("SPELL_CAST_SUCCESS", "SoulFractureSuccess", 348071)
self:Log("SPELL_AURA_APPLIED", "SoulExhaustionApplied", 348978)
self:Log("SPELL_AURA_REMOVED", "SoulExhaustionRemoved", 348978)
self:Log("SPELL_CAST_START", "PiercingWailStart", 348428)
self:Log("SPELL_CAST_START", "GlacialWrath", 346459)
self:Log("SPELL_SUMMON", "GlacialWrathSummon", 346469)
self:Log("SPELL_AURA_APPLIED", "GlacialWrathApplied", 353808)
self:Log("SPELL_AURA_REMOVED", "GlacialWrathRemoved", 353808)
self:Log("SPELL_AURA_APPLIED", "FrozenDestructionApplied", 346530)
self:Log("SPELL_AURA_APPLIED_DOSE", "FrozenDestructionApplied", 346530)
self:Log("SPELL_CAST_START", "OblivionsEcho", 347291, 352997) -- Stage 1, Stage 3
self:Log("SPELL_AURA_APPLIED", "OblivionsEchoApplied", 347292)
self:Log("SPELL_AURA_REMOVED", "OblivionsEchoRemoved", 347292)
self:Log("SPELL_CAST_START", "FrostBlast", 348756)
self:Log("SPELL_CAST_SUCCESS", "FrostBlastSuccess", 348756)
self:Log("SPELL_AURA_APPLIED", "FrostBlastApplied", 348760)
-- Stage Two: The Phylactery Opens
self:Log("SPELL_AURA_APPLIED", "PhylactryApplied", 348787)
self:Log("SPELL_AURA_REMOVED", "PhylactryRemoved", 348787)
self:Log("SPELL_AURA_APPLIED", "SinisterMiasmaApplied", 354289)
self:Log("SPELL_AURA_APPLIED_DOSE", "SinisterMiasmaApplied", 354289)
self:Log("SPELL_AURA_APPLIED", "NecroticSurgeApplied", 352051)
self:Log("SPELL_AURA_APPLIED_DOSE", "NecroticSurgeApplied", 352051)
self:Log("SPELL_CAST_START", "VengefulDestruction", 352293)
self:Log("SPELL_CAST_START", "FreezingBlast", 352379)
self:Log("SPELL_CAST_START", "GlacialWinds", 355055)
self:Log("SPELL_CAST_START", "FoulWinds", 355127)
self:Log("SPELL_CAST_START", "UndyingWrath", 352355)
self:Death("RemnantDeath", 176929) -- Remnant of Kel'Thuzad
self:Log("SPELL_CAST_START", "BansheesCry", 352141)
self:Log("SPELL_SUMMON", "MarchOfTheForsakenSummon", 352094)
-- Stage Three: The Final Stand
self:Log("SPELL_CAST_START", "OnslaughtOfTheDamned", 352348)
self:Log("SPELL_AURA_APPLIED", "GroundDamage", 354198, 354639, 355137) -- Howling Blizzard, Deep Freeze, Shadow Pool
self:Log("SPELL_PERIODIC_DAMAGE", "GroundDamage", 354198, 354639, 355137)
self:Log("SPELL_PERIODIC_MISSED", "GroundDamage", 354198, 354639, 355137)
self:Log("SPELL_AURA_APPLIED", "ReturnOfTheDamned", 348638)
self:Log("SPELL_AURA_REMOVED", "ReturnOfTheDamnedRemoved", 348638)
if self:GetOption("custom_on_nameplate_fixate") then
self:ShowPlates()
end
end
function mod:OnEngage()
self:SetStage(1)
mobCollector = {}
glacialSpikeMarks = {}
mindControlled = false
inPhylactry = false
darkEvocationCount = 1
blizzardCount = 1
soulFractureCount = 1
oblivionsEchoCount = 1
frostBlastCount = 1
glacialWrathCount = 1
-- Soul Fracture and Ice Shards can delay casts
self:CDBar(348071, 8.5, CL.count:format(self:SpellName(348071), soulFractureCount)) -- Soul Fracture (to _SUCCESS) 8.3~9.9
self:CDBar(347292, 10, CL.count:format(L.silence, oblivionsEchoCount)) -- Oblivion's Echo 9.5~11.7
self:CDBar(346459, 19.5, CL.count:format(L.spikes, glacialWrathCount)) -- Glacial Wrath 19.3~21.5
self:CDBar(348760, 43.5, CL.count:format(CL.meteor, frostBlastCount)) -- Frost Blast 43.8~48.4
self:CDBar(352530, 44.5, CL.count:format(self:SpellName(352530), darkEvocationCount)) -- Dark Evocation 44~48
self:CDBar(354198, 89, CL.count:format(self:SpellName(354198), blizzardCount)) -- Howling Blizzard
end
function mod:OnBossDisable()
if self:GetOption("custom_on_nameplate_fixate") then
self:HidePlates()
end
end
--------------------------------------------------------------------------------
-- Event Handlers
--
-- Stage One: Chains and Ice
function mod:HowlingBlizzardStart(args)
self:Message(args.spellId, "cyan", CL.casting:format(args.spellName))
self:PlaySound(args.spellId, "long")
end
function mod:HowlingBlizzard(args)
self:CastBar(args.spellId, 20, CL.count:format(args.spellName, blizzardCount))
blizzardCount = blizzardCount + 1
self:CDBar(args.spellId, 111.2, CL.count:format(args.spellName, blizzardCount))
end
function mod:DarkEvocation(args)
self:Message(args.spellId, "cyan", CL.casting:format(CL.count:format(args.spellName, darkEvocationCount)))
self:PlaySound(args.spellId, "long")
darkEvocationCount = darkEvocationCount + 1
self:CDBar(args.spellId, 111, CL.count:format(args.spellName, darkEvocationCount))
end
function mod:RelentlessHauntApplied(args)
if self:Me(args.destGUID) then
self:PersonalMessage(args.spellId, nil, CL.fixate)
self:PlaySound(args.spellId, "alarm")
if self:GetOption("custom_on_nameplate_fixate") then
self:AddPlateIcon(210130, args.sourceGUID) -- 210130 = ability_fixated_state_red
end
end
end
function mod:RelentlessHauntRemoved(args)
if self:Me(args.destGUID) and self:GetOption("custom_on_nameplate_fixate") then
self:RemovePlateIcon(210130, args.sourceGUID)
end
end
function mod:SoulFractureStart(args)
self:Message(args.spellId, "purple", CL.casting:format(CL.count:format(args.spellName, soulFractureCount)))
self:PlaySound(args.spellId, "alarm")
end
function mod:SoulFractureSuccess(args)
soulFractureCount = soulFractureCount + 1
self:CDBar(args.spellId, 33.2, CL.count:format(args.spellName, soulFractureCount)) -- 33~ or 40.3+ (delayed by a blizzard/dark evocation?)
end
function mod:SoulExhaustionApplied(args)
if self:Tank() and self:Tank(args.destName) then
local unit = self:GetBossId(args.sourceGUID) -- Check if its always boss1, then we dont have to GetBossId
if not self:Me(args.destGUID) and not self:Tanking(unit) then
self:TargetMessage(args.spellId, "purple", args.destName, CL.count:format(args.spellName, soulFractureCount-1))
self:PlaySound(args.spellId, "warning", "taunt", args.destName) -- Not taunted? Play warning sound.
elseif self:Me(args.destGUID) then
self:PersonalMessage(args.spellId)
self:PlaySound(args.spellId, "alarm")
end
end
self:TargetBar(args.spellId, 60, args.destName, CL.count:format(args.spellName, soulFractureCount-1))
end
function mod:SoulExhaustionRemoved(args)
if self:Me(args.destGUID) then
self:Message(args.spellId, "green", CL.removed:format(args.spellName))
self:PlaySound(args.spellId, "info")
end
self:StopBar(args.spellId, args.destName)
end
function mod:PiercingWailStart(args)
local canDo, ready = self:Interrupter(args.sourceGUID)
if canDo then
self:Message(args.spellId, "yellow")
if ready then
self:PlaySound(args.spellId, "alarm")
end
end
end
function mod:GlacialWrath(args)
self:Message(args.spellId, "orange", CL.casting:format(L.spikes))
self:PlaySound(args.spellId, "alert")
self:CDBar(args.spellId, 44.1, L.spikes)
mobCollector = {}
glacialSpikeMarks = {}
if self:GetOption(glacialWrathMarker) then
self:RegisterTargetEvents("GlacialSpikeMarker")
self:ScheduleTimer("UnregisterTargetEvents", 10)
end
end
function mod:GlacialWrathSummon(args)
mobCollector[args.destGUID] = tremove(glacialSpikeMarks, 1)
end
function mod:GlacialSpikeMarker(event, unit, guid)
if self:MobId(guid) == 175861 and mobCollector[guid] then
self:CustomIcon(glacialWrathMarker, unit, mobCollector[guid])
mobCollector[guid] = nil
end
end
do
local playerList = {}
local prev = 0
function mod:GlacialWrathApplied(args)
local t = args.time -- new set of debuffs
if t-prev > 5 then
prev = t
playerList = {}
end
local count = #playerList+1
local icon = count
playerList[count] = args.destName
playerList[args.destName] = icon -- Set raid marker
if self:Me(args.destGUID) then
self:Say(346459, CL.count_rticon:format(L.spike, count, count))
self:SayCountdown(346459, 5, count)
self:PlaySound(346459, "warning")
end
self:NewTargetsMessage(346459, "orange", playerList, nil, L.spike)
self:CustomIcon(glacialWrathMarker, args.destName, icon)
end
function mod:GlacialWrathRemoved(args)
if self:Me(args.destGUID) then
self:CancelSayCountdown(346459)
end
self:CustomIcon(glacialWrathMarker, args.destName)
glacialSpikeMarks[#glacialSpikeMarks+1] = playerList[args.destName] -- _REMOVED is more reliable for spike order
end
end
do
local playerName = mod:UnitName("player")
local stacks = 1
local scheduled = nil
local function FrozenDestructionStackMessage()
mod:NewStackMessage(346530, "blue", playerName, stacks)
mod:PlaySound(346530, stacks > 4 and "warning" or "info") -- How many stacks is too much?
scheduled = nil
end
function mod:FrozenDestructionApplied(args) -- Throttle incase several die at the same time
if self:Me(args.destGUID) then
stacks = args.amount or 1
if not scheduled then
scheduled = self:ScheduleTimer(FrozenDestructionStackMessage, 0.1)
end
end
end
end
do
local playerList = {}
function mod:OblivionsEcho(args)
playerList = {}
oblivionsEchoCount = oblivionsEchoCount + 1
if self:GetStage() == 3 then
self:CDBar(347292, oblivionsEchoCount % 2 == 0 and 17.1 or 23.3, CL.count:format(L.silence, oblivionsEchoCount))
else
self:CDBar(347292, 39.1, CL.count:format(L.silence, oblivionsEchoCount)) -- 38-44?
end
end
function mod:OblivionsEchoApplied(args)
local count = #playerList+1
playerList[count] = args.destName
if self:Me(args.destGUID) then
self:Say(args.spellId, L.silence)
self:SayCountdown(args.spellId, 6)
self:PlaySound(args.spellId, "warning")
end
self:NewTargetsMessage(args.spellId, "yellow", playerList, nil, CL.count:format(L.silence, oblivionsEchoCount-1))
end
end
function mod:OblivionsEchoRemoved(args)
if self:Me(args.destGUID) then
self:CancelSayCountdown(args.spellId)
end
end
function mod:FrostBlast(args)
frostBlastCount = frostBlastCount + 1
self:CDBar(348760, self:GetStage() == 3 and 40.2 or 42.5, CL.count:format(CL.meteor, frostBlastCount))
end
function mod:FrostBlastSuccess(args)
self:StopBar(CL.count:format(CL.meteor, frostBlastCount-1))
end
function mod:FrostBlastApplied(args)
if self:Me(args.destGUID) then
self:PlaySound(args.spellId, "warning")
self:Yell(args.spellId, CL.meteor)
self:Flash(args.spellId)
self:YellCountdown(args.spellId, 6)
else
self:PlaySound(args.spellId, "alert")
end
self:TargetMessage(args.spellId, "orange", args.destName, CL.count:format(CL.meteor, frostBlastCount-1))
end
function mod:PhylactryApplied(args)
if self:Me(args.destGUID) then
inPhylactry = true
end
end
function mod:PhylactryRemoved(args)
if self:Me(args.destGUID) then
inPhylactry = false
end
end
function mod:SinisterMiasmaApplied(args)
if self:Me(args.destGUID) then
local amount = args.amount or 1
if amount % 3 == 0 and amount > 15 then -- 15+ or every 3
self:NewStackMessage(args.spellId, "blue", args.destName, amount, 10, L.miasma)
if amount > 15 then
self:PlaySound(args.spellId, "alert")
end
end
end
end
function mod:VengefulDestruction(args)
self:Message(args.spellId, "yellow", CL.casting:format(self:SpellName(249436)))
self:PlaySound(args.spellId, "long")
self:CastBar(args.spellId, 45, self:SpellName(249436)) -- Destruction
-- UNIT_SPELLCAST_SUCCEEDED Events which are 2.5~s faster to do a stage change on:
-- ClearAllDebuffs-34098-npc:175559
-- Cosmetic Death-351625-npc:175559
-- Teleport to Floor-351418-npc:175559
self:StopBar(CL.count:format(self:SpellName(348071), soulFractureCount)) -- Soul Fracture
self:StopBar(CL.count:format(L.silence, oblivionsEchoCount)) -- Oblivion's Echo
self:StopBar(CL.count:format(L.spikes, glacialWrathCount)) -- Glacial Wrath
self:StopBar(CL.count:format(CL.meteor, frostBlastCount)) -- Frost Blast
self:StopBar(CL.count:format(self:SpellName(352530), darkEvocationCount)) -- Dark Evocation
self:StopBar(CL.count:format(self:SpellName(354198), blizzardCount)) -- Howling Blizzard
self:SetStage(2)
local remnant = self:GetBossId(176929) -- was only ever boss2, but just to make sure
if remnant and self:GetHealth(remnant) < 34 then -- final stage 2
self:CDBar(355055, 3) -- Glacial Winds
self:CDBar(352379, 11) -- Freezing Blast
-- if self:Mythic() then
-- self:CDBar(355127, 7) -- Foul Winds
-- end
else
-- XXX probably varies based on the first person entering?
self:CDBar(352379, self:Mythic() and 3 or 7) -- Freezing Blast
if self:Mythic() then
self:CDBar(355127, 7) -- Foul Winds
end
end
end
function mod:NecroticSurgeApplied(args)
if not self:IsEngaged() then return end
self:NewStackMessage(args.spellId, "cyan", args.destName, args.amount)
self:PlaySound(args.spellId, "info")
self:StopBar(CL.cast:format(self:SpellName(249436))) -- Destruction
self:StopBar(352379) -- Freezing Blast
self:StopBar(355055) -- Glacial Winds
if self:GetStage() == 2 then
self:SetStage(1)
soulFractureCount = 1
oblivionsEchoCount = 1
glacialWrathCount = 1
frostBlastCount = 1
-- Standard time if mana is 100
local evocationTime = 45.4
local blizzardTime = 86.2
local currentMana = UnitPower("boss1") or 0
if currentMana == 80 then
evocationTime = 46.1
blizzardTime = 86.4
elseif currentMana == 60 then
evocationTime = 11.5
blizzardTime = 46.5
elseif currentMana == 40 then
-- XXX under 5% or so he will only cast ice shards and if held for a period of time (evo cd elapsed?),
-- he will follow the 40 energy timings after the remnant regardless of actual energy
evocationTime = 3.2 -- XXX Check
blizzardTime = 21.5
elseif currentMana == 20 then
evocationTime = 46.5
blizzardTime = 15.5
end
self:CDBar(348071, self:Mythic() and 5.6 or 9.5, CL.count:format(self:SpellName(348071), soulFractureCount)) -- Soul Fracture
self:CDBar(347292, 11, CL.count:format(L.silence, oblivionsEchoCount)) -- Oblivion's Echo
if currentMana > 20 then
self:CDBar(346459, 19, CL.count:format(L.spikes, glacialWrathCount)) -- Glacial Wrath
if currentMana > 40 then
self:CDBar(348760, 44, CL.count:format(CL.meteor, frostBlastCount)) -- Frost Blast
end
end
self:CDBar(352530, evocationTime, CL.count:format(self:SpellName(352530), darkEvocationCount)) -- Dark Evocation
self:CDBar(354198, blizzardTime, CL.count:format(self:SpellName(354198), blizzardCount)) -- Howling Blizzard
else -- Stage 3
oblivionsEchoCount = 1
frostBlastCount = 1
-- oblivion > oblivion > frost blast > onslaught > repeat
-- will cast ice shard until he gets to a spell in the rotation that he has enough energy for
local currentMana = UnitPower("boss1")
if currentMana > 20 then -- costs 40 energy now
self:CDBar(347292, 5.8, CL.count:format(L.silence, oblivionsEchoCount)) -- Oblivion's Echo
end
if currentMana == 20 or currentMana > 40 then
self:CDBar(348760, 29.8, CL.count:format(CL.meteor, frostBlastCount)) -- Frost Blast
end
self:CDBar(352348, 34) -- Onslaught of the Damned
end
end
function mod:RemnantDeath()
self:Message("stages", "green", CL.stage:format(3), false)
self:PlaySound("stages", "info")
self:SetStage(3)
end
function mod:FreezingBlast(args)
self:Message(args.spellId, "orange")
self:CDBar(args.spellId, self:Mythic() and 12.1 or 4.9)
if inPhylactry then
self:PlaySound(args.spellId, "alarm")
end
end
function mod:GlacialWinds(args)
self:Message(args.spellId, "cyan")
self:CDBar(args.spellId, 13.5)
if inPhylactry then
self:PlaySound(args.spellId, "info")
end
end
function mod:FoulWinds(args)
self:Message(args.spellId, "yellow")
self:CDBar(args.spellId, 12.2)
if inPhylactry then
self:PlaySound(args.spellId, "alert")
end
end
function mod:UndyingWrath(args)
self:Message(args.spellId, "red")
self:CastBar(args.spellId, 10)
if inPhylactry then
self:PlaySound(args.spellId, "warning")
end
self:StopBar(352379) -- Freezing Blast
self:StopBar(355055) -- Glacial Winds
self:StopBar(355127) -- Foul Winds
end
function mod:OnslaughtOfTheDamned(args)
self:Message(args.spellId, "yellow")
self:PlaySound(args.spellId, "alert")
self:CDBar(args.spellId, 40.2)
end
do
local prev = 0
function mod:GroundDamage(args)
if self:Me(args.destGUID) and not mindControlled then
local t = args.time
local throttle = args.spellId == 354639 and 6 or 2 -- Deep Freeze
if t-prev > throttle then
prev = t
self:PlaySound(args.spellId, "underyou")
self:PersonalMessage(args.spellId, "underyou")
end
end
end
end
function mod:ReturnOfTheDamned(args)
if self:Me(args.destGUID) then
mindControlled = true
end
end
function mod:ReturnOfTheDamnedRemoved(args)
if self:Me(args.destGUID) then
mindControlled = false
end
end
function mod:BansheesCry(args)
local canDo, ready = self:Interrupter(args.sourceGUID)
if canDo and (self:GetStage() == 3 or not inPhylactry) then
self:Message(args.spellId, "yellow")
if ready then
self:PlaySound(args.spellId, "alarm")
end
end
end
do
function mod:SoulReaverMarker(event, unit, guid)
if mobCollector[guid] then
self:CustomIcon(soulReaverMarker, unit, mobCollector[guid])
mobCollector[guid] = nil
if not next(mobCollector) then
self:UnregisterTargetEvents()
end
end
end
local prev = 0
local count = 8
function mod:MarchOfTheForsakenSummon(args)
if not self:GetOption(soulReaverMarker) then return end
local t = args.time
if t-prev > 5 then
prev = t
mobCollector = {}
count = 8
self:RegisterTargetEvents("SoulReaverMarker")
self:ScheduleTimer("UnregisterTargetEvents", 10)
end
mobCollector[args.destGUID] = count
count = count - 1
end
end