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.

572 lines
19 KiB

-- GLOBALS: math
--------------------------------------------------------------------------------
-- TODO List:
-- - Keep updating the timers if any new timers shorter than current are found
-- - Check Desolate timers in P1 again - might get offset later in the fight
-- - Maybe mark Shadowy Blades? 3 in heroic, 7 in mythic. Maybe too much for mythic?
--------------------------------------------------------------------------------
-- Module Declaration
--
local mod, CL = BigWigs:NewBoss("Fallen Avatar", 1676, 1873)
if not mod then return end
mod:RegisterEnableMob(116939, 117264) -- Fallen Avatar, Maiden of Valor
mod.engageId = 2038
mod.respawnTime = 25
--------------------------------------------------------------------------------
-- Locals
--
local timersHeroic = {
[234057] = {7, 42, 35, 41, 36, 35, 43}, -- Unbound Chaos
[239207] = {15, 42.5, 55, 43, 42.5, 42.5}, -- Touch of Sargeras
[236573] = {30, 42.5, 36.5, 30, 30, 30, 33}, -- Shadowy Blades
[239132] = {37, 60.8, 60, 62}, -- Rupture Realities
}
local timersMythic = {
[234057] = {7, 43.8, 41.4, 35.7, 36.1, 35.3, 35.1}, -- Unbound Chaos
[239207] = {15.5, 63.5, 63, 63}, -- Touch of Sargeras
[236573] = {28.5, 35.3, 48.5, 42.5, 48.3, 40.1}, -- Shadowy Blades
[239132] = {34.5, 64.5, 63, 63}, -- Rupture Realities
}
local stage = 1
local corruptedMatrixCounter = 1
local ruptureRealitiesCounter = 1
local unboundChaosCounter = 1
local shadowyBladesCounter = 1
local touchofSargerasCounter = 1
local desolateCounter = 1
local darkMarkCounter = 1
local taintedMatrixCounter = 1
local energyLeakCheck = nil
local timers = mod:Mythic() and timersMythic or timersHeroic
--------------------------------------------------------------------------------
-- Localization
--
local L = mod:GetLocale()
if L then
L.touch_impact = "Touch Impact" -- Touch of Sargeras Impact (short)
L.custom_on_stop_timers = "Always show ability bars"
L.custom_on_stop_timers_desc = "Fallen Avatar randomizes which off-cooldown ability he uses next. When this option is enabled, the bars for those abilities will stay on your screen."
L.energy_leak = "Energy Leak"
L.energy_leak_desc = "Display a warning when energy has leaked onto the boss in stage 1."
L.energy_leak_msg = "Energy Leak! (%d)"
L.warmup_trigger = "The husk before you" -- The husk before you was once a vessel for the might of Sargeras. But this temple itself is our prize. The means by which we will reduce your world to cinders!
L.absorb = "Absorb"
L.absorb_text = "%s (|cff%s%.0f%%|r)"
L.cast = "Cast"
L.cast_text = "%.1fs (|cff%s%.0f%%|r)"
end
--------------------------------------------------------------------------------
-- Initialization
--
local darkMarkIcons = mod:AddMarkerOption(false, "player", 6, 239739, 6, 4, 3)
function mod:GetOptions()
return {
"warmup",
"stages",
"berserk",
"energy_leak",
"custom_on_stop_timers",
239207, -- Touch of Sargeras
239132, -- Rupture Realities
234059, -- Unbound Chaos
{236604, "SAY", "FLASH"}, -- Shadowy Blades
239212, -- Lingering Darkness
{236494, "TANK"}, -- Desolate
236528, -- Ripple of Darkness
{233856, "INFOBOX"}, -- Cleansing Protocol
233556, -- Corrupted Matrix
{239739, "FLASH", "SAY", "SAY_COUNTDOWN", "INFOBOX"}, -- Dark Mark
darkMarkIcons,
235572, -- Rupture Realities
242017, -- Black Winds
236684, -- Fel Infusion
240623, -- Tainted Matrix
240728, -- Tainted Essence
234418, -- Rain of the Destroyer
},{
["warmup"] = "general",
[239058] = -14709, -- Stage One: A Slumber Disturbed
[233856] = -14713, -- Maiden of Valor
[233556] = -15565, -- Containment Pylon
[239739] = -14719, -- Stage Two: An Avatar Awakened
[240623] = "mythic",
}
end
function mod:OnBossEnable()
self:RegisterEvent("CHAT_MSG_MONSTER_YELL")
self:RegisterUnitEvent("UNIT_SPELLCAST_SUCCEEDED", nil, "boss1", "boss2")
self:RegisterEvent("RAID_BOSS_WHISPER")
self:RegisterEvent("CHAT_MSG_RAID_BOSS_EMOTE")
self:Log("SPELL_AURA_APPLIED", "GroundEffectDamage", 239212) -- Lingering Darkness
self:Log("SPELL_PERIODIC_DAMAGE", "GroundEffectDamage", 239212)
self:Log("SPELL_PERIODIC_MISSED", "GroundEffectDamage", 239212)
-- Stage One: A Slumber Disturbed
self:Log("SPELL_CAST_START", "TouchofSargeras", 239207)
self:Log("SPELL_CAST_START", "RuptureRealities", 239132)
self:Log("SPELL_AURA_APPLIED", "UnboundChaos", 234059)
self:Log("SPELL_CAST_START", "Desolate", 236494)
self:Log("SPELL_AURA_APPLIED", "DesolateApplied", 236494)
self:Log("SPELL_AURA_APPLIED_DOSE", "DesolateApplied", 236494)
self:Log("SPELL_CAST_SUCCESS", "Consume", 240594)
self:Log("SPELL_CAST_SUCCESS", "RippleofDarkness", 236528)
-- Maiden of Valor
self:Log("SPELL_AURA_APPLIED", "CleansingProtocol", 241008)
self:Log("SPELL_AURA_REMOVED", "CleansingProtocolRemoved", 241008)
self:Log("SPELL_AURA_APPLIED", "Malfunction", 233739)
self:Death("MaidenDeath", 117264)
-- Containment Pylon
self:Log("SPELL_CAST_START", "CorruptedMatrix", 233556)
-- Stage Two: An Avatar Awakened
self:Log("SPELL_CAST_SUCCESS", "Annihilation", 235597) -- Stage 2 cast
self:Log("SPELL_AURA_APPLIED", "DarkMark", 239739)
self:Log("SPELL_AURA_REMOVED", "DarkMarkRemoved", 239739)
self:Log("SPELL_CAST_START", "RuptureRealitiesP2", 235572)
self:Log("SPELL_AURA_APPLIED", "FelInfusion", 236684)
self:Log("SPELL_AURA_APPLIED_DOSE", "FelInfusion", 236684)
-- Mythic
self:Log("SPELL_CAST_START", "TaintedMatrix", 240623)
self:Log("SPELL_AURA_APPLIED", "TaintedEssence", 240728)
self:Log("SPELL_AURA_APPLIED_DOSE", "TaintedEssence", 240728)
self:RegisterMessage("BigWigs_BarCreated", "BarCreated")
end
function mod:OnEngage()
timers = self:Mythic() and timersMythic or timersHeroic
stage = 1
corruptedMatrixCounter = 1
desolateCounter = 1
unboundChaosCounter = 1
touchofSargerasCounter = 1
shadowyBladesCounter = 1
ruptureRealitiesCounter = 1
darkMarkCounter = 1
taintedMatrixCounter = 1
self:Bar(236494, 12) -- Desolate
self:Bar(234059, timers[234057][unboundChaosCounter]) -- Unbound Chaos
if not self:Easy() then
self:Bar(239207, timers[239207][touchofSargerasCounter], CL.count:format(self:SpellName(239207), touchofSargerasCounter)) -- Touch of Sargeras
end
if self:Mythic() then
self:CDBar(233856, 73) -- Maiden Shield (if no fail)
end
self:Bar(236604, timers[236573][shadowyBladesCounter]) -- Shadowy Blades
self:Bar(239132, timers[239132][ruptureRealitiesCounter]) -- Rupture Realities (P1)
if self:LFR() then
self:Bar("stages", 186.5, CL.stage:format(2), 235597) -- Annihilation icon
self:Berserk(434.5)
else
self:InitCheckUnitPower()
energyLeakCheck = self:ScheduleRepeatingTimer("CheckUnitPower", 1)
end
self:RegisterUnitEvent("UNIT_POWER_FREQUENT", nil, "boss2")
end
--------------------------------------------------------------------------------
-- Event Handlers
--
function mod:CHAT_MSG_MONSTER_YELL(event, msg)
if msg:find(L.warmup_trigger, nil, true) then
self:UnregisterEvent(event)
self:Bar("warmup", 42, CL.active, "achievement_boss_titanconstructshell")
end
end
do
local tbl, checks, prev, last = {}, 0, 0, nil
function mod:InitCheckUnitPower()
tbl = {}
checks = 0
last = nil
end
function mod:CheckUnitPower()
local power = UnitPower("boss1")
tbl[checks%5 + 1] = math.max(power-(last or 0), 0)
local sum = 0
for _,v in pairs(tbl) do
sum = sum + v
end
if sum >= 5 then -- Check power gained
local t = GetTime()
if last and power > last and t-prev > 2 then -- Skip first message, only if power is bigger than before
self:MessageOld("energy_leak", "yellow", "info", L.energy_leak_msg:format(sum), false)
self:InitCheckUnitPower()
prev = t
end
end
last = power
checks = checks + 1
end
end
do
local abilitysToPause = {
[234059] = true, -- Unbound Chaos
[239207] = true, -- Touch of Sargeras
[236604] = true, -- Shadowy Blades
[239132] = true, -- Rupture Realities (P1)
}
local castPattern = CL.cast:gsub("%%s", ".+")
local function stopAtZeroSec(bar)
if bar.remaining < 0.15 then -- Pause at 0.0
bar:SetDuration(0.01) -- Make the bar look full
bar:Start()
bar:Pause()
bar:SetTimeVisibility(false)
end
end
function mod:BarCreated(_, _, bar, _, key, text)
if self:GetOption("custom_on_stop_timers") and abilitysToPause[key] and not text:match(castPattern) and text ~= L.touch_impact then
bar:AddUpdateFunction(stopAtZeroSec)
end
end
end
do
local bladeTimer = nil
function mod:UNIT_SPELLCAST_SUCCEEDED(_, _, _, spellId)
if spellId == 234057 then -- Unbound Chaos
if self:Tank() then
self:MessageOld(234059, "yellow", "alert")
end
unboundChaosCounter = unboundChaosCounter + 1
self:Bar(234059, timers[spellId][unboundChaosCounter] or 35)
elseif spellId == 236573 then -- Shadowy Blades
bladeTimer = self:ScheduleTimer("MessageOld", 0.3, 236604, "yellow", "alert")
shadowyBladesCounter = shadowyBladesCounter + 1
self:CDBar(236604, timers[spellId][shadowyBladesCounter] or 30)
self:CastBar(236604, 5)
end
end
function mod:RAID_BOSS_WHISPER(_, msg)
if msg:find("236604", nil, true) then -- Shadowy Blades
if bladeTimer then
self:CancelTimer(bladeTimer)
bladeTimer = nil
end
self:MessageOld(236604, "blue", "alarm", CL.you:format(self:SpellName(236604)))
self:Flash(236604)
self:Say(236604)
end
end
end
function mod:CHAT_MSG_RAID_BOSS_EMOTE(_, msg)
if msg:find("234418") then -- Rain of the Destroyer
self:MessageOld(234418, "red", "alarm")
self:Bar(234418, 35)
self:CDBar(234418, 6, self:SpellName(182580)) -- Meteor Impact (estimated)
end
end
function mod:UNIT_POWER_FREQUENT(event, unit)
local power = UnitPower(unit)
if power >= 85 then
self:MessageOld(233856, "yellow", self:Damager() and "info", CL.soon:format(self:SpellName(233856))) -- Cleansing Protocol
self:UnregisterUnitEvent(event, unit)
end
end
do
local prev = 0
function mod:GroundEffectDamage(args)
local t = GetTime()
if self:Me(args.destGUID) and t-prev > 1.5 then
prev = t
self:MessageOld(args.spellId, "blue", "alert", CL.underyou:format(args.spellName))
end
end
end
function mod:TouchofSargeras(args)
self:StopBar(CL.count:format(args.spellName, touchofSargerasCounter))
self:MessageOld(args.spellId, "yellow", "alert", CL.incoming:format(CL.count:format(args.spellName, touchofSargerasCounter)))
self:Bar(args.spellId, 10.5, L.touch_impact)
touchofSargerasCounter = touchofSargerasCounter + 1
self:Bar(args.spellId, timers[args.spellId][touchofSargerasCounter] or 42, CL.count:format(args.spellName, touchofSargerasCounter))
end
function mod:RuptureRealities(args)
self:MessageOld(args.spellId, "orange", "warning", CL.casting:format(args.spellName))
ruptureRealitiesCounter = ruptureRealitiesCounter + 1
self:Bar(args.spellId, timers[args.spellId][ruptureRealitiesCounter] or 60)
self:CastBar(args.spellId, 7.5)
end
function mod:UnboundChaos(args)
if self:Me(args.destGUID) then
self:TargetMessageOld(args.spellId, args.destName, "orange", "alarm")
end
end
function mod:Desolate(args)
self:MessageOld(args.spellId, "yellow", "alert", CL.casting:format(args.spellName))
desolateCounter = desolateCounter + 1
self:CDBar(args.spellId, stage == 2 and (desolateCounter % 2 == 0 and 12 or 23) or (desolateCounter % 4 == 3 and 24.3 or 11.5))
end
function mod:DesolateApplied(args)
local amount = args.amount or 1
self:StackMessage(args.spellId, args.destName, amount, "orange", "warning", nil, nil, amount > 1 and true)
end
function mod:Consume(args)
self:MessageOld("stages", "cyan", "info", args.spellName, args.spellId)
self:StopBar(CL.cast:format(self:SpellName(233856))) -- Malfunction
end
function mod:RippleofDarkness(args)
self:MessageOld(args.spellId, "orange", "warning")
end
do
local timer, castOver, maxAbsorb = nil, 0, 0
local red, yellow, green = {.6, 0, 0, .6}, {.7, .5, 0}, {0, .5, 0}
local function updateInfoBox(self)
local castTimeLeft = castOver - GetTime()
local castPercentage = castTimeLeft / 18
local absorb = UnitGetTotalAbsorbs("boss2")
local absorbPercentage = absorb / maxAbsorb
local diff = castPercentage - absorbPercentage
local hexColor = "ff0000"
local rgbColor = red
if diff > 0.1 then -- over 10%
hexColor = "00ff00"
rgbColor = green
elseif diff > 0 then -- below 10%, so it's still close
hexColor = "ffff00"
rgbColor = yellow
end
self:SetInfoBar(233856, 1, absorbPercentage, unpack(rgbColor))
self:SetInfo(233856, 2, L.absorb_text:format(self:AbbreviateNumber(absorb), hexColor, absorbPercentage*100))
self:SetInfoBar(233856, 3, castPercentage)
self:SetInfo(233856, 4, L.cast_text:format(castTimeLeft, hexColor, castPercentage*100))
end
function mod:CleansingProtocol(args)
self:MessageOld(233856, "orange", "alarm", CL.casting:format(args.spellName))
if self:Mythic() then
self:CDBar(233856, 80) -- Maiden Shield (if no fail)
end
self:CastBar(233856, 18)
if self:CheckOption(233856, "INFOBOX") then
self:OpenInfo(233856, args.spellName)
self:SetInfo(233856, 1, L.absorb)
self:SetInfo(233856, 3, L.cast)
castOver = GetTime() + 18
maxAbsorb = UnitGetTotalAbsorbs("boss2")
timer = self:ScheduleRepeatingTimer(updateInfoBox, 0.1, self)
end
end
function mod:CleansingProtocolRemoved(args)
self:CloseInfo(233856)
if timer then
self:CancelTimer(timer)
timer = nil
end
end
end
function mod:Malfunction()
self:MessageOld(233856, "green", "info", CL.removed:format(self:SpellName(233856))) -- Cleansing Protocol
self:StopBar(CL.cast:format(self:SpellName(233856))) -- Cleansing Protocol
self:RegisterUnitEvent("UNIT_POWER_FREQUENT", nil, "boss2")
end
function mod:MaidenDeath()
if energyLeakCheck then
self:CancelTimer(energyLeakCheck)
energyLeakCheck = nil
end
self:UnregisterUnitEvent("UNIT_POWER_FREQUENT", "boss2")
end
function mod:CorruptedMatrix(args)
self:MessageOld(args.spellId, "red", "warning", CL.incoming:format(args.spellName))
corruptedMatrixCounter = corruptedMatrixCounter + 1
self:CDBar(args.spellId, self:Mythic() and 20 or 50)
self:CastBar(args.spellId, self:Mythic() and 8 or 10)
end
function mod:Annihilation() -- Stage 2
stage = 2
self:MessageOld("stages", "green", "long", CL.stage:format(stage), false)
self:StopBar(233556) -- Corrupted Matrix
self:StopBar(CL.cast:format(self:SpellName(233556))) -- Corrupted Matrix (cast)
self:StopBar(236494) -- Desolate
self:StopBar(234059) -- Unbound Chaos
self:StopBar(CL.count:format(self:SpellName(239207), touchofSargerasCounter)) -- Touch of Sargeras
self:StopBar(CL.cast:format(CL.count:format(self:SpellName(239207), touchofSargerasCounter))) -- Touch of Sargeras (cast)
self:StopBar(236604) -- Shadowy Blades
self:StopBar(239132) -- Rupture Realities (P1)
self:StopBar(240623) -- Tainted Matrix
self:StopBar(233856) -- Maiden Shield
self:StopBar(CL.cast:format(self:SpellName(240623))) -- Tainted Matrix (cast)
ruptureRealitiesCounter = 1
desolateCounter = 1
darkMarkCounter = 1
if energyLeakCheck then
self:CancelTimer(energyLeakCheck)
energyLeakCheck = nil
end
self:UnregisterUnitEvent("UNIT_POWER_FREQUENT", "boss2")
self:CDBar(236494, 20) -- Desolate
self:CDBar(239739, self:Mythic() and 31.1 or 21.5) -- Dark Mark
if self:Mythic() then
self:Bar(234418, 14.3) -- Rain of the Destroyer
end
self:CDBar(235572, self:Mythic() and 38.4 or 38, CL.count:format(self:SpellName(235572), ruptureRealitiesCounter)) -- Rupture Realities (P2)
end
do
local list, infoBoxList, timer = mod:NewTargetList(), {}, nil
local function updateInfoBox(self)
for _,debuff in next, infoBoxList do
self:SetInfo(239739, debuff.pos-1, ("|T%d:0|t %.1f"):format(debuff.icon, debuff.expires-GetTime()))
end
end
function mod:DarkMark(args)
local count = #list+1
list[count] = args.destName
local icon = count == 1 and 6 or count == 2 and 4 or 3 -- (Blue -> Green -> Purple) in order of exploding/application
local _, _, _, expires = self:UnitDebuff(args.destName, args.spellId) -- random duration
if self:Me(args.destGUID) then
local remaining = expires-GetTime()
self:Flash(args.spellId)
if self:LFR() then
self:Say(args.spellId)
self:SayCountdown(args.spellId, remaining)
else
self:Say(args.spellId, CL.count_rticon:format(args.spellName, count, icon)) -- Announce which mark you have
self:SayCountdown(args.spellId, remaining, icon)
end
end
if count == 1 then
self:ScheduleTimer("TargetMessageOld", 0.3, args.spellId, list, "yellow", "alarm")
darkMarkCounter = darkMarkCounter + 1
self:Bar(args.spellId, self:Mythic() and (darkMarkCounter == 2 and 25.5 or 30.5) or 34, CL.count:format(args.spellName, darkMarkCounter))
self:OpenInfo(args.spellId, args.spellName)
self:SetInfo(args.spellId, 1, "|T137006:0|t 6")
if not self:LFR() then
self:SetInfo(args.spellId, 3, "|T137004:0|t 8")
end
if not self:Easy() then
self:SetInfo(args.spellId, 5, "|T137003:0|t 10")
end
infoBoxList = {}
timer = self:ScheduleRepeatingTimer(updateInfoBox, 0.1, self)
end
infoBoxList[args.destGUID] = {pos = count*2, expires = expires, icon = 137000+icon}
self:SetInfo(args.spellId, count*2, self:ColorName(args.destName))
if self:GetOption(darkMarkIcons) then
self:CustomIcon(false, args.destName, icon)
end
end
function mod:DarkMarkRemoved(args)
if self:Me(args.destGUID) then
self:CancelSayCountdown(args.spellId)
end
if self:GetOption(darkMarkIcons) then
self:CustomIcon(false, args.destName)
end
if infoBoxList[args.destGUID] then
self:SetInfo(args.spellId, infoBoxList[args.destGUID].pos, "")
self:SetInfo(args.spellId, infoBoxList[args.destGUID].pos-1, "")
infoBoxList[args.destGUID] = nil
end
if not next(infoBoxList) then
self:CloseInfo(args.spellId)
if timer then
self:CancelTimer(timer)
timer = nil
end
end
end
end
function mod:RuptureRealitiesP2(args)
self:MessageOld(args.spellId, "orange", "warning", CL.casting:format(CL.count:format(args.spellName, ruptureRealitiesCounter)))
self:CastBar(args.spellId, 7.5, CL.count:format(args.spellName, ruptureRealitiesCounter))
ruptureRealitiesCounter = ruptureRealitiesCounter + 1
self:Bar(args.spellId, 37.7, CL.count:format(args.spellName, ruptureRealitiesCounter))
end
function mod:FelInfusion(args)
local amount = args.amount or 1
if amount % 2 == 0 then
self:MessageOld(args.spellId, "yellow", "info", CL.count:format(args.spellName, amount), nil, false)
end
end
do
local prev = 0
function mod:TaintedMatrix(args)
local t = GetTime()
if t-prev > 1.5 then
prev = t
taintedMatrixCounter = taintedMatrixCounter + 1
self:MessageOld(args.spellId, "red", "warning", CL.incoming:format(args.spellName))
self:CDBar(args.spellId, 60, CL.count:format(args.spellName, taintedMatrixCounter))
self:CastBar(args.spellId, 8)
end
end
end
function mod:TaintedEssence(args)
local amount = args.amount or 1
if self:Me(args.destGUID) and amount > 4 then
self:StackMessage(args.spellId, args.destName, amount, "orange", "warning")
end
end