promotional bannermobile promotional banner

Epic Damage Meter

A lightweight, non-bloated World of Warcraft damage meter designed for accurate real-time tracking and minimal to non existent performance impact.

File Details

EpicDamageMeter v.2.4.28b

  • R
  • Jun 6, 2026
  • 451.68 KB
  • 763
  • 12.0.7+14
  • Classic TBC + 4

File Name

EpicDamageMeter.zip

Supported Versions

  • 12.0.7
  • 12.0.5
  • 12.0.1
  • 5.5.4
  • 5.5.3
  • 4.4.2
  • 4.4.1
  • 3.80.1
  • 3.80.0
  • 3.4.5
  • 3.4.4
  • 2.5.5
  • 2.5.4
  • 1.15.8
  • 1.15.7

EpicDamageMeter - Changelog
============================

Version 2.4.28
--------------
Key Bindings + Report to Chat Fix + Client Updates

KEY BINDINGS (user request from guino2377)
- New configurable key bindings under
  ESC > Options > Keybindings > AddOns > "Epic Damage Meter":
    * Toggle meter window
    * Show meter window
    * Hide meter window
    * Open settings
  Each player assigns their OWN key, so nothing is hardcoded and
  no existing bind is touched — the same approach Details! uses.
- New "Start Hidden" option (Settings > General): keep the meter
  hidden on login/reload and reveal it only with your toggle key.
  This is the "set display to Never, show via keybind" request.
- Implemented via standard Bindings.xml + Core/Bindings.lua
  (localized labels, enUS + deDE). Works on all clients.

REPORT TO CHAT ("can't post data to chat" — user feedback)

REPORT TO CHAT ("can't post data to chat" — user feedback)
- The chat report now honors the segment your window is actually
  showing (Current / Overall / a past fight) instead of always
  reading the live segment. Look at Overall, click report → you
  get the Overall numbers.
- Retail fallback: when the CLEU database has no rows for the
  shown segment (data only lives in Blizzard's C_DamageMeter),
  the report now reads straight from C_DamageMeter — the same
  source the bars render from. Previously this returned "No data
  to report" and nothing got posted.
- SecretValue-safe: source names/amounts that are tainted in PvP
  are skipped rather than throwing.
- Shared code path: new EDM.UI:BuildReportData() is used by both
  the Retail and Classic report commands.

CLIENT VERSION UPDATES
- Mists of Pandaria Classic: 5.5.3 -> 5.5.4 (Interface 50504)
- TBC Anniversary 2.5.5, Retail 12.0.5, Classic Era 1.15.8
  confirmed current. All .toc Version fields bumped to 2.4.28.

PvP Crash Fix: SecretValue Taint in Tooltip Cache Key

THE BUG
- 26x error spam in PvP: "attempt to compare local 'expectedKey'
  (a secret string value, while execution tainted by
  'EpicDamageMeter')" — fired every time a bar tooltip was shown
  on a Paladin (or any player) during an arena/BG match.
- Root cause: GetTooltipSpells and SetBarData both built a
  composite cache key via string.format("%s|%s|%s|%s", tostring(
  actor.guid), ...). On WoW 12.0+ in PvP, actor.guid can be a
  SecretValue. tostring() on a SecretValue returns a SECRET
  STRING, which then made the whole expectedKey secret. The
  subsequent `bar._cachedKey ~= expectedKey` comparison tainted
  Lua execution, throwing the error 26 times in a row.

THE FIX
- New _plain() helper at both cache-key build sites: returns the
  string form only if the value is NOT secret, otherwise returns
  a fallback. Falls back to bar.plainGUID (set for the local
  player) before giving up on "?".
- Comparison hardened: if bar._cachedKey itself is somehow
  secret, treat as cache miss and reset rather than running the
  `~=` compare. Plain == plain compares are safe; only mixed-
  taint compares throw.
- Both sites fixed in one pass: SetBarData (~line 1973) and
  GetTooltipSpells (~line 2934).

WHY THIS NEVER CAUGHT IT IN PvE
- In PvE, actor.guid is always plain. The bug only triggers
  when CLEU is restricted (arena, BG) so the C_DamageMeter path
  feeds the bar with secret GUIDs/names. SecretValues are a
  12.0+ feature; older clients never had this code path.

Version 2.4.27
--------------
Heal Tooltip: Merge Absorbs

THE BUG
- Side-by-side screenshot from the user — Details! showed nine
  healing entries for a Priest, EDM only five. The missing four
  were: "Schnelle Prognose", "Machtwort: Schild" (Power Word:
  Shield — an absorb), "Kosmische Welle" and "Gebet der
  Besserung". User flagged it as "HPS zeigt net alle Werte
  korrekt an".
- Root cause: when fetching the spell breakdown for the healing
  tooltip we only queried `Enum.DamageMeterType.HealingDone`,
  which returns only the actual-heal spells. Absorb-style
  spells (Power Word: Shield, Sacred Veil, etc.) live under
  `Enum.DamageMeterType.Absorbs` and never showed up.

THE FIX
- The Healing / HPS bar tooltip cache build now queries BOTH
  HealingDone AND Absorbs and merges the two spell lists into
  a single table (keyed by spellID).
- After merging, sort the combined list by `totalAmount`
  descending so a big shield doesn't end up below a small heal
  just because the API returned heals first.
- Damage / DPS mode path untouched (single enum, same as before).

NOTE
- The "Blitzheilung 238K vs 102K" amount discrepancy in the user's
  screenshots is most likely because Details! was on a longer /
  different combat segment when the screenshot was taken — the
  per-cell percentages already diverge (43.5% vs 40.2%), so the
  underlying segment totals were different snapshots, not a bug
  in our number. After this fix the COMPLETENESS of the spell
  list matches Details!; absolute amounts only line up when both
  meters are looking at the same segment at the same moment.

Version 2.4.26
--------------
Restore working math from reference v2.4.15

User uploaded a working reference build (v2.4.15) and pointed out
that my recent "improvements" broke the math everywhere. Compared
both trees and reverted the broken logic while keeping the visual
work intact.

REVERTED TO REFERENCE
- Core/Utils.lua restored byte-for-byte. My AbbreviateNumbers-based
  ResolveNumber (v2.4.21+) was over-engineered and returned 0 for
  some SecretValues — that's why the tracker showed "0 (0 DPS)"
  during combat. The reference's simple two-path is back:
    1. Plain numbers → tonumber, done.
    2. SecretValues → pcall'd string.format("%.0f", val) followed
       by pcall'd #length + string.byte byte-rebuild.
- UI/Graph.lua Update reverted to the reference's Strategy 1
  (Database) → Strategy 2 (tracker-fed values) order. No own
  sliding-window math.
- MainFrame UpdateBarsFromDamageMeter Abbr reverted to the
  reference's Details!-style two-path (SecretValue →
  AbbreviateNumbers, plain → Utils.FormatNumber).
- MainFrame graph-publishing reverted: writes `total` (cumulative)
  to _graphDPS like the reference, not a pre-divided rate.

KEPT FROM RECENT WORK
The following visual additions don't touch the math logic and
stay in place:
- Detail Window hero header + stat pills + ranked spell rows
- Settings hero logo + tagline banner + arena theme + version
  footer + ESC menu button
- Tracker title-bar mini hero logo
- Tracker bar gloss + shadow + edge highlight overlays
- Status bar combat-time icon + warm-gold colour + accent stripes
- Empty-combatSources bail (matches reference + Details! pattern;
  removed the synthesis hack)

Lesson learned: when the reference works, copy first, modify
second.

Version 2.4.25
--------------
Revert to Details! reference pattern

User feedback: stop inventing math, follow Details! exactly. I
sourced the relevant Details! code (parser_nocleu1.lua, boot.lua
in Tercioo/Details-Damage-Meter) and reverted the divergences:

  PER-SPELL DPS (was 2.4.24 amount / session.durationSeconds)
- Details reads spell.amountPerSecond DIRECTLY from
  C_DamageMeter (parser_nocleu1.lua:760-770). No own division.
- DetailWindow.ShowSpellList, MainFrame UpdateBars cache build,
  and MainFrame GetTooltipSpells lazy path all reverted to
  reading sp.amountPerSecond and feeding it through Abbr() like
  before.

  EMPTY combatSources (was 2.4.23 local-player synthesis)
- Details bails silently when session.combatSources is nil or
  empty (parser_nocleu1.lua:1859-1866). No synthesised local
  player row, no CLEU fallback.
- UpdateBarsFromDamageMeter reverted to the same behaviour. The
  "tracker shows no data during the SecretValue phase" is a
  Blizzard API artefact — every other meter on 12.0 has the
  same brief gap, and synthesising fake data created downstream
  consistency issues (math mismatches between bar and tooltip).

What this means:
- Numbers will match Details! exactly (since we use the same
  API fields).
- If Blizzard's stock meter shows different numbers, that's
  Blizzard's UI using a different formula — not a bug we should
  paper over with our own derivation.
- Brief no-data window at combat start (under arena/BG
  SecretValue restriction) is expected behaviour now.

Version 2.4.24
--------------
Per-Spell DPS Now Matches Blizzard's Damage Meter

THE PROBLEM
- Side-by-side screenshot from the user: Blizzard's stock meter
  showed "Heilige Pein 40.779 (3.719)", EDM Detail Window showed
  "40.8K (4.3K DPS)" — same spell, same moment, same player.
  Amounts matched (40K), DPS rates differed by ~16%.
- Root cause: we read the per-spell DPS from C_DamageMeter's
  spell.amountPerSecond, which is a different metric than what
  Blizzard's own UI displays. Blizzard's stock meter divides the
  spell's total damage by the session.durationSeconds (a stable
  "since combat start" duration). The API's amountPerSecond
  appears to be either an instantaneous or shorter-window rate,
  which tends to read higher.

THE FIX
- Per-spell DPS in the Detail Window's spell list and in the
  main-window bar tooltip is now computed as
    spell.totalAmount / session.durationSeconds
  exactly like Blizzard's UI does. We fetch session.durationSeconds
  via CallSessionAPI / GetCombatSessionFromType right before the
  spell loop and divide once per spell.
- Applies to:
    DetailWindow.ShowSpellList    (Damage + Healing tabs)
    MainFrame UpdateBarsFromDamageMeter spell-cache build
    MainFrame GetTooltipSpells    lazy-fetch path
- DB segment duration is still used as a fallback for Classic /
  when C_DamageMeter doesn't expose durationSeconds.

Version 2.4.23
--------------
Synthetic-Source Fallback — Bars during SecretValue Combat

THE REGRESSION
- Reported: tracker showed no data during combat and only filled
  in after PLAYER_REGEN_ENABLED. Debug log captured the smoking
  gun: "Synced C_DamageMeter -> segment: dmg=92872 heal=0
  players=0". `dmg` had a real value, but `players` (i.e.
  combatSources) was empty.
- Why: in arenas / BGs / wherever the SecretValue restriction is
  active, Blizzard's session.combatSources is empty during the
  fight. The API still gives you a session.totalAmount (a
  SecretValue you can pass to widgets) but no per-source array.
  UpdateBarsFromDamageMeter hit `if sourceCount == 0 then return
  false end` and fell back to the Database path — which has no
  CLEU-fed data on Retail since 2.4.8 — so the tracker rendered
  empty bars.

THE FIX
- When session.combatSources is empty AND session.totalAmount
  resolves to a non-zero number, synthesise a one-row source
  for the local player:
    name           = UnitName("player")
    classFilename  = the player's class
    sourceGUID     = UnitGUID("player")
    isLocalPlayer  = true
    totalAmount    = session.totalAmount  (still secret, OK —
                                          widgets handle it)
    amountPerSecond = session.amountPerSecond
- The rest of the render pipeline (which already handles
  SecretValues via SetText / SetMinMaxValues) shows the player
  their own bar with the live session total during the fight.
- Marked on the instance via `_syntheticSource` flag so we can
  later add a small "(local view)" hint or skip features that
  need a real multi-source array (left as a TODO).

Version 2.4.22
--------------
Taint-Hardening — Utils.ResolveNumber

CRITICAL FIX
- Fixed: UpdateBarsFromDamageMeter threw repeatedly with
  "attempt to get length of local 'str' (a secret string value,
  while execution tainted by 'EpicDamageMeter')" at Utils.lua:110.
- Root cause: 2.4.21 added an AbbreviateNumbers fast-path for
  SecretValues that called `#str > 0` without wrapping in pcall.
  AbbreviateNumbers returns a tainted string when fed a
  SecretValue, and `#` on a tainted string throws. The error
  fired ~4× per refresh tick, polluting the debug log.
- Rewrote ResolveNumber so EVERY taint-sensitive operation is
  pcall-guarded. The new flow:
    1. tonumber for plain values (fast path)
    2. AbbreviateNumbers (preferred) OR string.format (fallback)
       to get a representation of the SecretValue
    3. string.byte loop (taint-safe — returns plain integers)
       to rebuild a plain Lua string
    4. tonumber on the plain string (handles "12345" form)
       OR Utils.ParseAbbrev (handles "10.5K" / "1.2M" form)
  Nothing in this function can throw past pcall now. Worst case
  is returning 0 if the byte loop can't read any chars.

Version 2.4.21
--------------
Tracker / Graph / Footer Fixes

TRACKER SHOWED "0 (0 DPS)" DURING COMBAT
- Fixed: after the 2.4.11 ResolveNumber tightening, the tracker
  bar displayed "0 (0 DPS)" mid-combat and only filled in once
  PLAYER_REGEN_ENABLED fired. Root cause: forcing every
  SecretValue through the byte-rebuild slow path returned 0
  whenever string.format("%.0f", secretValue) didn't produce a
  parseable string — which on Midnight 12.0 happens for any rate
  that includes a fractional component.
- New strategy: ask Blizzard's AbbreviateNumbers (which handles
  SecretValues correctly) for a clean string like "10.5K", parse
  that back via Utils.ParseAbbrev into a plain number. The
  byte-rebuild path stays as a final fallback only.

GRAPH MIRRORS THE TRACKER NOW
- Per user request: removed the 3-second rolling-window
  calculation added in 2.4.20. The graph just plots whatever
  per-second rate the tracker is currently displaying.
- The tracker now publishes a proper RATE (not the cumulative
  total) on the Retail path too — `total / duration` when the
  display mode is DAMAGE_DONE / HEALING_DONE, or `total` when
  the mode is already DPS / HPS.
- A sample is pushed every Update tick while in combat so the
  line never freezes — even a momentary 0 between bursts is a
  real value worth plotting.

VERSION FOOTER POSITION
- Fixed: the "EpicDM v2.4.20" label sat in mid-air below the
  Reset Data / Reload UI buttons with a big gap. Now anchored
  to the bottom edge of the sidebar with a thin separator above
  it — visually grouped with the action buttons.

Version 2.4.20
--------------
Graph Sliding-Window + Decimal Precision + Stale Tooltip Cache

GRAPH — FLAT LINE BUG
- Fixed: the DPS/HPS graph drew a flat line that never moved during
  combat — every sample showed the same value as the last. Root
  cause: Graph:Update computed `dps = totalDamage / segmentDuration`,
  i.e. the *cumulative average* DPS since combat start. That value
  trends toward a constant the longer the fight runs, so the line
  flattens almost immediately.
- Replaced with a 3-second rolling-window: each tick we record
  (timestamp, cumulative total) into a buffer, drop entries older
  than the window, then derive the rate from the delta over the
  window span. The line now visibly moves with burst/lull cycles
  the way a real DPS meter should.
- Window state is cleared when combat ends so a new pull starts
  with a clean rate.

TRACKER — DECIMAL PRECISION
- Fixed: bar values rendered as "10K DPS" with no decimal place.
  The user asked for "10.4K DPS". The Abbr helper in MainFrame
  routed SecretValues straight through Blizzard's AbbreviateNumbers,
  which doesn't add a decimal for plain values < 1000 multiples.
  Now it normalises through Utils.ResolveNumber first (unwraps
  SecretValues to plain numbers) and formats with Utils.FormatNumber
  (`%.1f%s`) so we always get one decimal place.

BAR TOOLTIP — MISMATCHED MATH
- Fixed: hovering a bar showed spell totals that summed to more
  than the tracker bar value (e.g. tooltip showed 3.6M of spells
  under a 1.3M bar). Root cause: cache key was
  `guid|mode|segmentIndex`. A new combat keeps segmentIndex at 0
  (still "current"), so the cached spells from the *previous*
  combat survived into the new one — the tracker re-rendered with
  fresh data but the tooltip kept the stale list.
- Cache key now includes the Blizzard session ID
  (`instance._blzSessionId` or `EDM.Core.latestBlzSessionId`), so
  every new combat invalidates the cache automatically.

Version 2.4.19
--------------
DPS/HPS Graph Polish

GLYPH FIX
- The bottom-right duration label was showing the literal byte
  sequence "xE2x8FxB1 :43" instead of a stopwatch glyph. Root
  cause: U+23F1 STOPWATCH isn't in WoW's default font glyph set,
  so the renderer fell back to printing the escape bytes raw.
  Same problem on the crosshair tooltip's time line.
- Replaced both with a font-safe bullet glyph (•) in muted gold:
  "|cff998866●|r 0:43". Renders in every font we ship with.

GRAPH CHROME
- Added split accent stripes on the Y-axis edge: top half tinted
  the DPS red, bottom half tinted the HPS green, both fading
  toward the midpoint. Gives the chart a "billboard" frame
  without obscuring data.
- Bottom of the canvas now has a faint white-to-transparent
  horizontal accent stripe that visually separates the chart
  body from the X-axis labels.