Skip to content

fix(unitframes): honour a class power style changed outside the options panel - #1232

Open
dfrisone wants to merge 2 commits into
EllesmereGaming:mainfrom
dfrisone:override-connection
Open

fix(unitframes): honour a class power style changed outside the options panel#1232
dfrisone wants to merge 2 commits into
EllesmereGaming:mainfrom
dfrisone:override-connection

Conversation

@dfrisone

@dfrisone dfrisone commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Reported by @n0ObiX_ on 8.7.5: a Spec Override setting Enable Class Resource to None for Arms does not apply on login. Switching to another spec and back makes it work.

Cause

frames._toggleClassPower is the only thing that honours a classPowerStyle change. It tears the bar down, rebuilds it, and re-anchors the health bar. It had exactly two callers: the options dropdown, and the PLAYER_SPECIALIZATION_CHANGED watcher.

So a style changed through any other path left the live bar stale. Spec Overrides applying at login is one such path; so are a profile switch and an import. The reported workaround is the diagnosis, since switching spec away and back is the one event that does call it. The watcher deliberately does nothing on PLAYER_ENTERING_WORLD beyond setting its init flag, so login was never covered.

Fix

Realise the style on any reload instead, gated on the built style actually differing, because the toggle is a full teardown and running it on every reload would thrash the bar.

frames._classPowerBuiltStyle records what is currently built. It is written in exactly two places, both real build sites: the init build, and the toggle itself. The init one matters so the first reload of a session does not see a nil marker, assume a change, and pay a pointless rebuild for users with no override at all.

The spec-change watcher is unaffected. It calls the toggle itself, which updates the marker, so the reload it queues finds them equal and no-ops.

Two things the self-review caught after the first in-game pass

Both are in the second commit, and neither was visible to a passing test of the reported case.

The marker was originally seeded inside PositionClassPowerBar. That is a repositioning function, not a build, and ReassertClassPower calls it from Blizzard's SetParent and Hide hooks on form and spec changes with no rebuild behind it. It therefore stamped the current profile style onto a bar still built as something else, which makes the reload pass see no change and skip the rebuild: the reported bug, back again. It only bites while the Blizzard style is live, since _blizzCPActive gates the reassert. A marker that records what is built has to be written where the build happens, so it moved to the init build block, guarded on frames.player to match the toggle.

The call also had no combat gate. ReloadFrames() early-returns in lockdown, but this runs from the throttle body after that return, so that guard does not cover it, which is the same trap the UpdateFrameVisibility note a few lines up already documents. Unguarded it would reparent and hide Blizzard's class power frame and re-anchor the health bar mid-fight; ReassertClassPower guards Show() on that same frame the same way. Skipping alone was not enough either, because nothing re-arms the pass once combat ends, so it defers to PLAYER_REGEN_ENABLED and re-runs.

Performance

Steady state is a table lookup, a db read, and a string compare per reload pass, then an early return. The teardown only runs on an actual change.

No polling is added. The deferral frame has no OnUpdate and registers PLAYER_REGEN_ENABLED only while a change is pending in combat, unregistering as soon as it fires. Neither _toggleClassPower nor ResizeFrameForClassPower re-arms the reload pass, so there is no loop, and the marker would make a second pass a no-op even if something did.

Net it is marginally cheaper than before the review, since the marker write moved off PositionClassPowerBar, which runs on every Blizzard reparent, onto a build path that runs once.

Notes

One file. No new strings, no options changes, no saved-variable changes.

Tested in game: the reported Spec Override case now applies at login, and re-tested after the review changes.

cat GIF by The Videobook

…ns panel

Reported by @n0ObiX_ on 8.7.5: a Spec Override setting Enable Class Resource
to None for Arms does not apply on login. Switching to another spec and back
makes it work.

frames._toggleClassPower is the only thing that honours a classPowerStyle
change -- it tears the bar down, rebuilds it, and re-anchors the health bar --
and it had exactly two callers: the options dropdown, and the
PLAYER_SPECIALIZATION_CHANGED watcher.

So a style changed through any other path left the live bar stale. Spec
Overrides applying at login is one such path; so are a profile switch and an
import. The reported workaround is the diagnosis: switching spec away and back
is the one event that does call it.

Note the watcher deliberately does nothing on PLAYER_ENTERING_WORLD beyond
setting its init flag, so login was never covered.

Realise the style on any reload instead, gated on the built style actually
differing, because the toggle is a full teardown and running it on every
reload would thrash the bar. frames._classPowerBuiltStyle records what is
currently built, set both by the toggle and by the init build path -- the
latter so the first reload of a session does not see a nil marker, assume a
change, and pay a pointless rebuild for users with no override at all.

The spec-change watcher is unaffected: it calls the toggle itself, which
updates the marker, so the reload it queues finds them equal and no-ops.
… of combat

Two defects from a review of the previous commit.

The built-style marker was seeded inside PositionClassPowerBar, which is a
REPOSITIONING function, not a build. ReassertClassPower calls it from
Blizzard's SetParent and Hide hooks on form and spec changes, with no rebuild
behind it, so it stamped the CURRENT PROFILE style onto a bar that was still
built as something else. That claims a style is built when it is not, and the
reload pass then sees no change and skips the rebuild, which is exactly the
reported bug coming back. It only bites while the Blizzard style is live, since
_blizzCPActive gates the reassert, but the whole point of the marker is that it
describes what is built, so it has to be written where the build happens.
Moved to the init build block, guarded on frames.player to match the toggle,
which returns before stamping when there is no frame.

The reload pass had no combat gate. ReloadFrames() itself early-returns in
lockdown, but this runs from the throttle body AFTER that return, so the guard
does not cover it -- the same trap the UpdateFrameVisibility note a few lines
up already documents. Unguarded it would reparent and hide Blizzard's class
power frame and re-anchor the health bar mid-fight; ReassertClassPower guards
Show() on that same frame the same way. Skipping alone is not enough either,
because nothing re-arms the pass once combat ends, so the style would stay
stale until some later reload. Defers to PLAYER_REGEN_ENABLED and re-runs.

Both call sites now share one RealiseClassPowerStyle helper instead of
open-coding the check.
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