Skip to content

Fix Combat Text Font never applying for SharedMedia fonts - #1262

Open
dfrisone wants to merge 2 commits into
EllesmereGaming:mainfrom
dfrisone:Combat-Text-Font-Bug
Open

Fix Combat Text Font never applying for SharedMedia fonts#1262
dfrisone wants to merge 2 commits into
EllesmereGaming:mainfrom
dfrisone:Combat-Text-Font-Bug

Conversation

@dfrisone

@dfrisone dfrisone commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Reported on 8.6.3: setting Combat Text Font to a SharedMedia font (e.g. "Numbers"), following the logout prompt, and logging back in shows the font selected in Global Settings, but the damage numbers never change. Bundled fonts work; only SharedMedia picks fail.

Cause

Two defects stacked in ApplyCombatTextFont (EllesmereUI_Startup.lua).

Load order. A SharedMedia font is stored as smf:<name> and resolved at startup. The earliest apply with SavedVariables available is our own ADDON_LOADED, and external SharedMedia packs load after us, so at that moment the name is not registered yet. The engine caches DAMAGE_TEXT_FONT before the PLAYER_LOGIN re-apply (the feature's own "logout required" popup documents that cache), so the later correct write is invisible.

Silent substitution. LSM:Fetch("font", name) without its noDefault flag does not return nil for an unregistered name. It returns the default font, Friz Quadrata, the same face damage numbers already use. So the "skip if unavailable" guard never fired, and the engine cached "no change" every login.

Bundled fonts store a direct file path and skip resolution entirely, which is why they always worked.

Change

Mirrors the Name Font pattern already in this codebase (unitNameFontPath):

  • The dropdown's setValue also stores the resolved path (EllesmereUIDB.fctFontPath). At selection time the providing pack is necessarily loaded, since its font is in the dropdown.
  • Startup applies the cached path in the early window, fetches with noDefault so a missing name can never silently become Friz, refreshes the cache once login completes, and clears it when the providing pack is gone, so no later login points the engine at a missing file.
  • Profiles saved before this fix have no cache; their first login populates it and the font shows from the next login on. Re-selecting the font once applies it on the very next relog.

Also fixed in the same block: the ADDON_LOADED watch was unregistered on the first matching addon, which is always ours, so the load-on-demand CombatTextFont object (scrolling combat text) was never restyled at its actual load moment. Only Blizzard_CombatText's load retires the watch now.

Testing

luac -p passes on both files. The resolve logic was exercised offline across all five timing windows (bundled early, cached early, pre-fix profile early, login-complete refresh, pack removed), plus a demonstration that the old code fetched Friz Quadrata for an unregistered name.

Verified in game by the original reporter on a test build of this branch: SharedMedia font selected, logout to character select, damage numbers render in the chosen font on a training dummy. Bundled fonts and Blizzard Default unchanged.

Family Guy Reaction GIF

Update after review

A multi-angle review of the first commit surfaced two real defects in its failure paths, both fixed in the second commit:

  • Pack uninstalled: the early window applied the cached path blindly, leaving DAMAGE_TEXT_FONT pointed at a missing file for the whole session, which disables floating combat text entirely (worse than the original bug).
  • Destructive cache clear: "not registered by PLAYER_LOGIN" was treated as "pack gone", so one login with the pack disabled (an alt, out-of-date after a patch) or a pack registering its fonts late wiped the account-wide cache and cost an extra default-font login after recovery.

The revision replaces the event-driven branching with a single invariant: every candidate path (LSM result or cache) is probed with SetFont, which returns a success boolean (per SimpleFontStringAPIDocumentation), before it reaches an engine global or stays cached. An uninstalled pack reverts to Blizzard default with the cache cleared; a disabled-but-on-disk pack keeps working, since font files load regardless of addon enable state. The cache also records which key produced it, so a desynced fctFont can never apply a stale path, and ApplyUnitNameFont gets the same probe (its unitNameFontPath rides profile export, where an import could carry a path the importer's machine does not have). The ADDON_LOADED watch now retires when the feature is unconfigured, keeping the unused-feature cost at zero.

All eight resolve scenarios (healthy, uninstalled, disabled, late-registering, key-mismatch, dead registration, pre-fix profile, bundled) pass an offline simulation of the new logic. The reporter-verified happy path is preserved by construction; a re-test of the pack-uninstalled path in game is welcome but the failure it guards against was never reachable in the verified flow.

Bundled fonts store a direct file path and worked. A SharedMedia font
stores "smf:<name>" and resolved it at startup, which failed twice over:

The earliest apply with SavedVariables available is our own ADDON_LOADED,
and external SharedMedia packs load after us, so at that moment the font
name is not registered yet. LSM:Fetch without its noDefault flag does not
return nil for an unregistered name, it substitutes the DEFAULT font, Friz
Quadrata. So the "skip if unavailable" guard never fired and
DAMAGE_TEXT_FONT was set to the font damage numbers already use, right in
the window where the engine caches it. The PLAYER_LOGIN re-apply resolved
the real path but landed after the cache. Net effect: the dropdown showed
the chosen font, the damage numbers never changed.

Fix mirrors the Name Font pattern already in this codebase: cache the
resolved path (fctFontPath) at selection time, when the pack is
necessarily loaded because its font is in the dropdown. Startup applies
the cached path in the early window, fetches with noDefault so a missing
name can never silently become Friz, refreshes the cache once login
completes, and clears it when the providing pack is gone so a later login
never points the engine at a missing file. Profiles saved before this fix
have no cache; their first login populates it and the second login shows
the font.

Also keep the ADDON_LOADED watch alive until Blizzard_CombatText actually
loads: our own addon always fired first and unregistered it, so the
load-on-demand CombatTextFont object was never restyled at its real load
moment.
Review findings on the first commit, worst first: with the providing pack
uninstalled, the early window applied the cached path blindly, leaving
DAMAGE_TEXT_FONT pointed at a missing file for the whole session, which
disables floating combat text entirely. And the login-complete cache clear
treated "not registered by PLAYER_LOGIN" as "pack gone", so a single login
with the pack disabled (an alt, a troubleshooting session, out-of-date
after a patch) or a pack that registers its fonts late wiped the
account-wide cache and cost an extra default-font login after recovery.

Replace the event-driven state machine with a probe: SetFont returns a
success boolean, so both the LSM result and the cached path are verified
loadable before either reaches an engine global or stays in the cache.
That single invariant covers every reviewed scenario: an uninstalled pack
clears the cache and leaves the default intact, a disabled-but-on-disk
pack keeps working from the cache (font files load regardless of addon
enable state), late-registering packs lose nothing, and a registration
pointing at a moved file is refused. The loginComplete parameter goes away
entirely.

The cache also records which key it was resolved for (fctFontPathFor), so
a fctFont written by any future non-dropdown path cannot silently apply a
stale path for a different font, and it is only written for smf: keys,
where the key is not already the path.

ApplyUnitNameFont gets the same probe on its cached path: unitNameFontPath
rides profile export, so an import from someone with a pack the importer
lacks pointed UNIT_NAME_FONT at a missing file permanently. It now falls
back to ResolveFontName and skips the write rather than apply a dead path.

The ADDON_LOADED watch retires at our own load when no combat text font is
configured (a mid-session pick needs a relog regardless) or when
Blizzard_CombatText already loaded, so only enabled-feature sessions that
still await that addon keep listening.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant