612 lines
30 KiB
Lua
612 lines
30 KiB
Lua
-- DPS Log Tracker — popup checklist for verifying all 35 COMBAT_LOG_EVENT subevents
|
|
--
|
|
-- All events share: arg1=subevent, arg2=sourceGUID, arg3=destGUID
|
|
-- Remaining args are subevent-specific (see layouts below).
|
|
|
|
-- ============================================================================
|
|
-- Reference tables
|
|
-- ============================================================================
|
|
|
|
-- Spell school bitmask (matches WotLK):
|
|
-- 0x01 = Physical, 0x02 = Holy, 0x04 = Fire, 0x08 = Nature,
|
|
-- 0x10 = Frost, 0x20 = Shadow, 0x40 = Arcane
|
|
-- Common combos: 0x03 = Holystrike, 0x05 = Flamestrike, 0x1C = Elemental,
|
|
-- 0x7E = Magic (all non-physical), 0x7F = Chaos (all 7)
|
|
|
|
-- Power types: 0=Mana, 1=Rage, 2=Focus, 3=Energy, 4=Combo Points
|
|
local powerNames = { [0]="Mana", "Rage", "Focus", "Energy", "Combo Points" }
|
|
|
|
-- Miss types (string): "MISS", "DODGE", "PARRY", "BLOCK", "EVADE", "IMMUNE",
|
|
-- "DEFLECT", "RESIST", "ABSORB", "REFLECT"
|
|
|
|
-- Aura types (string): "BUFF", "DEBUFF"
|
|
|
|
-- Environmental types (string): "EXHAUSTED", "DROWNING", "FALLING", "LAVA",
|
|
-- "SLIME", "FIRE"
|
|
|
|
-- SWING_DAMAGE arg10 flags bitmask:
|
|
-- bit 0 (value 1) = glancing hit
|
|
-- bit 1 (value 2) = crushing blow
|
|
-- bit 2 (value 4) = offhand swing
|
|
-- Extract: glancing = (math.mod(flags, 2) == 1)
|
|
-- crushing = (math.mod(math.floor(flags / 2), 2) == 1)
|
|
-- offhand = (math.mod(math.floor(flags / 4), 2) == 1)
|
|
-- Combined: offhand glancing = 5, offhand crushing = 6, etc.
|
|
|
|
-- ============================================================================
|
|
-- Helpers
|
|
-- ============================================================================
|
|
|
|
local function schoolName(school)
|
|
if school == 1 then return "Physical"
|
|
elseif school == 2 then return "Holy"
|
|
elseif school == 4 then return "Fire"
|
|
elseif school == 8 then return "Nature"
|
|
elseif school == 16 then return "Frost"
|
|
elseif school == 32 then return "Shadow"
|
|
elseif school == 64 then return "Arcane"
|
|
else return "School:" .. (school or "?") end
|
|
end
|
|
|
|
local function s(v)
|
|
if v == nil then return "nil" end
|
|
return tostring(v)
|
|
end
|
|
|
|
-- ============================================================================
|
|
-- Subevent definitions — ordered list of all 34
|
|
-- ============================================================================
|
|
|
|
-- Each entry: { displayName, subevent, [variantField], [variantValue] }
|
|
-- variantField: which arg to check for the variant (nil = any occurrence)
|
|
-- variantValue: specific value to match (string or number)
|
|
local SUBEVENTS = {
|
|
-- Damage
|
|
{ "SPELL_DAMAGE", "SPELL_DAMAGE" },
|
|
{ "SPELL_DAMAGE (crit)", "SPELL_DAMAGE", 10, 1 },
|
|
{ "RANGE_DAMAGE", "RANGE_DAMAGE" },
|
|
{ "RANGE_DAMAGE (crit)", "RANGE_DAMAGE", 10, 1 },
|
|
{ "SPELL_PERIODIC_DAMAGE", "SPELL_PERIODIC_DAMAGE" },
|
|
{ "DAMAGE_SHIELD", "DAMAGE_SHIELD" },
|
|
{ "DAMAGE_SPLIT", "DAMAGE_SPLIT" },
|
|
{ "SWING_DAMAGE", "SWING_DAMAGE" },
|
|
{ "SWING_DAMAGE (crit)", "SWING_DAMAGE", 9, 1 },
|
|
{ "SWING_DAMAGE (glancing)", "SWING_DAMAGE", 10, 1 }, -- flags bit 0
|
|
{ "SWING_DAMAGE (crushing)", "SWING_DAMAGE", 10, 2 }, -- flags bit 1
|
|
{ "SWING_DAMAGE (offhand)", "SWING_DAMAGE", 10, 4 }, -- flags bit 2
|
|
{ "ENVIRONMENTAL (DROWNING)", "ENVIRONMENTAL_DAMAGE", 4, "DROWNING" },
|
|
{ "ENVIRONMENTAL (FALLING)", "ENVIRONMENTAL_DAMAGE", 4, "FALLING" },
|
|
{ "ENVIRONMENTAL (LAVA)", "ENVIRONMENTAL_DAMAGE", 4, "LAVA" },
|
|
{ "ENVIRONMENTAL (SLIME)", "ENVIRONMENTAL_DAMAGE", 4, "SLIME" },
|
|
{ "ENVIRONMENTAL (FIRE)", "ENVIRONMENTAL_DAMAGE", 4, "FIRE" },
|
|
{ "ENVIRONMENTAL (EXHAUSTED)", "ENVIRONMENTAL_DAMAGE", 4, "EXHAUSTED" },
|
|
-- Misses — swing
|
|
{ "SWING_MISSED (MISS)", "SWING_MISSED", 4, "MISS" },
|
|
{ "SWING_MISSED (DODGE)", "SWING_MISSED", 4, "DODGE" },
|
|
{ "SWING_MISSED (PARRY)", "SWING_MISSED", 4, "PARRY" },
|
|
{ "SWING_MISSED (BLOCK)", "SWING_MISSED", 4, "BLOCK" },
|
|
{ "SWING_MISSED (EVADE)", "SWING_MISSED", 4, "EVADE" },
|
|
{ "SWING_MISSED (IMMUNE)", "SWING_MISSED", 4, "IMMUNE" },
|
|
{ "SWING_MISSED (RESIST)", "SWING_MISSED", 4, "RESIST" },
|
|
{ "SWING_MISSED (ABSORB)", "SWING_MISSED", 4, "ABSORB" },
|
|
-- Misses — spell/range
|
|
{ "SPELL_MISSED (MISS)", "SPELL_MISSED", 6, "MISS" },
|
|
{ "SPELL_MISSED (DODGE)", "SPELL_MISSED", 6, "DODGE" },
|
|
{ "SPELL_MISSED (PARRY)", "SPELL_MISSED", 6, "PARRY" },
|
|
{ "SPELL_MISSED (IMMUNE)", "SPELL_MISSED", 6, "IMMUNE" },
|
|
{ "SPELL_MISSED (RESIST)", "SPELL_MISSED", 6, "RESIST" },
|
|
{ "SPELL_MISSED (ABSORB)", "SPELL_MISSED", 6, "ABSORB" },
|
|
{ "SPELL_MISSED (DEFLECT)", "SPELL_MISSED", 6, "DEFLECT" }, -- dead in vanilla, kept for completeness
|
|
{ "SPELL_MISSED (REFLECT)", "SPELL_MISSED", 6, "REFLECT" },
|
|
{ "RANGE_MISSED", "RANGE_MISSED" },
|
|
{ "SPELL_PERIODIC_MISSED", "SPELL_PERIODIC_MISSED" },
|
|
{ "DAMAGE_SHIELD_MISSED", "DAMAGE_SHIELD_MISSED" },
|
|
-- Heals
|
|
{ "SPELL_HEAL", "SPELL_HEAL" },
|
|
{ "SPELL_HEAL (crit)", "SPELL_HEAL", 8, 1 },
|
|
{ "SPELL_PERIODIC_HEAL", "SPELL_PERIODIC_HEAL" },
|
|
-- Power
|
|
{ "SPELL_ENERGIZE (Mana)", "SPELL_ENERGIZE" }, -- amount=0 from hook, can't distinguish
|
|
{ "SPELL_PERIODIC_ENERGIZE", "SPELL_PERIODIC_ENERGIZE" },
|
|
{ "SPELL_PERIODIC_DRAIN", "SPELL_PERIODIC_DRAIN" },
|
|
{ "SPELL_PERIODIC_LEECH", "SPELL_PERIODIC_LEECH" },
|
|
-- Auras
|
|
{ "SPELL_AURA_APPLIED (BUFF)", "SPELL_AURA_APPLIED", 6, "BUFF" },
|
|
{ "SPELL_AURA_APPLIED (DEBUFF)", "SPELL_AURA_APPLIED", 6, "DEBUFF" },
|
|
{ "SPELL_AURA_REMOVED (BUFF)", "SPELL_AURA_REMOVED", 6, "BUFF" },
|
|
{ "SPELL_AURA_REMOVED (DEBUFF)", "SPELL_AURA_REMOVED", 6, "DEBUFF" },
|
|
{ "SPELL_AURA_APPLIED_DOSE", "SPELL_AURA_APPLIED_DOSE" },
|
|
{ "SPELL_AURA_REMOVED_DOSE", "SPELL_AURA_REMOVED_DOSE" },
|
|
{ "SPELL_AURA_REFRESH", "SPELL_AURA_REFRESH" },
|
|
{ "SPELL_AURA_BROKEN", "SPELL_AURA_BROKEN" },
|
|
{ "SPELL_AURA_BROKEN_SPELL", "SPELL_AURA_BROKEN_SPELL" },
|
|
-- Casts
|
|
{ "SPELL_CAST_START", "SPELL_CAST_START" },
|
|
{ "SPELL_CAST_SUCCESS", "SPELL_CAST_SUCCESS" },
|
|
{ "SPELL_CAST_FAILED", "SPELL_CAST_FAILED" },
|
|
-- Misc spell
|
|
{ "SPELL_INTERRUPT", "SPELL_INTERRUPT" },
|
|
{ "SPELL_DISPEL", "SPELL_DISPEL" },
|
|
{ "SPELL_DISPEL_FAILED", "SPELL_DISPEL_FAILED" },
|
|
{ "SPELL_EXTRA_ATTACKS", "SPELL_EXTRA_ATTACKS" },
|
|
{ "SPELL_SUMMON", "SPELL_SUMMON" },
|
|
{ "SPELL_RESURRECT", "SPELL_RESURRECT" },
|
|
{ "SPELL_INSTAKILL", "SPELL_INSTAKILL" },
|
|
-- Deaths
|
|
{ "UNIT_DIED", "UNIT_DIED" },
|
|
{ "UNIT_DESTROYED", "UNIT_DESTROYED" },
|
|
{ "PARTY_KILL", "PARTY_KILL" },
|
|
}
|
|
|
|
local NUM_SUBEVENTS = table.getn(SUBEVENTS)
|
|
|
|
-- Build reverse lookup: for each subevent name, list all indices that match it
|
|
-- (one subevent may have multiple variant entries)
|
|
local subeventIndices = {} -- [subeventName] = { idx1, idx2, ... }
|
|
for i = 1, NUM_SUBEVENTS do
|
|
local name = SUBEVENTS[i][2]
|
|
if not subeventIndices[name] then
|
|
subeventIndices[name] = {}
|
|
end
|
|
table.insert(subeventIndices[name], i)
|
|
end
|
|
|
|
-- Tracking state
|
|
local seen = {} -- [index] = true
|
|
local seenArgs = {} -- [index] = formatted arg string
|
|
local seenCount = 0
|
|
|
|
-- ============================================================================
|
|
-- Arg formatters per subevent (return a short summary string)
|
|
-- ============================================================================
|
|
|
|
local function fmtSpellDmg(spellId, amount, school, resisted, absorbed, blocked, critical)
|
|
local c = (critical == 1) and " CRIT" or ""
|
|
return format("spell=%s amt=%s %s res=%s abs=%s blk=%s%s",
|
|
s(spellId), s(amount), schoolName(school or 0), s(resisted), s(absorbed), s(blocked), c)
|
|
end
|
|
|
|
local function fmtSwingDmg(amount, school, resisted, absorbed, blocked, critical, flags)
|
|
local c = (critical == 1) and " CRIT" or ""
|
|
local f = ""
|
|
if flags and flags > 0 then
|
|
if math.mod(flags, 2) == 1 then f = f .. " GLANC" end
|
|
if math.mod(math.floor(flags / 2), 2) == 1 then f = f .. " CRUSH" end
|
|
if math.mod(math.floor(flags / 4), 2) == 1 then f = f .. " OH" end
|
|
end
|
|
return format("amt=%s %s res=%s abs=%s blk=%s%s%s",
|
|
s(amount), schoolName(school or 0), s(resisted), s(absorbed), s(blocked), c, f)
|
|
end
|
|
|
|
local argFormatters = {}
|
|
|
|
-- Pattern A — numeric args
|
|
argFormatters["SPELL_DAMAGE"] = function(a4,a5,a6,a7,a8,a9,a10) return fmtSpellDmg(a4,a5,a6,a7,a8,a9,a10) end
|
|
argFormatters["RANGE_DAMAGE"] = function(a4,a5,a6,a7,a8,a9,a10) return fmtSpellDmg(a4,a5,a6,a7,a8,a9,a10) end
|
|
argFormatters["SPELL_PERIODIC_DAMAGE"] = function(a4,a5,a6,a7,a8,a9,a10) return fmtSpellDmg(a4,a5,a6,a7,a8,a9,a10) end
|
|
argFormatters["DAMAGE_SHIELD"] = function(a4,a5,a6,a7,a8,a9,a10) return fmtSpellDmg(a4,a5,a6,a7,a8,a9,a10) end
|
|
argFormatters["SWING_DAMAGE"] = function(a4,a5,a6,a7,a8,a9,a10) return fmtSwingDmg(a4,a5,a6,a7,a8,a9,a10) end
|
|
argFormatters["SPELL_HEAL"] = function(a4,a5,a6,a7,a8)
|
|
local c = (a8 == 1) and " CRIT" or ""
|
|
return format("spell=%s amt=%s oh=%s abs=%s%s", s(a4), s(a5), s(a6), s(a7), c)
|
|
end
|
|
argFormatters["SPELL_PERIODIC_HEAL"] = function(a4,a5) return format("spell=%s amt=%s", s(a4), s(a5)) end
|
|
argFormatters["SPELL_ENERGIZE"] = function(a4,a5,a6)
|
|
return format("spell=%s amt=%s type=%s", s(a4), s(a5), powerNames[a6] or s(a6))
|
|
end
|
|
argFormatters["SPELL_PERIODIC_ENERGIZE"] = function(a4,a5,a6,a7)
|
|
return format("spell=%s amt=%s over=%s type=%s", s(a4), s(a5), s(a6), powerNames[a7] or s(a7))
|
|
end
|
|
argFormatters["SPELL_PERIODIC_LEECH"] = function(a4,a5,a6,a7,a8,a9)
|
|
return format("spell=%s amt=%s %s gain=%s abs=%s res=%s", s(a4), s(a5), schoolName(a6 or 0), s(a7), s(a8), s(a9))
|
|
end
|
|
argFormatters["SPELL_PERIODIC_DRAIN"] = function(a4,a5,a6,a7)
|
|
return format("spell=%s amt=%s type=%s extra=%s", s(a4), s(a5), powerNames[a6] or s(a6), s(a7))
|
|
end
|
|
argFormatters["SPELL_EXTRA_ATTACKS"] = function(a4,a5,a6)
|
|
return format("spell=%s school=%s amt=%s", s(a4), s(a5), s(a6))
|
|
end
|
|
|
|
-- Pattern B — swing missed
|
|
argFormatters["SWING_MISSED"] = function(a4) return s(a4) end
|
|
|
|
-- Pattern C — spell + string
|
|
argFormatters["SPELL_MISSED"] = function(a4,a5,a6) return format("spell=%s %s %s", s(a4), schoolName(a5 or 0), s(a6)) end
|
|
argFormatters["SPELL_PERIODIC_MISSED"] = function(a4,a5,a6) return format("spell=%s %s %s", s(a4), schoolName(a5 or 0), s(a6)) end
|
|
argFormatters["DAMAGE_SHIELD_MISSED"] = function(a4,a5,a6) return format("spell=%s %s %s", s(a4), schoolName(a5 or 0), s(a6)) end
|
|
argFormatters["RANGE_MISSED"] = function(a4,a5,a6) return format("spell=%s %s %s", s(a4), schoolName(a5 or 0), s(a6)) end
|
|
argFormatters["SPELL_AURA_APPLIED"] = function(a4,a5,a6) return format("spell=%s %s %s", s(a4), schoolName(a5 or 0), s(a6)) end
|
|
argFormatters["SPELL_AURA_REMOVED"] = function(a4,a5,a6) return format("spell=%s %s %s", s(a4), schoolName(a5 or 0), s(a6)) end
|
|
argFormatters["SPELL_AURA_REFRESH"] = function(a4,a5,a6) return format("spell=%s %s %s", s(a4), schoolName(a5 or 0), s(a6)) end
|
|
argFormatters["SPELL_AURA_BROKEN"] = function(a4,a5,a6) return format("spell=%s %s %s", s(a4), schoolName(a5 or 0), s(a6)) end
|
|
argFormatters["SPELL_CAST_FAILED"] = function(a4,a5,a6) return format("spell=%s %s reason=%s", s(a4), schoolName(a5 or 0), s(a6)) end
|
|
|
|
-- Pattern D — spell only
|
|
argFormatters["SPELL_CAST_START"] = function(a4,a5) return format("spell=%s %s", s(a4), schoolName(a5 or 0)) end
|
|
argFormatters["SPELL_CAST_SUCCESS"] = function(a4,a5) return format("spell=%s %s", s(a4), schoolName(a5 or 0)) end
|
|
argFormatters["SPELL_SUMMON"] = function(a4,a5) return format("spell=%s %s", s(a4), schoolName(a5 or 0)) end
|
|
argFormatters["SPELL_RESURRECT"] = function(a4,a5) return format("spell=%s %s", s(a4), schoolName(a5 or 0)) end
|
|
argFormatters["SPELL_INSTAKILL"] = function(a4,a5) return format("spell=%s %s", s(a4), schoolName(a5 or 0)) end
|
|
|
|
-- Pattern E — base (no extra args)
|
|
argFormatters["UNIT_DIED"] = function() return "" end
|
|
argFormatters["PARTY_KILL"] = function() return "" end
|
|
|
|
-- Pattern F — spell + string + amount
|
|
argFormatters["SPELL_AURA_APPLIED_DOSE"] = function(a4,a5,a6,a7) return format("spell=%s %s %s x%s", s(a4), schoolName(a5 or 0), s(a6), s(a7)) end
|
|
argFormatters["SPELL_AURA_REMOVED_DOSE"] = function(a4,a5,a6,a7) return format("spell=%s %s %s x%s", s(a4), schoolName(a5 or 0), s(a6), s(a7)) end
|
|
|
|
-- Pattern G — dispel/interrupt
|
|
argFormatters["SPELL_DISPEL"] = function(a4,a5,a6,a7,a8)
|
|
return format("spell=%s extra=%s %s %s", s(a4), s(a6), schoolName(a7 or 0), s(a8))
|
|
end
|
|
argFormatters["SPELL_INTERRUPT"] = function(a4,a5,a6,a7)
|
|
return format("spell=%s extra=%s %s", s(a4), s(a6), schoolName(a7 or 0))
|
|
end
|
|
argFormatters["SPELL_AURA_BROKEN_SPELL"] = function(a4,a5,a6,a7,a8)
|
|
return format("spell=%s extra=%s %s %s", s(a4), s(a6), schoolName(a7 or 0), s(a8))
|
|
end
|
|
|
|
-- Pattern H — environmental
|
|
argFormatters["ENVIRONMENTAL_DAMAGE"] = function(a4,a5,a6,a7)
|
|
return format("env=%s amt=%s %s abs=%s", s(a4), s(a5), schoolName(a6 or 0), s(a7))
|
|
end
|
|
|
|
-- New subevents
|
|
argFormatters["DAMAGE_SPLIT"] = function(a4,a5,a6,a7,a8,a9,a10) return fmtSpellDmg(a4,a5,a6,a7,a8,a9,a10) end
|
|
argFormatters["SPELL_DISPEL_FAILED"] = function(a4,a5) return format("spell=%s %s", s(a4), schoolName(a5 or 0)) end
|
|
argFormatters["UNIT_DESTROYED"] = function() return "" end
|
|
|
|
-- ============================================================================
|
|
-- Chat output handlers (preserved from original)
|
|
-- ============================================================================
|
|
|
|
local chatHandlers = {}
|
|
|
|
chatHandlers["SPELL_DAMAGE"] = function(src, dst, spellId, amount, school, resisted, absorbed, blocked, critical)
|
|
local crit = critical == 1 and " (CRIT)" or ""
|
|
DEFAULT_CHAT_FRAME:AddMessage(format("|cffff0000SPELL_DAMAGE|r %s -> %s: spell %d for %d %s%s", src, dst, spellId, amount, schoolName(school), crit))
|
|
end
|
|
chatHandlers["RANGE_DAMAGE"] = function(src, dst, spellId, amount, school, resisted, absorbed, blocked, critical)
|
|
local crit = critical == 1 and " (CRIT)" or ""
|
|
DEFAULT_CHAT_FRAME:AddMessage(format("|cffff0000RANGE_DAMAGE|r %s -> %s: spell %d for %d %s%s", src, dst, spellId, amount, schoolName(school), crit))
|
|
end
|
|
chatHandlers["SWING_DAMAGE"] = function(src, dst, amount, school, resisted, absorbed, blocked, critical, flags)
|
|
local extra = ""
|
|
if critical == 1 then extra = extra .. " CRIT" end
|
|
if flags then
|
|
if math.mod(flags, 2) == 1 then extra = extra .. " GLANCING" end
|
|
if math.mod(math.floor(flags / 2), 2) == 1 then extra = extra .. " CRUSHING" end
|
|
if math.mod(math.floor(flags / 4), 2) == 1 then extra = extra .. " (OH)" end
|
|
end
|
|
DEFAULT_CHAT_FRAME:AddMessage(format("|cffff0000SWING_DAMAGE|r %s -> %s: %d%s", src, dst, amount, extra))
|
|
end
|
|
chatHandlers["SWING_MISSED"] = function(src, dst, missType)
|
|
DEFAULT_CHAT_FRAME:AddMessage(format("|cffaaaaaaSWING_MISSED|r %s -> %s: %s", src, dst, missType))
|
|
end
|
|
chatHandlers["SPELL_MISSED"] = function(src, dst, spellId, spellSchool, missType)
|
|
DEFAULT_CHAT_FRAME:AddMessage(format("|cffaaaaaaSPELL_MISSED|r %s -> %s: spell %d %s", src, dst, spellId, missType))
|
|
end
|
|
chatHandlers["RANGE_MISSED"] = function(src, dst, spellId, spellSchool, missType)
|
|
DEFAULT_CHAT_FRAME:AddMessage(format("|cfffaaaaaaRANGE_MISSED|r %s -> %s: spell %d %s", src, dst, spellId, missType))
|
|
end
|
|
chatHandlers["ENVIRONMENTAL_DAMAGE"] = function(src, dst, envType, amount, school, absorbed)
|
|
DEFAULT_CHAT_FRAME:AddMessage(format("|cffff8800ENVIRONMENTAL_DAMAGE|r %s: %s for %d", dst, envType, amount))
|
|
end
|
|
chatHandlers["DAMAGE_SHIELD"] = function(src, dst, spellId, amount, school)
|
|
DEFAULT_CHAT_FRAME:AddMessage(format("|cffff4400DAMAGE_SHIELD|r %s -> %s: %d %s", src, dst, amount, schoolName(school)))
|
|
end
|
|
chatHandlers["SPELL_HEAL"] = function(src, dst, spellId, amount, overheal, absorbed, critical)
|
|
local crit = critical == 1 and " (CRIT)" or ""
|
|
DEFAULT_CHAT_FRAME:AddMessage(format("|cff00ff00SPELL_HEAL|r %s -> %s: spell %d for %d%s", src, dst, spellId, amount, crit))
|
|
end
|
|
chatHandlers["SPELL_PERIODIC_HEAL"] = function(src, dst, spellId, amount)
|
|
DEFAULT_CHAT_FRAME:AddMessage(format("|cff00cc00SPELL_PERIODIC_HEAL|r %s -> %s: spell %d for %d", src, dst, spellId, amount))
|
|
end
|
|
chatHandlers["SPELL_PERIODIC_DAMAGE"] = function(src, dst, spellId, amount, school)
|
|
DEFAULT_CHAT_FRAME:AddMessage(format("|cffcc0000SPELL_PERIODIC_DAMAGE|r %s -> %s: spell %d for %d %s", src, dst, spellId, amount, schoolName(school)))
|
|
end
|
|
chatHandlers["SPELL_ENERGIZE"] = function(src, dst, spellId, amount, powerType)
|
|
local pname = powerNames[powerType] or "Unknown"
|
|
DEFAULT_CHAT_FRAME:AddMessage(format("|cff4488ffSPELL_ENERGIZE|r %s -> %s: spell %d +%d %s", src, dst, spellId, amount, pname))
|
|
end
|
|
chatHandlers["SPELL_PERIODIC_ENERGIZE"] = function(src, dst, spellId, amount, overEnergize, powerType)
|
|
local pname = powerNames[powerType] or "Unknown"
|
|
DEFAULT_CHAT_FRAME:AddMessage(format("|cff4488ffSPELL_PERIODIC_ENERGIZE|r %s -> %s: spell %d +%d %s", src, dst, spellId, amount, pname))
|
|
end
|
|
chatHandlers["SPELL_PERIODIC_LEECH"] = function(src, dst, spellId, amount, school, gained)
|
|
DEFAULT_CHAT_FRAME:AddMessage(format("|cff8844ffSPELL_PERIODIC_LEECH|r %s -> %s: spell %d drained %d, gained %d", src, dst, spellId, amount, gained or 0))
|
|
end
|
|
chatHandlers["SPELL_PERIODIC_DRAIN"] = function(src, dst, spellId, amount, powerType, extraAmount)
|
|
local pname = powerNames[powerType] or "Unknown"
|
|
DEFAULT_CHAT_FRAME:AddMessage(format("|cff8844ffSPELL_PERIODIC_DRAIN|r %s -> %s: spell %d drained %d %s, gained %d", src, dst, spellId, amount, pname, extraAmount or 0))
|
|
end
|
|
chatHandlers["SPELL_AURA_APPLIED"] = function(src, dst, spellId, spellSchool, auraType)
|
|
DEFAULT_CHAT_FRAME:AddMessage(format("|cff8888ffSPELL_AURA_APPLIED|r %s: spell %d (%s, %s)", dst, spellId, auraType, schoolName(spellSchool)))
|
|
end
|
|
chatHandlers["SPELL_AURA_REMOVED"] = function(src, dst, spellId, spellSchool, auraType)
|
|
DEFAULT_CHAT_FRAME:AddMessage(format("|cff6666ccSPELL_AURA_REMOVED|r %s: spell %d (%s)", dst, spellId, auraType))
|
|
end
|
|
chatHandlers["SPELL_AURA_APPLIED_DOSE"] = function(src, dst, spellId, spellSchool, auraType, stacks)
|
|
DEFAULT_CHAT_FRAME:AddMessage(format("|cff8888ffSPELL_AURA_APPLIED_DOSE|r %s: spell %d x%d (%s)", dst, spellId, stacks, auraType))
|
|
end
|
|
chatHandlers["SPELL_AURA_REMOVED_DOSE"] = function(src, dst, spellId, spellSchool, auraType, stacks)
|
|
DEFAULT_CHAT_FRAME:AddMessage(format("|cff6666ccSPELL_AURA_REMOVED_DOSE|r %s: spell %d x%d (%s)", dst, spellId, stacks, auraType))
|
|
end
|
|
chatHandlers["SPELL_AURA_REFRESH"] = function(src, dst, spellId, spellSchool, auraType)
|
|
DEFAULT_CHAT_FRAME:AddMessage(format("|cff7777ddSPELL_AURA_REFRESH|r %s: spell %d (%s)", dst, spellId, auraType))
|
|
end
|
|
chatHandlers["SPELL_AURA_BROKEN"] = function(src, dst, spellId, spellSchool, auraType)
|
|
DEFAULT_CHAT_FRAME:AddMessage(format("|cffff8800SPELL_AURA_BROKEN|r %s -> %s: spell %d broken by melee (%s)", src, dst, spellId, auraType))
|
|
end
|
|
chatHandlers["SPELL_AURA_BROKEN_SPELL"] = function(src, dst, spellId, spellSchool, extraSpellId, extraSchool, auraType)
|
|
DEFAULT_CHAT_FRAME:AddMessage(format("|cffff8800SPELL_AURA_BROKEN_SPELL|r %s -> %s: spell %d broken by spell %d (%s)", src, dst, spellId, extraSpellId, auraType))
|
|
end
|
|
chatHandlers["SPELL_CAST_START"] = function(src, dst, spellId, spellSchool)
|
|
DEFAULT_CHAT_FRAME:AddMessage(format("|cffffff00SPELL_CAST_START|r %s: spell %d (%s)", src, spellId, schoolName(spellSchool)))
|
|
end
|
|
chatHandlers["SPELL_CAST_SUCCESS"] = function(src, dst, spellId)
|
|
DEFAULT_CHAT_FRAME:AddMessage(format("|cffffff00SPELL_CAST_SUCCESS|r %s: spell %d", src, spellId))
|
|
end
|
|
chatHandlers["SPELL_CAST_FAILED"] = function(src, dst, spellId, spellSchool, failedType)
|
|
DEFAULT_CHAT_FRAME:AddMessage(format("|cffff4444SPELL_CAST_FAILED|r %s: spell %d (%s)", src, spellId, failedType))
|
|
end
|
|
chatHandlers["SPELL_INTERRUPT"] = function(src, dst, spellId, spellSchool, extraSpellId, extraSchool)
|
|
DEFAULT_CHAT_FRAME:AddMessage(format("|cffff8800SPELL_INTERRUPT|r %s interrupted %s: spell %d (%s)", src, dst, extraSpellId, schoolName(extraSchool)))
|
|
end
|
|
chatHandlers["SPELL_DISPEL"] = function(src, dst, spellId, spellSchool, extraSpellId, extraSchool, auraType)
|
|
DEFAULT_CHAT_FRAME:AddMessage(format("|cff88ffffSPELL_DISPEL|r %s dispelled %s: spell %d (%s, %s)", src, dst, extraSpellId, auraType, schoolName(extraSchool)))
|
|
end
|
|
chatHandlers["SPELL_EXTRA_ATTACKS"] = function(src, dst, spellId)
|
|
DEFAULT_CHAT_FRAME:AddMessage(format("|cffff8800SPELL_EXTRA_ATTACKS|r %s: spell %d", src, spellId))
|
|
end
|
|
chatHandlers["SPELL_SUMMON"] = function(src, dst, spellId)
|
|
DEFAULT_CHAT_FRAME:AddMessage(format("|cff88ff88SPELL_SUMMON|r %s summoned %s: spell %d", src, dst, spellId))
|
|
end
|
|
chatHandlers["SPELL_RESURRECT"] = function(src, dst, spellId)
|
|
DEFAULT_CHAT_FRAME:AddMessage(format("|cff88ff88SPELL_RESURRECT|r %s resurrected %s: spell %d", src, dst, spellId))
|
|
end
|
|
chatHandlers["SPELL_INSTAKILL"] = function(src, dst, spellId)
|
|
DEFAULT_CHAT_FRAME:AddMessage(format("|cffff0000SPELL_INSTAKILL|r %s killed %s: spell %d", src, dst, spellId))
|
|
end
|
|
chatHandlers["UNIT_DIED"] = function(src, dst)
|
|
DEFAULT_CHAT_FRAME:AddMessage(format("|cff888888UNIT_DIED|r %s", dst))
|
|
end
|
|
chatHandlers["PARTY_KILL"] = function(src, dst)
|
|
DEFAULT_CHAT_FRAME:AddMessage(format("|cffff8800PARTY_KILL|r %s killed %s", src, dst))
|
|
end
|
|
chatHandlers["SPELL_PERIODIC_MISSED"] = function(src, dst, spellId, spellSchool, missType)
|
|
DEFAULT_CHAT_FRAME:AddMessage(format("|cffaaaaaaSPELL_PERIODIC_MISSED|r %s -> %s: spell %d %s", src, dst, spellId, missType))
|
|
end
|
|
chatHandlers["DAMAGE_SHIELD_MISSED"] = function(src, dst, spellId, spellSchool, missType)
|
|
DEFAULT_CHAT_FRAME:AddMessage(format("|cffaaaaaaDAMAGE_SHIELD_MISSED|r %s -> %s: spell %d %s", src, dst, spellId, missType))
|
|
end
|
|
chatHandlers["DAMAGE_SPLIT"] = function(src, dst, spellId, amount, school)
|
|
DEFAULT_CHAT_FRAME:AddMessage(format("|cffff6600DAMAGE_SPLIT|r %s -> %s: spell %d for %d %s", src, dst, spellId, amount, schoolName(school)))
|
|
end
|
|
chatHandlers["SPELL_DISPEL_FAILED"] = function(src, dst, spellId, spellSchool)
|
|
DEFAULT_CHAT_FRAME:AddMessage(format("|cff888888SPELL_DISPEL_FAILED|r %s -> %s: spell %d resisted", src, dst, spellId))
|
|
end
|
|
chatHandlers["UNIT_DESTROYED"] = function(src, dst)
|
|
DEFAULT_CHAT_FRAME:AddMessage(format("|cff666666UNIT_DESTROYED|r %s", dst))
|
|
end
|
|
|
|
-- ============================================================================
|
|
-- UI — Draggable popup tracker
|
|
-- ============================================================================
|
|
|
|
local ROW_HEIGHT = 14
|
|
local FRAME_WIDTH = 420
|
|
local VISIBLE_ROWS = 25
|
|
local FRAME_HEIGHT = ROW_HEIGHT * VISIBLE_ROWS + 52 -- title + counter + padding
|
|
|
|
local main = CreateFrame("Frame", "DPSLogTracker", UIParent)
|
|
main:SetWidth(FRAME_WIDTH)
|
|
main:SetHeight(FRAME_HEIGHT)
|
|
main:SetPoint("CENTER", UIParent, "CENTER", 300, 0)
|
|
main:SetBackdrop({
|
|
bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background",
|
|
edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border",
|
|
tile = true, tileSize = 32, edgeSize = 16,
|
|
insets = { left = 4, right = 4, top = 4, bottom = 4 },
|
|
})
|
|
main:SetBackdropColor(0, 0, 0, 0.85)
|
|
main:EnableMouse(true)
|
|
main:SetMovable(true)
|
|
main:RegisterForDrag("LeftButton")
|
|
main:SetScript("OnDragStart", function() main:StartMoving() end)
|
|
main:SetScript("OnDragStop", function() main:StopMovingOrSizing() end)
|
|
main:SetFrameStrata("DIALOG")
|
|
main:SetClampedToScreen(true)
|
|
|
|
-- Title
|
|
local title = main:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
|
|
title:SetPoint("TOPLEFT", main, "TOPLEFT", 12, -10)
|
|
title:SetText("DPS Log Tracker")
|
|
title:SetTextColor(1, 0.82, 0)
|
|
|
|
-- Close button
|
|
local closeBtn = CreateFrame("Button", nil, main, "UIPanelCloseButton")
|
|
closeBtn:SetPoint("TOPRIGHT", main, "TOPRIGHT", -2, -2)
|
|
closeBtn:SetScript("OnClick", function() main:Hide() end)
|
|
|
|
-- Counter
|
|
local counter = main:CreateFontString(nil, "OVERLAY", "GameFontNormal")
|
|
counter:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -2)
|
|
counter:SetTextColor(0.7, 0.7, 0.7)
|
|
|
|
local function updateCounter()
|
|
counter:SetText(format("Seen: %d/%d", seenCount, NUM_SUBEVENTS))
|
|
end
|
|
updateCounter()
|
|
|
|
-- Scroll frame
|
|
local scrollFrame = CreateFrame("ScrollFrame", "DPSLogTrackerScroll", main, "FauxScrollFrameTemplate")
|
|
scrollFrame:SetPoint("TOPLEFT", main, "TOPLEFT", 8, -48)
|
|
scrollFrame:SetPoint("BOTTOMRIGHT", main, "BOTTOMRIGHT", -28, 8)
|
|
|
|
-- Row frames
|
|
local rows = {}
|
|
for i = 1, VISIBLE_ROWS do
|
|
local row = CreateFrame("Frame", nil, main)
|
|
row:SetWidth(FRAME_WIDTH - 40)
|
|
row:SetHeight(ROW_HEIGHT)
|
|
row:SetPoint("TOPLEFT", scrollFrame, "TOPLEFT", 2, -((i - 1) * ROW_HEIGHT))
|
|
|
|
local check = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
|
|
check:SetPoint("LEFT", row, "LEFT", 0, 0)
|
|
check:SetWidth(20)
|
|
check:SetJustifyH("LEFT")
|
|
|
|
local nameStr = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
|
|
nameStr:SetPoint("LEFT", check, "RIGHT", 0, 0)
|
|
nameStr:SetWidth(FRAME_WIDTH - 60)
|
|
nameStr:SetJustifyH("LEFT")
|
|
|
|
row.check = check
|
|
row.nameStr = nameStr
|
|
rows[i] = row
|
|
end
|
|
|
|
local function updateRows()
|
|
local offset = FauxScrollFrame_GetOffset(scrollFrame)
|
|
for i = 1, VISIBLE_ROWS do
|
|
local idx = offset + i
|
|
local row = rows[i]
|
|
if idx <= NUM_SUBEVENTS then
|
|
row:Show()
|
|
local displayName = SUBEVENTS[idx][1]
|
|
if seen[idx] then
|
|
row.check:SetText("|cff00ff00x|r")
|
|
local argStr = seenArgs[idx]
|
|
if argStr and argStr ~= "" then
|
|
row.nameStr:SetText("|cffffffff" .. displayName .. "|r |cffaaaaaa" .. argStr .. "|r")
|
|
else
|
|
row.nameStr:SetText("|cffffffff" .. displayName .. "|r")
|
|
end
|
|
else
|
|
row.check:SetText("|cff666666-|r")
|
|
row.nameStr:SetText("|cff666666" .. displayName .. "|r")
|
|
end
|
|
else
|
|
row:Hide()
|
|
end
|
|
end
|
|
FauxScrollFrame_Update(scrollFrame, NUM_SUBEVENTS, VISIBLE_ROWS, ROW_HEIGHT)
|
|
end
|
|
|
|
scrollFrame:SetScript("OnVerticalScroll", function()
|
|
FauxScrollFrame_OnVerticalScroll(ROW_HEIGHT, updateRows)
|
|
end)
|
|
|
|
updateRows()
|
|
|
|
-- Reset button
|
|
local resetBtn = CreateFrame("Button", nil, main, "UIPanelButtonTemplate")
|
|
resetBtn:SetWidth(60)
|
|
resetBtn:SetHeight(18)
|
|
resetBtn:SetPoint("TOPRIGHT", closeBtn, "TOPLEFT", -4, -6)
|
|
resetBtn:SetText("Reset")
|
|
resetBtn:SetScript("OnClick", function()
|
|
for i = 1, NUM_SUBEVENTS do
|
|
seen[i] = nil
|
|
seenArgs[i] = nil
|
|
end
|
|
seenCount = 0
|
|
updateCounter()
|
|
updateRows()
|
|
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00DPSLog Tracker|r reset.")
|
|
end)
|
|
|
|
-- ============================================================================
|
|
-- Event dispatcher
|
|
-- ============================================================================
|
|
|
|
local function print(msg)
|
|
DEFAULT_CHAT_FRAME:AddMessage(msg)
|
|
end
|
|
|
|
local eventFrame = CreateFrame("Frame")
|
|
eventFrame:RegisterEvent("COMBAT_LOG_EVENT")
|
|
|
|
eventFrame:SetScript("OnEvent", function()
|
|
-- if event == "PLAYER_LOGIN" then
|
|
-- eventFrame:RegisterEvent("COMBAT_LOG_EVENT")
|
|
-- DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00DPSLog|r registered COMBAT_LOG_EVENT")
|
|
-- return
|
|
-- end
|
|
|
|
local subevent = arg1
|
|
if not subevent then return end
|
|
|
|
-- Collect args into a table for variant field lookup
|
|
local args = { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10 }
|
|
|
|
-- Check all variant entries for this subevent
|
|
local indices = subeventIndices[subevent]
|
|
if not indices then return end
|
|
|
|
local any_new = false
|
|
for _, idx in ipairs(indices) do
|
|
if not seen[idx] then
|
|
local entry = SUBEVENTS[idx]
|
|
local varField = entry[3]
|
|
local varValue = entry[4]
|
|
|
|
local matches = true
|
|
if varField then
|
|
-- Check specific arg matches variant value
|
|
-- For swing flags (bitfield), check if the bit is set
|
|
if subevent == "SWING_DAMAGE" and varField == 10 then
|
|
-- flags is arg10, check bitfield
|
|
local flags = args[varField] or 0
|
|
if varValue == 1 then matches = (math.mod(flags, 2) == 1) -- glancing
|
|
elseif varValue == 2 then matches = (math.mod(math.floor(flags / 2), 2) == 1) -- crushing
|
|
elseif varValue == 4 then matches = (math.mod(math.floor(flags / 4), 2) == 1) -- offhand
|
|
else matches = false end
|
|
else
|
|
matches = (args[varField] == varValue)
|
|
end
|
|
end
|
|
|
|
if matches then
|
|
seen[idx] = true
|
|
seenCount = seenCount + 1
|
|
local formatter = argFormatters[subevent]
|
|
if formatter then
|
|
seenArgs[idx] = formatter(arg4, arg5, arg6, arg7, arg8, arg9, arg10)
|
|
else
|
|
seenArgs[idx] = ""
|
|
end
|
|
any_new = true
|
|
|
|
-- Chat output (first occurrence of this variant)
|
|
local chatHandler = chatHandlers[subevent]
|
|
if chatHandler then
|
|
chatHandler(arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if any_new then
|
|
updateCounter()
|
|
updateRows()
|
|
end
|
|
end)
|
|
|
|
-- ============================================================================
|
|
-- Slash command
|
|
-- ============================================================================
|
|
|
|
SLASH_DPSLOG1 = "/dpslog"
|
|
SlashCmdList["DPSLOG"] = function(msg)
|
|
if main:IsVisible() then
|
|
main:Hide()
|
|
else
|
|
main:Show()
|
|
end
|
|
end
|
|
|
|
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00DPSLog Tracker|r loaded — /dpslog to toggle window")
|