InterruptAwareness
This addon works as a plugin for Deadly Boss Mods or it can also work as a standalone using the stopwatch.
What it does, is it keeps track of Interrupts done to and by friendlies and displays them as a bar which degrades over time displaying the duration of the Spell-Tree Lockout and prints a detailed report of the interrupt in Party chat.
CUSTOMIZATION:
The bar can be moved and changed in size in a variety of ways in the Deadly Boss Mods Bar Setup Options window. Simply click "Move me" to bring up the test bars so you can position them or tweak the size as you prefer.
local SPELL_LOCKOUTS =
{
-- [interruptSpellID] = lockoutDuration, -- Spell Name (Class)
[2139] = 6, -- Counterspell (Mage)
[115781] = 6, -- Optical Blast (Warlock)
[19647] = 6, -- Spell Lock (Warlock)
[173320] = 5, -- Spear Hand Strike (Monk)
[1766] = 5, -- Kick (Rogue)
[78675] = 5, -- Solar Beam (Druid)
[97547] = 5, -- Solar Beam Int (Druid)
[96231] = 4, -- Rebuke (Paladin)
[6552] = 4, -- Pummel (Warrior)
[47528] = 4, -- Mind Freeze (Death Knight)
[106839] = 4, -- Skull Bash (Druid)
[147362] = 3, -- Counter Shot (Hunter)
[187707] = 3, -- Muzzle (Hunter)
[57994] = 3, -- Wind Shear (Shaman)
[183752] = 3, -- Consume Magic (Demon Hunter)
}
-- If this is "DBM", DBM's timers will be used. If this is "SW", Blizzard's stopwatch will be used.
local MODE = "DBM"
-- The text to display on DBM Timers.
-- %SPELL% will be replaced with the name of the interrupt spell you cast.
-- %ISPELL% will be replaced with the name of the spell you interrupted
-- %DUR% will be replaced with the duration of the lockout.
local BARTEXT = "%SPELL% interrupted %ISPELL%. Locked out for %DUR% seconds"
-------------------
-- END OF CONFIG --
-------------------
local ARENA1_GUID;
local ARENA2_GUID;
local ARENA3_GUID;
local TARGET_GUID;
local PLAYER_GUID;
local ShowTimer; -- Declare the ShowTimer variable as local
if MODE == "DBM" and DBM then -- If MODE is "DBM" and there's a global variable DBM
local DBM = DBM -- Create a local alias to DBM
local gsubTable = {} -- This table is used to handle text substitutions with gsub
ShowTimer = function(seconds, interruptSpell, interruptedSpell) -- Define the ShowTimer function
gsubTable.SPELL = interruptSpell -- Populate the fields of the table
gsubTable.ISPELL = interruptedSpell
gsubTable.DUR = seconds
local text = BARTEXT:gsub("%%(%a+)%%", gsubTable) -- Replace each occurrence of %SPELL%, %ISPELL% or %DUR% with the corresponding field of gsubTable and assing the result to the text variable
DBM:CreatePizzaTimer(seconds, text) -- Create the timer
end
else -- Either MODE isn't "DBM", or there's no global variable DBM
ShowTimer = function(seconds)
Stopwatch_StartCountdown(0, 0, seconds)
Stopwatch_Play()
end
end
local spell_school_enum =
{
[1] = "Physical",
[2] = "Holy",
[4] = "Fire",
[8] = "Nature",
[12] = "Frost",
[32] = "Shadow",
[64] = "Arcane",
[3] = "HolyStrike",
[5] = "FlameStrike",
[6] = "HolyFire",
[9] = "StormStrike",
[124] = "Chaos", -- Chromatic
[127] = "Chaos",
}
function has_value (tab, val)
for index, value in ipairs (tab) do
if value == val then
return true
end
end
return false
end
function set_new_val(tab, pkey, pval)
for key, value in pairs (tab) do
if value == pval then
--print("Removed Key: ",key," | Value: ",value)
tab[key] = nil
end
end
if pkey == 0 then
table.insert(tab,pval)
else
tab[pkey] = pval
end
--print("Added Key: ",pkey," | Value: ",pval)
end
function rem_old_val(tab, pval)
for key, value in pairs (tab) do
if value == pval then
--print("Removed Key: ",key," | Value: ",value)
tab[key] = nil
end
end
end
function get_key(tab, pval)
for key, value in pairs (tab) do
if value == pval then
return key
end
end
return nil
end
function round(num, numDecimalPlaces)
return tonumber(string.format("%." .. (numDecimalPlaces or 0) .. "f", num))
end
local LastCast = nil
local LastCastID = nil
local CastArray = { }
local logframe = CreateFrame("Frame")
logframe:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
logframe:SetScript("OnEvent", function(self, event, ...)
local ticktime,event_name,_,_,sourceName,_,_,_,destName,_,_,LastCastID,spellName,spellSchool = ...
if event_name == "SPELL_CAST_START" then
set_new_val(CastArray,ticktime,sourceName)
end
if event_name == "SPELL_INTERRUPT" then
local IntSpellID,IntSpellName,IntSchool = select(15,...)
local name,_,_,CastTime,_,_ = GetSpellInfo(IntSpellID)
local CastStartTime = get_key(CastArray,destName)
local isMe = UnitName("player") == destName
local inParty = UnitInParty("player",destName)
local CanAttack = nil
if inParty then
if UnitCanAttack("player",destName) then
CanAttack = true
else
CanAttack = false
end
end
if CastStartTime ~= nil then
local CastedTime = (ticktime - CastStartTime)*1000
local CastedPercent = (CastedTime/CastTime)*100
CastedPercent = round(CastedPercent,1)
local msg = nil
if isMe then
msg = string.format("Ally: [%s\\%s] interrupted by %s at %d%% \\ School Lockout: %s",destName,spellName,sourceName,CastedPercent,spell_school_enum[IntSchool])
elseif inParty then
if CanAttack == true then
msg = string.format("Enemy: [%s\\%s] interrupted by %s at %d%% \\ School Lockout: %s",destName,spellName,sourceName,CastedPercent,spell_school_enum[IntSchool])
else
msg = string.format("Ally: [%s\\%s] interrupted by %s at %d%% \\ School Lockout: %s",destName,spellName,sourceName,CastedPercent,spell_school_enum[IntSchool])
end
else
msg = string.format("Enemy: [%s\\%s] interrupted by %s at %d%% \\ School Lockout: %s",destName,spellName,sourceName,CastedPercent,spell_school_enum[IntSchool])
end
SendChatMessage(msg,"PARTY")
rem_old_val(CastArray,destName)
else
--print("Error!")
end
local lockout = SPELL_LOCKOUTS[LastCastID] -- Check if we have a lockout time for this interrupt spell
if lockout then -- If we do, show a timer.
ShowTimer(lockout, IntSpellName)
end
end
end)
local zoneframe = CreateFrame("Frame")
zoneframe:RegisterEvent("ZONE_CHANGED_NEW_AREA")
zoneframe:RegisterEvent("ZONE_CHANGED_INDOORS")
zoneframe:RegisterEvent("ZONE_CHANGED")
zoneframe:RegisterEvent("PLAYER_ENTERING_WORLD")
zoneframe:RegisterEvent("PLAYER_ENTERING_BATTLEGROUND")
zoneframe:SetScript("OnEvent", function(self, event, ...)
local ZoneName = GetZoneText()
local SubZoneName = GetSubZoneText()
local MapID = GetCurrentMapAreaID()
local InBattleground = false
local InArena = false
local i = 1
while i <= GetMaxBattlefieldID() do
local status,_,_,_,_,teamSize = GetBattlefieldStatus(i)
if status == "active" and teamSize == 0 then
InBattleground = true
break
elseif status == "active" then
InArena = true
break
end
i = i + 1
end
--if InBattleground == true then
-- print(string.format("Zone: [%s\\%s\\%d] In a Battleground!",ZoneName,SubZoneName,MapID))
--elseif InArena == true then
-- print(string.format("Zone: [%s\\%s\\%d] In a Arena!",ZoneName,SubZoneName,MapID))
--else
-- print(string.format("Zone: [%s\\%s\\%d]",ZoneName,SubZoneName,MapID))
--end
end)
local SPELL_LOCKOUTS =
{
-- [interruptSpellID] = lockoutDuration, -- Spell Name (Class)
[2139] = 6, -- Counterspell (Mage)
[115781] = 6, -- Optical Blast (Warlock)
[19647] = 6, -- Spell Lock (Warlock)
[173320] = 5, -- Spear Hand Strike (Monk)
[1766] = 5, -- Kick (Rogue)
[78675] = 5, -- Solar Beam (Druid)
[97547] = 5, -- Solar Beam Int (Druid)
[96231] = 4, -- Rebuke (Paladin)
[6552] = 4, -- Pummel (Warrior)
[47528] = 4, -- Mind Freeze (Death Knight)
[106839] = 4, -- Skull Bash (Druid)
[147362] = 3, -- Counter Shot (Hunter)
[187707] = 3, -- Muzzle (Hunter)
[57994] = 3, -- Wind Shear (Shaman)
[183752] = 3, -- Consume Magic (Demon Hunter)
}
-- If this is "DBM", DBM's timers will be used. If this is "SW", Blizzard's stopwatch will be used.
local MODE = "DBM"
-- The text to display on DBM Timers.
-- %SPELL% will be replaced with the name of the interrupt spell you cast.
-- %ISPELL% will be replaced with the name of the spell you interrupted
-- %DUR% will be replaced with the duration of the lockout.
local BARTEXT = "%SPELL% interrupted %ISPELL%. Locked out for %DUR% seconds"
-------------------
-- END OF CONFIG --
-------------------
local ARENA1_GUID;
local ARENA2_GUID;
local ARENA3_GUID;
local TARGET_GUID;
local PLAYER_GUID;
local ShowTimer; -- Declare the ShowTimer variable as local
if MODE == "DBM" and DBM then -- If MODE is "DBM" and there's a global variable DBM
local DBM = DBM -- Create a local alias to DBM
local gsubTable = {} -- This table is used to handle text substitutions with gsub
ShowTimer = function(seconds, interruptSpell, interruptedSpell) -- Define the ShowTimer function
gsubTable.SPELL = interruptSpell -- Populate the fields of the table
gsubTable.ISPELL = interruptedSpell
gsubTable.DUR = seconds
local text = BARTEXT:gsub("%%(%a+)%%", gsubTable) -- Replace each occurrence of %SPELL%, %ISPELL% or %DUR% with the corresponding field of gsubTable and assing the result to the text variable
DBM:CreatePizzaTimer(seconds, text) -- Create the timer
end
else -- Either MODE isn't "DBM", or there's no global variable DBM
ShowTimer = function(seconds)
Stopwatch_StartCountdown(0, 0, seconds)
Stopwatch_Play()
end
end
local spell_school_enum =
{
[1] = "Physical",
[2] = "Holy",
[4] = "Fire",
[8] = "Nature",
[12] = "Frost",
[32] = "Shadow",
[64] = "Arcane",
[3] = "HolyStrike",
[5] = "FlameStrike",
[6] = "HolyFire",
[9] = "StormStrike",
[124] = "Chaos", -- Chromatic
[127] = "Chaos",
}
function has_value (tab, val)
for index, value in ipairs (tab) do
if value == val then
return true
end
end
return false
end
function set_new_val(tab, pkey, pval)
for key, value in pairs (tab) do
if value == pval then
--print("Removed Key: ",key," | Value: ",value)
tab[key] = nil
end
end
if pkey == 0 then
table.insert(tab,pval)
else
tab[pkey] = pval
end
--print("Added Key: ",pkey," | Value: ",pval)
end
function rem_old_val(tab, pval)
for key, value in pairs (tab) do
if value == pval then
--print("Removed Key: ",key," | Value: ",value)
tab[key] = nil
end
end
end
function get_key(tab, pval)
for key, value in pairs (tab) do
if value == pval then
return key
end
end
return nil
end
function round(num, numDecimalPlaces)
return tonumber(string.format("%." .. (numDecimalPlaces or 0) .. "f", num))
end
local LastCast = nil
local LastCastID = nil
local CastArray = { }
local logframe = CreateFrame("Frame")
logframe:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
logframe:SetScript("OnEvent", function(self, event, ...)
local ticktime,event_name,_,_,sourceName,_,_,_,destName,_,_,LastCastID,spellName,spellSchool = ...
if event_name == "SPELL_CAST_START" then
set_new_val(CastArray,ticktime,sourceName)
end
if event_name == "SPELL_INTERRUPT" then
local IntSpellID,IntSpellName = select(15,...)
local name,_,_,CastTime,_,_ = GetSpellInfo(IntSpellID)
local CastStartTime = get_key(CastArray,destName)
if CastStartTime ~= nil then
local CastedTime = (ticktime - CastStartTime)*1000
local CastedPercent = (CastedTime/CastTime)*100
CastedPercent = round(CastedPercent,3)
local msg = string.format("%s interrupted by %s at %d%%",destName,sourceName,CastedPercent)
SendChatMessage(msg)
rem_old_val(CastArray,destName)
else
--print("Error!")
end
local friendly = UnitCanAttack("player",sourceName)
if friendly then
SendChatMessage(string.format("Ally: %s \\ Interrupted by: %s \\ Spell Cast: %s \\ Interrupted By: [%s\\%d] \\ School Lockout: %s",destName,sourceName,spellName,IntSpellName,IntSpellID,spell_school_enum[spellSchool]),"PARTY")
else
SendChatMessage(string.format("Enemy: %s \\ Interrupted by: %s \\ Spell Cast: %s \\ Interrupted By: [%s\\%d] \\ School Lockout: %s",destName,sourceName,spellName,IntSpellName,IntSpellID,spell_school_enum[spellSchool]),"PARTY")
end
local lockout = SPELL_LOCKOUTS[spellID] -- Check if we have a lockout time for this interrupt spell
if lockout then -- If we do, show a timer.
ShowTimer(lockout, Interrupted_Spell)
end
end
end)
local f = CreateFrame("Frame") -- Create a frame and register some events.
f:RegisterEvent("PLAYER_ENTERING_WORLD")
function f:PLAYER_ENTERING_WORLD() -- This fires towards the end of the intitial login process.
ARENA1_GUID = UnitGUID("arena1") -- Record the arena1 GUID
ARENA2_GUID = UnitGUID("arena2") -- Record the arena2 GUID
ARENA3_GUID = UnitGUID("arena3") -- Record the arena3 GUID
TARGET_GUID = UnitGUID("target") -- Record the Target's GUID
PLAYER_GUID = UnitGUID("player") -- Record the player's GUID
self:UnregisterEvent("PLAYER_ENTERING_WORLD") -- Unregister this event, we don't care about it any more.
end
local SPELL_LOCKOUTS =
{
-- [interruptSpellID] = lockoutDuration, -- Spell Name (Class)
[2139] = 6, -- Counterspell (Mage)
[115781] = 6, -- Optical Blast (Warlock)
[19647] = 6, -- Spell Lock (Warlock)
[173320] = 5, -- Spear Hand Strike (Monk)
[1766] = 5, -- Kick (Rogue)
[78675] = 5, -- Solar Beam (Druid)
[97547] = 5, -- Solar Beam Int (Druid)
[96231] = 4, -- Rebuke (Paladin)
[6552] = 4, -- Pummel (Warrior)
[47528] = 4, -- Mind Freeze (Death Knight)
[106839] = 4, -- Skull Bash (Druid)
[147362] = 3, -- Counter Shot (Hunter)
[187707] = 3, -- Muzzle (Hunter)
[57994] = 3, -- Wind Shear (Shaman)
[183752] = 3, -- Consume Magic (Demon Hunter)
}
-- If this is "DBM", DBM's timers will be used. If this is "SW", Blizzard's stopwatch will be used.
local MODE = "DBM"
-- The text to display on DBM Timers.
-- %SPELL% will be replaced with the name of the interrupt spell you cast.
-- %ISPELL% will be replaced with the name of the spell you interrupted
-- %DUR% will be replaced with the duration of the lockout.
local BARTEXT = "%SPELL% interrupted %ISPELL%. Locked out for %DUR% seconds"
-------------------
-- END OF CONFIG --
-------------------
local ARENA1_GUID;
local ARENA2_GUID;
local ARENA3_GUID;
local TARGET_GUID;
local PLAYER_GUID;
local ShowTimer; -- Declare the ShowTimer variable as local
if MODE == "DBM" and DBM then -- If MODE is "DBM" and there's a global variable DBM
local DBM = DBM -- Create a local alias to DBM
local gsubTable = {} -- This table is used to handle text substitutions with gsub
ShowTimer = function(seconds, interruptSpell, interruptedSpell) -- Define the ShowTimer function
gsubTable.SPELL = interruptSpell -- Populate the fields of the table
gsubTable.ISPELL = interruptedSpell
gsubTable.DUR = seconds
local text = BARTEXT:gsub("%%(%a+)%%", gsubTable) -- Replace each occurrence of %SPELL%, %ISPELL% or %DUR% with the corresponding field of gsubTable and assing the result to the text variable
DBM:CreatePizzaTimer(seconds, text) -- Create the timer
end
else -- Either MODE isn't "DBM", or there's no global variable DBM
ShowTimer = function(seconds)
Stopwatch_StartCountdown(0, 0, seconds)
Stopwatch_Play()
end
end
local spell_school_enum =
{
[1] = "Physical",
[2] = "Holy",
[4] = "Fire",
[8] = "Nature",
[12] = "Frost",
[32] = "Shadow",
[64] = "Arcane",
[3] = "HolyStrike",
[5] = "FlameStrike",
[6] = "HolyFire",
[9] = "StormStrike",
[124] = "Chaos", -- Chromatic
[127] = "Chaos",
}
local logframe = CreateFrame("Frame")
logframe:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
logframe:SetScript("OnEvent", function(self, event, ...)
--print(spell_school_enum[2])
local _,spell_event,_,_,Interrupter,_,_,_,Interrupted,_,_,spellId,Interrupt_Spell,_,_,Interrupted_Spell,school_Lockout = ...
if spell_event == "SPELL_INTERRUPT" then
--print(school_Lockout)
if friendly then
SendChatMessage(string.format("Ally: %s \\ Interrupted by: %s \\ Spell Cast: %s \\ Interrupted By: [%s\\%d] \\ School Lockout: %s",Interrupted,Interrupter,Interrupted_Spell,Interrupt_Spell,spellId,spell_school_enum[school_Lockout]),"PARTY")
else
SendChatMessage(string.format("Enemy: %s \\ Interrupted by: %s \\ Spell Cast: %s \\ Interrupted By: [%s\\%d] \\ School Lockout: %s",Interrupted,Interrupter,Interrupted_Spell,Interrupt_Spell,spellId,spell_school_enum[school_Lockout]),"PARTY")
end
local lockout = SPELL_LOCKOUTS[spellId] -- Check if we have a lockout time for this interrupt spell
if lockout then -- If we do, show a timer.
ShowTimer(lockout, Interrupted_Spell)
end
end
end)
local f = CreateFrame("Frame") -- Create a frame and register some events.
f:RegisterEvent("PLAYER_ENTERING_WORLD")
function f:PLAYER_ENTERING_WORLD() -- This fires towards the end of the intitial login process.
ARENA1_GUID = UnitGUID("arena1") -- Record the arena1 GUID
ARENA2_GUID = UnitGUID("arena2") -- Record the arena2 GUID
ARENA3_GUID = UnitGUID("arena3") -- Record the arena3 GUID
TARGET_GUID = UnitGUID("target") -- Record the Target's GUID
PLAYER_GUID = UnitGUID("player") -- Record the player's GUID
self:UnregisterEvent("PLAYER_ENTERING_WORLD") -- Unregister this event, we don't care about it any more.
end