dpslog: WotLK CLEU parity — overkill, overheal, spell names, unit names, GetSpellInfo

Full WotLK 3.3.5 COMBAT_LOG_EVENT_UNFILTERED layout parity:

Event structure:
- Base: subevent, sourceGUID, sourceName, destGUID, destName
- Spell prefix: spellId, spellName, spellSchool
- All suffix field orders match WotLK (blocked before absorbed, etc.)

New fields:
- overkill (-1 if alive) on all _DAMAGE events
- overheal on SPELL_HEAL and SPELL_PERIODIC_HEAL
- sourceName/destName via getNameByGUID (name cache 0x55F080)
- spellName via SpellRec+0x1E0+locale*4 (verified from SuperWoW)
- glancing/crushing as separate booleans (was packed flags)
- amountMissed field (0 — downstream hook lacks amount)

New hooks:
- ProcessStandardPowerGainMessage (0x62CA00) for SPELL_ENERGIZE with
  actual amount and powerType from SMSG_SPELLENERGIZELOG (opcode 0x151)
- Periodic energize: divide by power display factor (rage/10, happiness/1000)

Leech/drain reclassification:
- Drain Life routed through SPELL_PERIODIC_LEECH (aura 53 check in spell DB)
- SPELL_HEAL suppressed for leech spells (covered by leech event)
- Power leech (aura 64): gainMultiplier read as f32, >0 = LEECH, ==0 = DRAIN
- _LEECH/_DRAIN suffix: amount, powerType(-2=health), extraAmount

Lua API:
- GetSpellInfo(spellId) -> name, rank, icon, castTime, minRange, maxRange, spellId
  Matches WotLK returns. DBC addresses verified from nampower + Ghidra:
  SpellRange.dbc (0xC0D79C), SpellIcon.dbc (0xC0D7EC),
  SpellCastTimes.dbc (0xC0D878). SpellRec offsets from nampower struct.

Reference docs:
- WOTLK_CLEU_SPEC.md: full suffix spec from Blizzard_CombatLog.lua + Skada
- WARDEN_CONCERNS.md: hook risk analysis and remaining parity gaps
This commit is contained in:
MarcelineVQ
2026-03-18 00:08:42 -07:00
parent 76f0691540
commit 56f5c0405d
7 changed files with 1212 additions and 450 deletions
+141
View File
@@ -0,0 +1,141 @@
# DPSLog Warden Concerns
Hooks and patterns that could trigger Warden false positives. Our addon is legitimate
(combat log enhancement), but we should understand the detection surface to reduce
headaches for server admins reviewing client modifications.
## What Warden Covers
Warden is Blizzard's anti-cheat system. On vanilla private servers, implementations vary
(some use stock mangos Warden, some extend it, some disable it). The main detection
vectors are:
1. **Memory scanning**: Warden reads specific addresses and compares against known-good
values. Patched bytes at scanned addresses trigger detection.
2. **Module scanning**: Warden enumerates loaded DLLs and checks against blocklists
(by name or hash). Our DLL loads via injection, so it shows up in the module list.
3. **Window enumeration**: Checks for known cheat tool windows (not relevant to us).
4. **Driver checks**: Checks for known cheat drivers (not relevant).
5. **Timing checks**: Detects speed hacks via tick count validation (not relevant).
### What We Should Avoid
- **Patching packet handler dispatch tables**: The opcode-to-handler mapping tables are
commonly scanned. Modifying entries there is high risk.
- **Patching Warden itself**: Obvious detection surface; we don't touch it.
- **Known DLL names**: Some servers blocklist specific DLL names. Our DLL name
("weirdutils") is not on any known list.
- **Hooking at addresses that overlap with known Warden scan points**: If Warden reads
the first N bytes of a function and we've overwritten them with a JMP, it detects the
modification. We use zhook's Detour which patches the function prologue.
### What's Generally Safe
- **Calling game functions** via indirect CALL: No memory modification, just normal
function calls. GetSpellNameById, getNameByGUID, etc.
- **Reading game memory**: Simple pointer dereferences. Descriptor reads, object manager
traversal, aura slot reads.
- **Hooking internal processing functions** (not packet handlers): These are deep inside
the call chain, less likely to be scanned.
- **Registering custom Lua events**: We modify the event count but don't patch any
Warden-monitored jump tables.
## Current Hook Strategy
We hook **downstream processing functions** (0x62xxxx range) rather than packet handlers
(0x5Exxxx range) where possible. Downstream hooks are lower risk because:
- They're internal client functions, not dispatch table entries
- Warden scans typically check packet handler jump tables
- The downstream functions are called after packet validation
## Hooks That Are Packet Handlers (Higher Risk)
| Address | Function | Why packet-level |
|---------|----------|-----------------|
| 0x5E85E0 | SpellNonMeleeDmgLogHandler | Downstream loses school field; no viable alternative |
| 0x626DD0 | PeriodicAuraLogHandler | IS the handler; no downstream exists |
| 0x6255B0 | MeleeDispatcher | Shared handler for opcode range 0x143-0x14A |
| 0x6E7640 | SpellStartHandler | SPELL_CAST_START/SUCCESS |
| 0x6E7330 | CastResultHandler | SPELL_CAST_FAILED |
**Mitigation**: These are all registered via `setClientIndexedPointers` (0x5AB650), not
in a flat dispatch table. Warden typically scans flat opcode tables, not the indexed
handler registration system. Risk is moderate, not high.
## Hooks That Are Safe (Downstream)
| Address | Function | Subevent |
|---------|----------|----------|
| 0x62C770 | ProcessSpellPowerDrainMessage | SPELL_HEAL |
| 0x62BAB0 | ProcessSpellCombatResult | SPELL_MISSED |
| 0x62CA20 | ProcessSpellDrainEffectMessage | DAMAGE_SHIELD |
| 0x626A10 | DisplaySpellInterruptMessage | SPELL_INTERRUPT |
| 0x62CBE0 | ProcessInstaKillSpellMessage | SPELL_INSTAKILL |
| 0x62D9F0 | (extra attacks handler) | SPELL_EXTRA_ATTACKS |
| 0x62AAC0 | ProcessEnvironmentalDamage | ENVIRONMENTAL_DAMAGE |
| 0x628890 | PartyKillLogHandler | PARTY_KILL |
| 0x605860 | HandleUnitDeath | UNIT_DIED |
| 0x6123F0 | SetSpellTarget | SPELL_AURA_APPLIED |
| 0x612320 | CastSpell (aura remove) | SPELL_AURA_REMOVED |
| 0x628C20 | ProcessMultipleSpellInterrupts | SPELL_DISPEL_FAILED |
| 0x6E75F0 | HandleSpellInterruptUpdate | SPELL_CAST_FAILED (others) |
| 0x62CA00 | ProcessStandardPowerGainMessage | SPELL_ENERGIZE |
## Event Registration
We register COMBAT_LOG_EVENT at slot 549 by hooking FrameScript_CreateEvents (0x703D90)
and bumping the event count. This modifies the event table size but doesn't patch any
Warden-monitored code.
## Memory Reads
All descriptor reads (health, power, aura slots) use `readMem` which is a simple
pointer dereference -- no API hooking involved.
## GetSpellNameById (0x6264B0) / getNameByGUID (0x55F080)
Called via `hook.call` (indirect CALL instruction). Not hooked/patched -- we just call
them as normal functions. No Warden concern.
## Remaining WotLK Parity Gaps
Features not yet implemented, with notes on Warden implications:
### amountMissed (partial absorb/block/resist on misses)
The downstream hook ProcessSpellCombatResult (0x62BAB0) doesn't receive the miss amount.
Options:
1. Hook SMSG_SPELLLOGMISS packet handler (0x5E7E00) -- **higher Warden risk**
2. Access the CDS buffer from the downstream hook -- needs investigation, the buffer
may still be readable if the packet handler hasn't cleaned it up
3. Track state: when we see a partial absorb/block in a damage event that also has a
miss component, carry the amount forward
**Recommendation**: Option 2 first (no new hooks). If CDS is stale, option 3.
### sourceFlags / destFlags (COMBATLOG_OBJECT_* bitfield)
Constructable from GUID type bits and descriptor flags. No new hooks needed -- pure
memory reads. Requires defining the WotLK COMBATLOG_OBJECT_* constants and mapping
vanilla unit types to them.
### Boolean 1/nil (critical, glancing, crushing)
SignalEventParam always pushes numbers via `%d`. To push nil for false booleans, we'd
need to bypass SignalEventParam and push args directly onto the Lua stack using
`lua_pushnil` / `lua_pushnumber`. Requires finding the Lua state pointer and calling
Lua C API functions directly. No Warden concern (just function calls), but invasive
change to the event-push mechanism.
### Interrupter / Dispeller / Damage Shield spell IDs
Solvable with a cast tracking ring buffer -- no new hooks. On SPELL_CAST_SUCCESS, record
(casterGUID, spellId, timestamp) for interrupt-type, dispel-type, and damage-shield-type
spells. When the corresponding event fires, look up the most recent matching cast by the
same caster. Pure memory tracking, no Warden concern.
### SPELL_ENERGIZE amount (RESOLVED)
Now hooked via ProcessStandardPowerGainMessage (0x62CA00), downstream of
SMSG_SPELLENERGIZELOG (opcode 0x151). Receives spellId, powerType, and display amount
directly. Safe downstream hook -- no Warden concern.
+201
View File
@@ -0,0 +1,201 @@
# WotLK 3.3.5 COMBAT_LOG_EVENT_UNFILTERED — Full Arg Specification
Sources:
- `Blizzard_CombatLog.lua` from tekkub/wow-ui-source 3.3.5 branch (authoritative)
- Validated against `bkader/Skada-WoTLK` parser (Core/Functions.lua suffix tables)
## Base Args (all events)
| Pos | Field | Type |
|-----|-------|------|
| 1 | timestamp | number |
| 2 | subevent | string |
| 3 | sourceGUID | string |
| 4 | sourceName | string |
| 5 | sourceFlags | number |
| 6 | destGUID | string |
| 7 | destName | string |
| 8 | destFlags | number |
After base args, events have prefix args then suffix args.
## Prefixes
**Spell prefix** (SPELL_, RANGE_, SPELL_PERIODIC_):
- spellId (number)
- spellName (string)
- spellSchool (number)
**Environmental prefix** (ENVIRONMENTAL_):
- environmentalType (string) — "Drowning", "Falling", "Fatigue", "Fire", "Lava", "Slime"
**Swing prefix** (SWING_):
- (none)
**Inline prefix** (DAMAGE_SHIELD, DAMAGE_SHIELD_MISSED, DAMAGE_SPLIT):
- spellId, spellName, spellSchool inlined before suffix (not a standard prefix)
## Suffixes
### _DAMAGE
Used by: SPELL_DAMAGE, RANGE_DAMAGE, SPELL_PERIODIC_DAMAGE, DAMAGE_SHIELD, DAMAGE_SPLIT,
SWING_DAMAGE, ENVIRONMENTAL_DAMAGE
| # | Field | Type | Notes |
|---|-------|------|-------|
| 1 | amount | number | damage dealt |
| 2 | overkill | number | -1 if target alive |
| 3 | school | number | damage school bitmask |
| 4 | resisted | number | 0 if none |
| 5 | blocked | number | 0 if none |
| 6 | absorbed | number | 0 if none |
| 7 | critical | 1/nil | |
| 8 | glancing | 1/nil | |
| 9 | crushing | 1/nil | |
### _MISSED
Used by: SPELL_MISSED, RANGE_MISSED, SPELL_PERIODIC_MISSED, DAMAGE_SHIELD_MISSED,
SWING_MISSED
| # | Field | Type | Notes |
|---|-------|------|-------|
| 1 | missType | string | ABSORB, BLOCK, DEFLECT, DODGE, EVADE, IMMUNE, MISS, PARRY, REFLECT, RESIST |
| 2 | amountMissed | number/nil | only for ABSORB, BLOCK, RESIST |
Note: isOffHand was added in a later client version (post-3.3.5), not present in 3.3.5.
### _HEAL
Used by: SPELL_HEAL, SPELL_PERIODIC_HEAL
| # | Field | Type | Notes |
|---|-------|------|-------|
| 1 | amount | number | total heal |
| 2 | overhealing | number | overheal amount |
| 3 | absorbed | number | heal absorb (e.g. Necrotic Aura) |
| 4 | critical | 1/nil | |
### _ENERGIZE
Used by: SPELL_ENERGIZE, SPELL_PERIODIC_ENERGIZE
| # | Field | Type | Notes |
|---|-------|------|-------|
| 1 | amount | number | power gained |
| 2 | powerType | number | 0=mana, 1=rage, 2=focus, 3=energy, 4=combo, 5=runes, 6=runic |
### _DRAIN
Used by: SPELL_DRAIN, SPELL_PERIODIC_DRAIN
| # | Field | Type | Notes |
|---|-------|------|-------|
| 1 | amount | number | amount drained |
| 2 | powerType | number | power type |
| 3 | extraAmount | number | amount gained by drainer (typically 0) |
### _LEECH
Used by: SPELL_LEECH, SPELL_PERIODIC_LEECH
| # | Field | Type | Notes |
|---|-------|------|-------|
| 1 | amount | number | amount leeched |
| 2 | powerType | number | -2=health, 0=mana, etc. |
| 3 | extraAmount | number | amount gained by leecher |
### _INTERRUPT
Used by: SPELL_INTERRUPT
| # | Field | Type | Notes |
|---|-------|------|-------|
| 1 | extraSpellId | number | interrupted spell |
| 2 | extraSpellName | string | |
| 3 | extraSchool | number | |
### _DISPEL
Used by: SPELL_DISPEL
| # | Field | Type | Notes |
|---|-------|------|-------|
| 1 | extraSpellId | number | dispelled aura |
| 2 | extraSpellName | string | |
| 3 | extraSchool | number | |
| 4 | auraType | string | "BUFF" or "DEBUFF" |
### _STOLEN
Used by: SPELL_STOLEN (Spellsteal)
Same as _DISPEL: extraSpellId, extraSpellName, extraSchool, auraType
### _DISPEL_FAILED
Used by: SPELL_DISPEL_FAILED
| # | Field | Type | Notes |
|---|-------|------|-------|
| 1 | extraSpellId | number | aura that resisted |
| 2 | extraSpellName | string | |
| 3 | extraSchool | number | |
### _EXTRA_ATTACKS
Used by: SPELL_EXTRA_ATTACKS
| # | Field | Type | Notes |
|---|-------|------|-------|
| 1 | amount | number | number of extra attacks |
### _AURA_APPLIED / _AURA_REMOVED / _AURA_REFRESH
Used by: SPELL_AURA_APPLIED, SPELL_AURA_REMOVED, SPELL_AURA_REFRESH
| # | Field | Type | Notes |
|---|-------|------|-------|
| 1 | auraType | string | "BUFF" or "DEBUFF" |
### _AURA_APPLIED_DOSE / _AURA_REMOVED_DOSE
| # | Field | Type | Notes |
|---|-------|------|-------|
| 1 | auraType | string | "BUFF" or "DEBUFF" |
| 2 | amount | number | stack count |
### _AURA_BROKEN
| # | Field | Type | Notes |
|---|-------|------|-------|
| 1 | auraType | string | "BUFF" or "DEBUFF" |
### _AURA_BROKEN_SPELL
| # | Field | Type | Notes |
|---|-------|------|-------|
| 1 | extraSpellId | number | breaking spell |
| 2 | extraSpellName | string | |
| 3 | extraSchool | number | |
| 4 | auraType | string | "BUFF" or "DEBUFF" |
### No-suffix events
_CAST_START, _CAST_SUCCESS, _INSTAKILL, _SUMMON, _RESURRECT, _CREATE:
(no suffix args — spell prefix only)
### _CAST_FAILED
| # | Field | Type | Notes |
|---|-------|------|-------|
| 1 | failedType | string | localized failure reason |
### UNIT_DIED, PARTY_KILL, UNIT_DESTROYED
(base args only, no prefix, no suffix)
## Power Types
0=Mana, 1=Rage, 2=Focus, 3=Energy, 4=Combo Points, 5=Runes, 6=Runic Power, -2=Health
## School Bitmask
1=Physical, 2=Holy, 4=Fire, 8=Nature, 16=Frost, 32=Shadow, 64=Arcane
## Notes
- overkill added in WotLK (not in TBC)
- overhealing on _HEAL added in 3.2/3.3
- absorbed on _HEAL (heal absorb) added in 3.3
- Boolean fields are 1 or nil, not true/false
- DAMAGE_SPLIT exists as a distinct subevent, uses inline prefix + _DAMAGE suffix
- DAMAGE_SHIELD and DAMAGE_SHIELD_MISSED use inline prefix (not standard spell prefix)
- spellName is part of the prefix; our vanilla DLL omits it (addon can look up from spellId)
- hideCaster was added in 4.1.0 (Cata), NOT present in WotLK 3.3.5
- Skada aliases DAMAGE_SHIELD and DAMAGE_SPLIT to SPELL_DAMAGE layout (prefix+suffix)
- Skada reads _AURA_APPLIED/REMOVED/REFRESH with (auraType, amount) — amount is nil for non-dose
+341 -293
View File
@@ -1,42 +1,29 @@
-- DPS Log Tracker — popup checklist for verifying all 35 COMBAT_LOG_EVENT subevents
-- DPS Log Tracker -- WotLK CLEU parity for vanilla 1.12.1
--
-- All events share: arg1=subevent, arg2=sourceGUID, arg3=destGUID
-- Remaining args are subevent-specific (see layouts below).
-- Event layout matches WotLK 3.3.5 COMBAT_LOG_EVENT_UNFILTERED (see WOTLK_CLEU_SPEC.md).
-- Base: arg1=subevent, arg2=sourceGUID, arg3=sourceName, arg4=destGUID, arg5=destName
-- Spell prefix: spellId, spellName, spellSchool
-- Env prefix: envType (string)
-- Swing prefix: (none)
-- ============================================================================
-- Reference tables
-- Reference
-- ============================================================================
-- 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)
-- School bitmask: 1=Physical, 2=Holy, 4=Fire, 8=Nature, 16=Frost, 32=Shadow, 64=Arcane
-- Power types: 0=Mana, 1=Rage, 2=Focus, 3=Energy, 4=Combo, -2=Health
-- Miss types: MISS, DODGE, PARRY, BLOCK, EVADE, IMMUNE, DEFLECT, RESIST, ABSORB, REFLECT
-- Aura types: BUFF, DEBUFF
-- Env types: EXHAUSTED, DROWNING, FALLING, LAVA, SLIME, FIRE
-- 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 format = string.format
local function schoolName(school)
if school == 1 then return "Physical"
elseif school == 2 then return "Holy"
@@ -53,68 +40,118 @@ local function s(v)
return tostring(v)
end
local function displayName(name, guid)
if name and name ~= "" then return name end
return guid or "?"
end
-- ============================================================================
-- Subevent definitions — ordered list of all 34
-- Arg position constants -- WotLK CLEU parity
-- ============================================================================
-- 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)
-- Base: arg1=subevent, arg2=srcGUID, arg3=srcName, arg4=dstGUID, arg5=dstName
-- Spell prefix events: arg6=spellId, arg7=spellName, arg8=spellSchool
-- Suffix starts at arg9 for spell-prefix events
-- Swing events: suffix starts at arg6 (no prefix)
-- Env events: arg6=envType, suffix starts at arg7
-- _DAMAGE suffix (9 fields): amount, overkill, school, resisted, blocked, absorbed, critical, glancing, crushing
-- Spell damage: arg6-8=prefix, arg9-17=suffix
-- Swing damage: arg6-14=suffix
-- Env damage: arg6=envType, arg7-15=suffix
-- _MISSED suffix: missType, amountMissed
-- Spell missed: arg6-8=prefix, arg9=missType, arg10=amountMissed
-- Swing missed: arg6=missType, arg7=amountMissed
-- _HEAL suffix: amount, overheal, absorbed, critical
-- arg6-8=prefix, arg9-12=suffix
-- _ENERGIZE suffix: amount, powerType
-- arg6-8=prefix, arg9-10=suffix
-- _LEECH/_DRAIN suffix: amount, powerType, extraAmount
-- arg6-8=prefix, arg9-11=suffix
-- _EXTRA_ATTACKS suffix: amount
-- arg6-8=prefix, arg9=amount
-- _INTERRUPT/_DISPEL_FAILED suffix: extraSpellId, extraSpellName, extraSchool
-- arg6-8=prefix, arg9-11=suffix
-- _DISPEL/_STOLEN/_AURA_BROKEN_SPELL suffix: extraSpellId, extraSpellName, extraSchool, auraType
-- arg6-8=prefix, arg9-12=suffix
-- _AURA_APPLIED/REMOVED/REFRESH/BROKEN: auraType
-- arg6-8=prefix, arg9=auraType
-- _AURA_DOSE: auraType, amount
-- arg6-8=prefix, arg9=auraType, arg10=amount
-- _CAST_FAILED: failedType
-- arg6-8=prefix, arg9=failedType
-- ============================================================================
-- Subevent definitions -- variant checklist
-- ============================================================================
-- { displayName, subevent, [variantField], [variantValue] }
local SUBEVENTS = {
-- Damage
-- Damage (spell prefix: arg9=amount ... arg15=critical, arg16=glancing, arg17=crushing)
{ "SPELL_DAMAGE", "SPELL_DAMAGE" },
{ "SPELL_DAMAGE (crit)", "SPELL_DAMAGE", 10, 1 },
{ "SPELL_DAMAGE (crit)", "SPELL_DAMAGE", 15, 1 },
{ "RANGE_DAMAGE", "RANGE_DAMAGE" },
{ "RANGE_DAMAGE (crit)", "RANGE_DAMAGE", 10, 1 },
{ "RANGE_DAMAGE (crit)", "RANGE_DAMAGE", 15, 1 },
{ "SPELL_PERIODIC_DAMAGE", "SPELL_PERIODIC_DAMAGE" },
{ "DAMAGE_SHIELD", "DAMAGE_SHIELD" },
{ "DAMAGE_SPLIT", "DAMAGE_SPLIT" },
-- Swing damage: arg6=amount ... arg12=critical, arg13=glancing, arg14=crushing
{ "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" }, -- twow does not implement fatigue damage
-- 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" },
{ "SWING_DAMAGE (crit)", "SWING_DAMAGE", 12, 1 },
{ "SWING_DAMAGE (glancing)", "SWING_DAMAGE", 13, 1 },
{ "SWING_DAMAGE (crushing)", "SWING_DAMAGE", 14, 1 },
-- Env damage: arg6=envType, arg7=amount ... arg15=crushing
{ "ENVIRONMENTAL (DROWNING)", "ENVIRONMENTAL_DAMAGE", 6, "DROWNING" },
{ "ENVIRONMENTAL (FALLING)", "ENVIRONMENTAL_DAMAGE", 6, "FALLING" },
{ "ENVIRONMENTAL (LAVA)", "ENVIRONMENTAL_DAMAGE", 6, "LAVA" },
{ "ENVIRONMENTAL (SLIME)", "ENVIRONMENTAL_DAMAGE", 6, "SLIME" },
{ "ENVIRONMENTAL (FIRE)", "ENVIRONMENTAL_DAMAGE", 6, "FIRE" },
{ "ENVIRONMENTAL (EXHAUSTED)", "ENVIRONMENTAL_DAMAGE", 6, "EXHAUSTED" }, -- twow: no fatigue
-- Misses -- swing: arg6=missType
{ "SWING_MISSED (MISS)", "SWING_MISSED", 6, "MISS" },
{ "SWING_MISSED (DODGE)", "SWING_MISSED", 6, "DODGE" },
{ "SWING_MISSED (PARRY)", "SWING_MISSED", 6, "PARRY" },
{ "SWING_MISSED (BLOCK)", "SWING_MISSED", 6, "BLOCK" },
{ "SWING_MISSED (EVADE)", "SWING_MISSED", 6, "EVADE" },
{ "SWING_MISSED (IMMUNE)", "SWING_MISSED", 6, "IMMUNE" },
{ "SWING_MISSED (RESIST)", "SWING_MISSED", 6, "RESIST" },
{ "SWING_MISSED (ABSORB)", "SWING_MISSED", 6, "ABSORB" },
-- Misses -- spell: arg9=missType (after prefix)
{ "SPELL_MISSED (MISS)", "SPELL_MISSED", 9, "MISS" },
{ "SPELL_MISSED (DODGE)", "SPELL_MISSED", 9, "DODGE" },
{ "SPELL_MISSED (PARRY)", "SPELL_MISSED", 9, "PARRY" },
{ "SPELL_MISSED (IMMUNE)", "SPELL_MISSED", 9, "IMMUNE" },
{ "SPELL_MISSED (RESIST)", "SPELL_MISSED", 9, "RESIST" },
{ "SPELL_MISSED (ABSORB)", "SPELL_MISSED", 9, "ABSORB" },
{ "SPELL_MISSED (DEFLECT)", "SPELL_MISSED", 9, "DEFLECT" }, -- dead in vanilla
{ "SPELL_MISSED (REFLECT)", "SPELL_MISSED", 9, "REFLECT" },
{ "RANGE_MISSED", "RANGE_MISSED" },
{ "SPELL_PERIODIC_MISSED", "SPELL_PERIODIC_MISSED" },
{ "DAMAGE_SHIELD_MISSED", "DAMAGE_SHIELD_MISSED" },
-- Heals
-- Heals: arg9=amount, arg10=overheal, arg11=absorbed, arg12=critical
{ "SPELL_HEAL", "SPELL_HEAL" },
{ "SPELL_HEAL (crit)", "SPELL_HEAL", 8, 1 },
{ "SPELL_HEAL (crit)", "SPELL_HEAL", 12, 1 },
{ "SPELL_PERIODIC_HEAL", "SPELL_PERIODIC_HEAL" },
-- Power
{ "SPELL_ENERGIZE (Mana)", "SPELL_ENERGIZE" }, -- amount=0 from hook, can't distinguish
-- Power: arg9=amount, arg10=powerType
{ "SPELL_ENERGIZE", "SPELL_ENERGIZE" },
{ "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" },
-- Auras: arg9=auraType
{ "SPELL_AURA_APPLIED (BUFF)", "SPELL_AURA_APPLIED", 9, "BUFF" },
{ "SPELL_AURA_APPLIED (DEBUFF)", "SPELL_AURA_APPLIED", 9, "DEBUFF" },
{ "SPELL_AURA_REMOVED (BUFF)", "SPELL_AURA_REMOVED", 9, "BUFF" },
{ "SPELL_AURA_REMOVED (DEBUFF)", "SPELL_AURA_REMOVED", 9, "DEBUFF" },
{ "SPELL_AURA_APPLIED_DOSE", "SPELL_AURA_APPLIED_DOSE" },
{ "SPELL_AURA_REMOVED_DOSE", "SPELL_AURA_REMOVED_DOSE" },
{ "SPELL_AURA_REFRESH", "SPELL_AURA_REFRESH" },
@@ -124,7 +161,7 @@ local SUBEVENTS = {
{ "SPELL_CAST_START", "SPELL_CAST_START" },
{ "SPELL_CAST_SUCCESS", "SPELL_CAST_SUCCESS" },
{ "SPELL_CAST_FAILED", "SPELL_CAST_FAILED" },
-- Misc spell
-- Misc
{ "SPELL_INTERRUPT", "SPELL_INTERRUPT" },
{ "SPELL_DISPEL", "SPELL_DISPEL" },
{ "SPELL_DISPEL_FAILED", "SPELL_DISPEL_FAILED" },
@@ -140,9 +177,7 @@ local SUBEVENTS = {
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, ... }
local subeventIndices = {}
for i = 1, NUM_SUBEVENTS do
local name = SUBEVENTS[i][2]
if not subeventIndices[name] then
@@ -151,254 +186,295 @@ for i = 1, NUM_SUBEVENTS do
table.insert(subeventIndices[name], i)
end
-- Tracking state
local seen = {} -- [index] = true
local seenArgs = {} -- [index] = formatted arg string
local seen = {}
local seenArgs = {}
local seenCount = 0
-- ============================================================================
-- Arg formatters per subevent (return a short summary string)
-- Arg formatters (summary string for tracker display)
-- ============================================================================
-- Formatters receive args starting from arg6 (after base: sub, srcGUID, srcName, dstGUID, dstName)
local function fmtSpellDmg(spellId, amount, school, resisted, absorbed, blocked, critical)
-- Spell-prefix damage: spellId, spellName, spellSchool, amount, overkill, school, resisted, blocked, absorbed, critical, glancing, crushing
local function fmtSpellDmg(spellId, spellName, spellSchool, amount, overkill, school, resisted, blocked, absorbed, critical, glancing, crushing)
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)
local ok = (overkill and overkill >= 0) and format(" OK=%s", s(overkill)) or ""
local g = (glancing == 1) and " GLANC" or ""
local cr = (crushing == 1) and " CRUSH" or ""
return format("[%s] %s amt=%s %s res=%s blk=%s abs=%s%s%s%s%s",
s(spellId), s(spellName), s(amount), schoolName(school or 0), s(resisted), s(blocked), s(absorbed), c, ok, g, cr)
end
local function fmtSwingDmg(amount, school, resisted, absorbed, blocked, critical, flags)
-- Swing damage: amount, overkill, school, resisted, blocked, absorbed, critical, glancing, crushing
local function fmtSwingDmg(amount, overkill, school, resisted, blocked, absorbed, critical, glancing, crushing)
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)
local ok = (overkill and overkill >= 0) and format(" OK=%s", s(overkill)) or ""
local g = (glancing == 1) and " GLANC" or ""
local cr = (crushing == 1) and " CRUSH" or ""
return format("amt=%s %s res=%s blk=%s abs=%s%s%s%s%s",
s(amount), schoolName(school or 0), s(resisted), s(blocked), s(absorbed), c, ok, g, cr)
end
-- Spell prefix helper
local function spellPrefix(spellId, spellName, spellSchool)
return format("[%s] %s %s", s(spellId), s(spellName), schoolName(spellSchool or 0))
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))
-- Spell-prefix damage (args start at a6=spellId)
argFormatters["SPELL_DAMAGE"] = function(...) return fmtSpellDmg(arg[1],arg[2],arg[3],arg[4],arg[5],arg[6],arg[7],arg[8],arg[9],arg[10],arg[11],arg[12]) end
argFormatters["RANGE_DAMAGE"] = argFormatters["SPELL_DAMAGE"]
argFormatters["SPELL_PERIODIC_DAMAGE"] = argFormatters["SPELL_DAMAGE"]
argFormatters["DAMAGE_SHIELD"] = argFormatters["SPELL_DAMAGE"]
argFormatters["DAMAGE_SPLIT"] = argFormatters["SPELL_DAMAGE"]
-- Swing damage (no prefix, args start at a6=amount)
argFormatters["SWING_DAMAGE"] = function(...) return fmtSwingDmg(arg[1],arg[2],arg[3],arg[4],arg[5],arg[6],arg[7],arg[8],arg[9]) end
-- Env damage: a6=envType, a7-a15=damage suffix
argFormatters["ENVIRONMENTAL_DAMAGE"] = function(...)
local ok = (arg[3] and arg[3] >= 0) and format(" OK=%s", s(arg[3])) or ""
return format("env=%s amt=%s %s abs=%s%s", s(arg[1]), s(arg[2]), schoolName(arg[4] or 0), s(arg[7]), ok)
end
-- Pattern B — swing missed
argFormatters["SWING_MISSED"] = function(a4) return s(a4) end
-- Swing missed: a6=missType, a7=amountMissed
argFormatters["SWING_MISSED"] = function(...) return format("%s amt=%s", s(arg[1]), s(arg[2])) 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
-- Spell missed: a6-a8=prefix, a9=missType, a10=amountMissed
argFormatters["SPELL_MISSED"] = function(...) return format("%s %s amt=%s", spellPrefix(arg[1],arg[2],arg[3]), s(arg[4]), s(arg[5])) end
argFormatters["SPELL_PERIODIC_MISSED"] = argFormatters["SPELL_MISSED"]
argFormatters["DAMAGE_SHIELD_MISSED"] = argFormatters["SPELL_MISSED"]
argFormatters["RANGE_MISSED"] = argFormatters["SPELL_MISSED"]
-- 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))
-- Spell heal: a6-a8=prefix, a9=amount, a10=overheal, a11=absorbed, a12=critical
argFormatters["SPELL_HEAL"] = function(...)
local c = (arg[7] == 1) and " CRIT" or ""
local oh = (arg[5] and arg[5] > 0) and format(" OH=%s", s(arg[5])) or ""
return format("%s amt=%s abs=%s%s%s", spellPrefix(arg[1],arg[2],arg[3]), s(arg[4]), s(arg[6]), c, oh)
end
argFormatters["SPELL_INTERRUPT"] = function(a4,a5,a6,a7)
return format("spell=%s extra=%s %s", s(a4), s(a6), schoolName(a7 or 0))
argFormatters["SPELL_PERIODIC_HEAL"] = argFormatters["SPELL_HEAL"]
-- Spell energize: a6-a8=prefix, a9=amount, a10=powerType
argFormatters["SPELL_ENERGIZE"] = function(...)
return format("%s amt=%s type=%s", spellPrefix(arg[1],arg[2],arg[3]), s(arg[4]), powerNames[arg[5]] or s(arg[5]))
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))
argFormatters["SPELL_PERIODIC_ENERGIZE"] = argFormatters["SPELL_ENERGIZE"]
-- Spell leech/drain: a6-a8=prefix, a9=amount, a10=powerType, a11=extraAmount
argFormatters["SPELL_PERIODIC_LEECH"] = function(...)
return format("%s amt=%s type=%s gain=%s", spellPrefix(arg[1],arg[2],arg[3]), s(arg[4]), s(arg[5]), s(arg[6]))
end
argFormatters["SPELL_PERIODIC_DRAIN"] = argFormatters["SPELL_PERIODIC_LEECH"]
-- Extra attacks: a6-a8=prefix, a9=amount
argFormatters["SPELL_EXTRA_ATTACKS"] = function(...)
return format("%s amt=%s", spellPrefix(arg[1],arg[2],arg[3]), s(arg[4]))
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
-- Aura applied/removed/refresh/broken: a6-a8=prefix, a9=auraType
argFormatters["SPELL_AURA_APPLIED"] = function(...) return format("%s %s", spellPrefix(arg[1],arg[2],arg[3]), s(arg[4])) end
argFormatters["SPELL_AURA_REMOVED"] = argFormatters["SPELL_AURA_APPLIED"]
argFormatters["SPELL_AURA_REFRESH"] = argFormatters["SPELL_AURA_APPLIED"]
argFormatters["SPELL_AURA_BROKEN"] = argFormatters["SPELL_AURA_APPLIED"]
-- 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
-- Aura dose: a6-a8=prefix, a9=auraType, a10=amount
argFormatters["SPELL_AURA_APPLIED_DOSE"] = function(...) return format("%s %s x%s", spellPrefix(arg[1],arg[2],arg[3]), s(arg[4]), s(arg[5])) end
argFormatters["SPELL_AURA_REMOVED_DOSE"] = argFormatters["SPELL_AURA_APPLIED_DOSE"]
-- Cast: a6-a8=prefix only
argFormatters["SPELL_CAST_START"] = function(...) return spellPrefix(arg[1],arg[2],arg[3]) end
argFormatters["SPELL_CAST_SUCCESS"] = argFormatters["SPELL_CAST_START"]
argFormatters["SPELL_SUMMON"] = argFormatters["SPELL_CAST_START"]
argFormatters["SPELL_RESURRECT"] = argFormatters["SPELL_CAST_START"]
argFormatters["SPELL_INSTAKILL"] = argFormatters["SPELL_CAST_START"]
-- Cast failed: a6-a8=prefix, a9=failedType
argFormatters["SPELL_CAST_FAILED"] = function(...) return format("%s reason=%s", spellPrefix(arg[1],arg[2],arg[3]), s(arg[4])) end
-- Interrupt/dispel_failed: a6-a8=prefix, a9=extraSpellId, a10=extraSpellName, a11=extraSchool
argFormatters["SPELL_INTERRUPT"] = function(...)
return format("%s -> extra=[%s] %s %s", spellPrefix(arg[1],arg[2],arg[3]), s(arg[4]), s(arg[5]), schoolName(arg[6] or 0))
end
argFormatters["SPELL_DISPEL_FAILED"] = argFormatters["SPELL_INTERRUPT"]
-- Dispel/stolen/aura_broken_spell: a6-a8=prefix, a9=extraSpellId, a10=extraSpellName, a11=extraSchool, a12=auraType
argFormatters["SPELL_DISPEL"] = function(...)
return format("%s -> extra=[%s] %s %s %s", spellPrefix(arg[1],arg[2],arg[3]), s(arg[4]), s(arg[5]), schoolName(arg[6] or 0), s(arg[7]))
end
argFormatters["SPELL_AURA_BROKEN_SPELL"] = argFormatters["SPELL_DISPEL"]
-- Base (no args)
argFormatters["UNIT_DIED"] = function() return "" end
argFormatters["PARTY_KILL"] = function() return "" end
argFormatters["UNIT_DESTROYED"] = function() return "" end
-- ============================================================================
-- Chat output handlers (preserved from original)
-- Chat output handlers
-- ============================================================================
-- All chatHandlers receive: (src, srcName, dst, dstName, ...) where ... starts at arg6
local chatHandlers = {}
chatHandlers["SPELL_DAMAGE"] = function(src, dst, spellId, amount, school, resisted, absorbed, blocked, critical)
-- Spell damage: src, srcName, dst, dstName, spellId, spellName, spellSchool, amount, overkill, school, resisted, blocked, absorbed, critical
chatHandlers["SPELL_DAMAGE"] = function(src, srcName, dst, dstName, spellId, spellName, spellSchool, amount, overkill, school, resisted, blocked, absorbed, 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))
local ok = (overkill and overkill >= 0) and format(" (OK %d)", overkill) or ""
DEFAULT_CHAT_FRAME:AddMessage(format("|cffff0000SPELL_DAMAGE|r %s -> %s: %s[%d] for %d %s%s%s", displayName(srcName, src), displayName(dstName, dst), spellName or "", spellId, amount, schoolName(school), crit, ok))
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))
chatHandlers["RANGE_DAMAGE"] = chatHandlers["SPELL_DAMAGE"]
chatHandlers["DAMAGE_SPLIT"] = chatHandlers["SPELL_DAMAGE"]
chatHandlers["SPELL_PERIODIC_DAMAGE"] = function(src, srcName, dst, dstName, spellId, spellName, spellSchool, amount, overkill, school)
DEFAULT_CHAT_FRAME:AddMessage(format("|cffcc0000SPELL_PERIODIC_DAMAGE|r %s -> %s: %s[%d] for %d %s", displayName(srcName, src), displayName(dstName, dst), spellName or "", spellId, amount, schoolName(school)))
end
chatHandlers["SWING_DAMAGE"] = function(src, dst, amount, school, resisted, absorbed, blocked, critical, flags)
chatHandlers["DAMAGE_SHIELD"] = function(src, srcName, dst, dstName, spellId, spellName, spellSchool, amount, overkill, school)
DEFAULT_CHAT_FRAME:AddMessage(format("|cffff4400DAMAGE_SHIELD|r %s -> %s: %d %s", displayName(srcName, src), displayName(dstName, dst), amount, schoolName(school)))
end
-- Swing damage: src, srcName, dst, dstName, amount, overkill, school, resisted, blocked, absorbed, critical, glancing, crushing
chatHandlers["SWING_DAMAGE"] = function(src, srcName, dst, dstName, amount, overkill, school, resisted, blocked, absorbed, critical, glancing, crushing)
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))
if overkill and overkill >= 0 then extra = extra .. format(" OK=%d", overkill) end
if glancing == 1 then extra = extra .. " GLANCING" end
if crushing == 1 then extra = extra .. " CRUSHING" end
DEFAULT_CHAT_FRAME:AddMessage(format("|cffff0000SWING_DAMAGE|r %s -> %s: %d%s", displayName(srcName, src), displayName(dstName, 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))
-- Swing missed: src, srcName, dst, dstName, missType, amountMissed
chatHandlers["SWING_MISSED"] = function(src, srcName, dst, dstName, missType, amountMissed)
DEFAULT_CHAT_FRAME:AddMessage(format("|cffaaaaaaSWING_MISSED|r %s -> %s: %s", displayName(srcName, src), displayName(dstName, 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))
-- Spell missed: src, srcName, dst, dstName, spellId, spellName, spellSchool, missType, amountMissed
chatHandlers["SPELL_MISSED"] = function(src, srcName, dst, dstName, spellId, spellName, spellSchool, missType, amountMissed)
DEFAULT_CHAT_FRAME:AddMessage(format("|cffaaaaaaSPELL_MISSED|r %s -> %s: %s[%d] %s", displayName(srcName, src), displayName(dstName, dst), spellName or "", 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))
chatHandlers["RANGE_MISSED"] = chatHandlers["SPELL_MISSED"]
chatHandlers["SPELL_PERIODIC_MISSED"] = chatHandlers["SPELL_MISSED"]
chatHandlers["DAMAGE_SHIELD_MISSED"] = chatHandlers["SPELL_MISSED"]
-- Env damage: src, srcName, dst, dstName, envType, amount, overkill, school, resisted, blocked, absorbed
chatHandlers["ENVIRONMENTAL_DAMAGE"] = function(src, srcName, dst, dstName, envType, amount)
DEFAULT_CHAT_FRAME:AddMessage(format("|cffff8800ENVIRONMENTAL_DAMAGE|r %s: %s for %d", displayName(dstName, dst), envType, amount))
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)
-- Spell heal: src, srcName, dst, dstName, spellId, spellName, spellSchool, amount, overheal, absorbed, critical
chatHandlers["SPELL_HEAL"] = function(src, srcName, dst, dstName, spellId, spellName, spellSchool, 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))
local oh = (overheal and overheal > 0) and format(" (OH %d)", overheal) or ""
DEFAULT_CHAT_FRAME:AddMessage(format("|cff00ff00SPELL_HEAL|r %s -> %s: %s[%d] for %d%s%s", displayName(srcName, src), displayName(dstName, dst), spellName or "", spellId, amount, crit, oh))
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))
chatHandlers["SPELL_PERIODIC_HEAL"] = function(src, srcName, dst, dstName, spellId, spellName, spellSchool, amount, overheal)
local oh = (overheal and overheal > 0) and format(" (OH %d)", overheal) or ""
DEFAULT_CHAT_FRAME:AddMessage(format("|cff00cc00SPELL_PERIODIC_HEAL|r %s -> %s: %s[%d] for %d%s", displayName(srcName, src), displayName(dstName, dst), spellName or "", spellId, amount, oh))
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)))
-- Energize: src, srcName, dst, dstName, spellId, spellName, spellSchool, amount, powerType
chatHandlers["SPELL_ENERGIZE"] = function(src, srcName, dst, dstName, spellId, spellName, spellSchool, amount, powerType)
DEFAULT_CHAT_FRAME:AddMessage(format("|cff4488ffSPELL_ENERGIZE|r %s -> %s: %s[%d] +%d %s", displayName(srcName, src), displayName(dstName, dst), spellName or "", spellId, amount, powerNames[powerType] or "?"))
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))
chatHandlers["SPELL_PERIODIC_ENERGIZE"] = function(src, srcName, dst, dstName, spellId, spellName, spellSchool, amount, powerType)
DEFAULT_CHAT_FRAME:AddMessage(format("|cff4488ffSPELL_PERIODIC_ENERGIZE|r %s -> %s: %s[%d] +%d %s", displayName(srcName, src), displayName(dstName, dst), spellName or "", spellId, amount, powerNames[powerType] or "?"))
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))
-- Leech: src, srcName, dst, dstName, spellId, spellName, spellSchool, amount, powerType, extraAmount
chatHandlers["SPELL_PERIODIC_LEECH"] = function(src, srcName, dst, dstName, spellId, spellName, spellSchool, amount, powerType, extraAmount)
DEFAULT_CHAT_FRAME:AddMessage(format("|cff8844ffSPELL_PERIODIC_LEECH|r %s -> %s: %s[%d] drained %d, gained %d", displayName(srcName, src), displayName(dstName, dst), spellName or "", spellId, amount, extraAmount or 0))
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))
-- Drain: src, srcName, dst, dstName, spellId, spellName, spellSchool, amount, powerType, extraAmount
chatHandlers["SPELL_PERIODIC_DRAIN"] = function(src, srcName, dst, dstName, spellId, spellName, spellSchool, amount, powerType, extraAmount)
local pname = powerNames[powerType] or "?"
DEFAULT_CHAT_FRAME:AddMessage(format("|cff8844ffSPELL_PERIODIC_DRAIN|r %s -> %s: %s[%d] drained %d %s", displayName(srcName, src), displayName(dstName, dst), spellName or "", spellId, amount, pname))
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))
-- Aura: src, srcName, dst, dstName, spellId, spellName, spellSchool, auraType
chatHandlers["SPELL_AURA_APPLIED"] = function(src, srcName, dst, dstName, spellId, spellName, spellSchool, auraType)
DEFAULT_CHAT_FRAME:AddMessage(format("|cff8888ffSPELL_AURA_APPLIED|r %s: %s[%d] (%s)", displayName(dstName, dst), spellName or "", spellId, auraType))
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)))
chatHandlers["SPELL_AURA_REMOVED"] = function(src, srcName, dst, dstName, spellId, spellName, spellSchool, auraType)
DEFAULT_CHAT_FRAME:AddMessage(format("|cff6666ccSPELL_AURA_REMOVED|r %s: %s[%d] (%s)", displayName(dstName, dst), spellName or "", spellId, auraType))
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))
chatHandlers["SPELL_AURA_REFRESH"] = function(src, srcName, dst, dstName, spellId, spellName, spellSchool, auraType)
DEFAULT_CHAT_FRAME:AddMessage(format("|cff7777ddSPELL_AURA_REFRESH|r %s: %s[%d] (%s)", displayName(dstName, dst), spellName or "", 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))
chatHandlers["SPELL_AURA_BROKEN"] = function(src, srcName, dst, dstName, spellId, spellName, spellSchool, auraType)
DEFAULT_CHAT_FRAME:AddMessage(format("|cffff8800SPELL_AURA_BROKEN|r %s -> %s: %s[%d] broken (%s)", displayName(srcName, src), displayName(dstName, dst), spellName or "", spellId, 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))
chatHandlers["SPELL_AURA_APPLIED_DOSE"] = function(src, srcName, dst, dstName, spellId, spellName, spellSchool, auraType, stacks)
DEFAULT_CHAT_FRAME:AddMessage(format("|cff8888ffSPELL_AURA_APPLIED_DOSE|r %s: %s[%d] x%d (%s)", displayName(dstName, dst), spellName or "", 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))
chatHandlers["SPELL_AURA_REMOVED_DOSE"] = function(src, srcName, dst, dstName, spellId, spellName, spellSchool, auraType, stacks)
DEFAULT_CHAT_FRAME:AddMessage(format("|cff6666ccSPELL_AURA_REMOVED_DOSE|r %s: %s[%d] x%d (%s)", displayName(dstName, dst), spellName or "", spellId, stacks, 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))
chatHandlers["SPELL_AURA_BROKEN_SPELL"] = function(src, srcName, dst, dstName, spellId, spellName, spellSchool, extraSpellId, extraSpellName, extraSchool, auraType)
DEFAULT_CHAT_FRAME:AddMessage(format("|cffff8800SPELL_AURA_BROKEN_SPELL|r %s -> %s: %s[%d] broken by %s[%d]", displayName(srcName, src), displayName(dstName, dst), spellName or "", spellId, extraSpellName or "", extraSpellId))
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))
-- Cast
chatHandlers["SPELL_CAST_START"] = function(src, srcName, dst, dstName, spellId, spellName, spellSchool)
DEFAULT_CHAT_FRAME:AddMessage(format("|cffffff00SPELL_CAST_START|r %s: %s[%d]", displayName(srcName, src), spellName or "", spellId))
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)))
chatHandlers["SPELL_CAST_SUCCESS"] = function(src, srcName, dst, dstName, spellId, spellName)
DEFAULT_CHAT_FRAME:AddMessage(format("|cffffff00SPELL_CAST_SUCCESS|r %s: %s[%d]", displayName(srcName, src), spellName or "", spellId))
end
chatHandlers["SPELL_CAST_SUCCESS"] = function(src, dst, spellId)
DEFAULT_CHAT_FRAME:AddMessage(format("|cffffff00SPELL_CAST_SUCCESS|r %s: spell %d", src, spellId))
chatHandlers["SPELL_CAST_FAILED"] = function(src, srcName, dst, dstName, spellId, spellName, spellSchool, failedType)
DEFAULT_CHAT_FRAME:AddMessage(format("|cffff4444SPELL_CAST_FAILED|r %s: %s[%d] (%s)", displayName(srcName, src), spellName or "", spellId, failedType))
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))
-- Interrupt: src, srcName, dst, dstName, spellId, spellName, spellSchool, extraSpellId, extraSpellName, extraSchool
chatHandlers["SPELL_INTERRUPT"] = function(src, srcName, dst, dstName, spellId, spellName, spellSchool, extraSpellId, extraSpellName, extraSchool)
DEFAULT_CHAT_FRAME:AddMessage(format("|cffff8800SPELL_INTERRUPT|r %s interrupted %s: %s[%d]", displayName(srcName, src), displayName(dstName, dst), extraSpellName or "", extraSpellId))
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)))
-- Dispel: src, srcName, dst, dstName, spellId, spellName, spellSchool, extraSpellId, extraSpellName, extraSchool, auraType
chatHandlers["SPELL_DISPEL"] = function(src, srcName, dst, dstName, spellId, spellName, spellSchool, extraSpellId, extraSpellName, extraSchool, auraType)
DEFAULT_CHAT_FRAME:AddMessage(format("|cff88ffffSPELL_DISPEL|r %s dispelled %s: %s[%d] (%s)", displayName(srcName, src), displayName(dstName, dst), extraSpellName or "", extraSpellId, auraType))
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)))
-- Dispel failed: src, srcName, dst, dstName, spellId, spellName, spellSchool, extraSpellId, extraSpellName, extraSchool
chatHandlers["SPELL_DISPEL_FAILED"] = function(src, srcName, dst, dstName, spellId, spellName, spellSchool, extraSpellId, extraSpellName, extraSchool)
DEFAULT_CHAT_FRAME:AddMessage(format("|cff888888SPELL_DISPEL_FAILED|r %s -> %s: %s[%d] resisted", displayName(srcName, src), displayName(dstName, dst), extraSpellName or "", extraSpellId))
end
chatHandlers["SPELL_EXTRA_ATTACKS"] = function(src, dst, spellId)
DEFAULT_CHAT_FRAME:AddMessage(format("|cffff8800SPELL_EXTRA_ATTACKS|r %s: spell %d", src, spellId))
-- Extra attacks: src, srcName, dst, dstName, spellId, spellName, spellSchool, amount
chatHandlers["SPELL_EXTRA_ATTACKS"] = function(src, srcName, dst, dstName, spellId, spellName, spellSchool, amount)
DEFAULT_CHAT_FRAME:AddMessage(format("|cffff8800SPELL_EXTRA_ATTACKS|r %s: %s[%d] x%d", displayName(srcName, src), spellName or "", spellId, amount))
end
chatHandlers["SPELL_SUMMON"] = function(src, dst, spellId)
DEFAULT_CHAT_FRAME:AddMessage(format("|cff88ff88SPELL_SUMMON|r %s summoned %s: spell %d", src, dst, spellId))
-- Summon/resurrect/instakill
chatHandlers["SPELL_SUMMON"] = function(src, srcName, dst, dstName, spellId, spellName)
DEFAULT_CHAT_FRAME:AddMessage(format("|cff88ff88SPELL_SUMMON|r %s summoned %s: %s[%d]", displayName(srcName, src), displayName(dstName, dst), spellName or "", 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))
chatHandlers["SPELL_RESURRECT"] = function(src, srcName, dst, dstName, spellId, spellName)
DEFAULT_CHAT_FRAME:AddMessage(format("|cff88ff88SPELL_RESURRECT|r %s resurrected %s: %s[%d]", displayName(srcName, src), displayName(dstName, dst), spellName or "", 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))
chatHandlers["SPELL_INSTAKILL"] = function(src, srcName, dst, dstName, spellId, spellName)
DEFAULT_CHAT_FRAME:AddMessage(format("|cffff0000SPELL_INSTAKILL|r %s killed %s: %s[%d]", displayName(srcName, src), displayName(dstName, dst), spellName or "", spellId))
end
chatHandlers["UNIT_DIED"] = function(src, dst)
DEFAULT_CHAT_FRAME:AddMessage(format("|cff888888UNIT_DIED|r %s", dst))
-- Deaths
chatHandlers["UNIT_DIED"] = function(src, srcName, dst, dstName)
DEFAULT_CHAT_FRAME:AddMessage(format("|cff888888UNIT_DIED|r %s", displayName(dstName, dst)))
end
chatHandlers["PARTY_KILL"] = function(src, dst)
DEFAULT_CHAT_FRAME:AddMessage(format("|cffff8800PARTY_KILL|r %s killed %s", src, dst))
chatHandlers["PARTY_KILL"] = function(src, srcName, dst, dstName)
DEFAULT_CHAT_FRAME:AddMessage(format("|cffff8800PARTY_KILL|r %s killed %s", displayName(srcName, src), displayName(dstName, 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))
chatHandlers["UNIT_DESTROYED"] = function(src, srcName, dst, dstName)
DEFAULT_CHAT_FRAME:AddMessage(format("|cff666666UNIT_DESTROYED|r %s", displayName(dstName, dst)))
end
-- ============================================================================
-- UI — Draggable popup tracker
-- 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 FRAME_HEIGHT = ROW_HEIGHT * VISIBLE_ROWS + 52
local main = CreateFrame("Frame", "DPSLogTracker", UIParent)
main:SetWidth(FRAME_WIDTH)
@@ -419,18 +495,15 @@ 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)
@@ -440,29 +513,24 @@ local function updateCounter()
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
@@ -475,18 +543,18 @@ local function updateRows()
local row = rows[i]
if idx <= NUM_SUBEVENTS then
row:Show()
local displayName = SUBEVENTS[idx][1]
local displayN = 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")
row.nameStr:SetText("|cffffffff" .. displayN .. "|r |cffaaaaaa" .. argStr .. "|r")
else
row.nameStr:SetText("|cffffffff" .. displayName .. "|r")
row.nameStr:SetText("|cffffffff" .. displayN .. "|r")
end
else
row.check:SetText("|cff666666-|r")
row.nameStr:SetText("|cff666666" .. displayName .. "|r")
row.nameStr:SetText("|cff666666" .. displayN .. "|r")
end
else
row:Hide()
@@ -501,7 +569,6 @@ end)
updateRows()
-- Reset button
local resetBtn = CreateFrame("Button", nil, main, "UIPanelButtonTemplate")
resetBtn:SetWidth(60)
resetBtn:SetHeight(18)
@@ -522,27 +589,17 @@ end)
-- Event dispatcher
-- ============================================================================
local function print(msg)
DEFAULT_CHAT_FRAME:AddMessage(msg)
end
-- Expand args table to handle up to 17 args (spell damage has the most: 5 base + 3 prefix + 9 suffix)
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 }
local args = { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10,
arg11, arg12, arg13, arg14, arg15, arg16, arg17 }
-- Check all variant entries for this subevent
local indices = subeventIndices[subevent]
if not indices then return end
@@ -555,18 +612,7 @@ eventFrame:SetScript("OnEvent", function()
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
matches = (args[varField] == varValue)
end
if matches then
@@ -574,16 +620,18 @@ eventFrame:SetScript("OnEvent", function()
seenCount = seenCount + 1
local formatter = argFormatters[subevent]
if formatter then
seenArgs[idx] = formatter(arg4, arg5, arg6, arg7, arg8, arg9, arg10)
-- Pass args starting from arg6 (after base: sub, srcGUID, srcName, dstGUID, dstName)
seenArgs[idx] = formatter(arg6, arg7, arg8, arg9, arg10, arg11, arg12,
arg13, arg14, arg15, arg16, arg17)
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)
chatHandler(arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10,
arg11, arg12, arg13, arg14, arg15, arg16, arg17)
end
end
end
@@ -608,4 +656,4 @@ SlashCmdList["DPSLOG"] = function(msg)
end
end
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00DPSLog Tracker|r loaded — /dpslog to toggle window")
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00DPSLog Tracker|r loaded -- /dpslog to toggle window")
+504 -157
View File
File diff suppressed because it is too large Load Diff
+3
View File
@@ -109,6 +109,9 @@ fn registerLuaFunctions() void {
if (build_opts.transform44) {
registerFunction("SetWeatherOverride", @intFromPtr(&transform44.luaSetWeatherOverride));
}
if (build_opts.dpslog) {
registerFunction("GetSpellInfo", @intFromPtr(&dpslog.luaGetSpellInfo));
}
if (build_opts.addonperf) {
registerFunction("GetAddOnMemoryUsage", @intFromPtr(&addonperf.luaGetAddOnMemoryUsage));
registerFunction("UpdateAddOnMemoryUsage", @intFromPtr(&addonperf.luaUpdateAddOnMemoryUsage));
+6
View File
@@ -162,6 +162,12 @@ pub const FN_CVAR_LOOKUP: usize = 0x0063DEC0;
/// SceneEnd: __thiscall(device). Per-frame hook point.
pub const FN_SCENE_END: usize = 0x5A17A0;
/// RetrieveNPCDataFromCache — __thiscall(ECX=cache_obj), 6 stack params, RET 0x18.
/// Returns char* name or 0 on miss.
pub const FN_NAME_CACHE_LOOKUP: usize = 0x0055F080;
/// Name cache object pointer (global at 0xC0E228).
pub const NAME_CACHE_OBJ: usize = 0x00C0E228;
// =============================================================================
// D3D9 / GxDevice
// =============================================================================
+16
View File
@@ -224,6 +224,22 @@ pub fn getLocalPlayer() u32 {
return getObjectByGUID(guid);
}
/// Resolve a GUID to a unit/NPC name via the name cache.
/// Returns the name string or "" if not cached.
pub fn getNameByGUID(guid: u64) [*:0]const u8 {
if (guid == 0) return "";
const lo: u32 = @truncate(guid);
const hi: u32 = @truncate(guid >> 32);
var name_buf: [2]u32 = .{ 0, 0 };
const result = hook.call(fn (u32, u32, u32, u32, u32, u32, u32) callconv(hook.cc.thiscall) u32, o.FN_NAME_CACHE_LOOKUP, .{
o.NAME_CACHE_OBJ, lo, hi, @intFromPtr(&name_buf), 0, 0, 0,
});
if (result != 0) {
return @ptrFromInt(result);
}
return "";
}
/// Get the current target's GUID.
pub fn getTargetGUID() u64 {
return unitGUID("target");