v1.1.0
What's new
Unbuffed
v1.1.0 (2026-10-09)
Full Changelog Previous Releases
- Choose where the icons attach, and rework the settings panel
The icon row can now sit right of the level text (the default), left of the health
bar, centered under the health bar, or at a custom X/Y from the plate's centre. The
three presets follow the plate's own parts, found the way FlareUI finds them on
Forever (UnitFrame.HealthBarsContainer.healthBar and UnitFrame.PlayerLevelDiffFrame),
and fall back level box -> health bar -> plate when a plate lacks one. The X/Y
sliders appear only for Custom. Profiles saved with the old side setting are
migrated: RIGHT and LEFT map to the nearest preset, and CENTER to Custom with its
offsets unchanged.
The settings panel is now grouped into sections inside a scroll frame, with a live
mock-nameplate preview drawn by the same placement code the real plates use, a
greyed-out expiry threshold when its toggle is off, an icon spacing slider, and a
two-click Reset for position and look. Reset is not a popup, because a StaticPopup
shown from addon code can hang the client in Gamepad mode.
Also:- Fortitude is tracked on a brand-new profile, seeded once rather than through
copyDefaults, which merges the tracked list by index. - SavedVariables are adopted once, at ADDON_LOADED. The VARIABLES_LOADED and login
re-adoption was written for a beta client bug that has since been fixed. - /ub check prints the settings state first, without needing a target.
- The options panel's build now sits inside the refreshing guard.
Co-Authored-By: Claude Sonnet 5.5 noreply@anthropic.com
- Fortitude is tracked on a brand-new profile, seeded once rather than through
- Add the logo and the CurseForge project ID
The logo (512 and 256 px PNGs, plus the SVG it was drawn from) matches the tile
style of the other addons. It lives under .github, which .pkgmeta already keeps
out of the packaged zip.
X-Curse-Project-ID points the packager's CurseForge upload at the project page.
Co-Authored-By: Claude Sonnet 5.5 noreply@anthropic.com - Re-adopt SavedVariables at login, and report when they never arrive
The instrumentation answered it, and it was neither of the things I had been
fixing: at ADDON_LOADED UnbuffedDB is empty. The settings are not being clobbered
after loading -- they never load. Every session starts from defaults, saves the
session's edits on the way out, and discards them on the way back in, which is
exactly "loses its settings on every reload".
So ADDON_LOADED is no longer the only chance to pick them up. Adopting the global
is now a function, and PLAYER_LOGIN calls it again if the global is no longer the
table we adopted -- i.e. if the real saved values landed after us. This client has
already been caught resolving things later than the Classic ones do; the key
binding set was unavailable at login for the same reason.
If they genuinely never arrive, that is not something the addon can fix, so /ub
check now says which it is: it snapshots UnbuffedDB at both ADDON_LOADED and
PLAYER_LOGIN, prints the type and key count at each, and states plainly whether
the values turned up late or not at all.
Co-Authored-By: Claude Opus 5 noreply@anthropic.com - Stop the options panel writing defaults back over loaded settings
Settings were still being lost, though not all of them -- side and yOffset came
back fine while iconSize and xOffset reverted to defaults. That pattern rules out
the save path and points at something writing over individual values after they
load.
Two ways that could happen, both now closed:
A widget resync is a READ, but it triggers writes. A slider's SetValue fires
OnValueChanged, and a dropdown's GenerateMenu can re-invoke its setter; both then
call set() with whatever the widget was showing. Refreshes now run under a flag
that makes those setters no-ops, so resyncing can never write.
And the panel could still be built before the real db existed. ns.db is a stand-in
full of defaults until ADDON_LOADED, and the Settings frame may call OnRefresh
during registration -- which would build every widget against that stand-in and
bake defaults in. refreshPanel now refuses to do anything until ns.dbReady.
If it somehow persists, /ub check now settles it in one line: ADDON_LOADED
snapshots what arrived from disk before anything touches it, and check prints that
next to the live values, flagging any that differ.
Co-Authored-By: Claude Opus 5 noreply@anthropic.com - Flag buffs that are about to expire
A tracked buff with less than the warning threshold left (5 minutes by default)
now draws too: the same icon, tinted amber with the minutes remaining on it, so
"running out" reads differently at a glance from "never cast".
HasBuff returns the aura record alongside its verdict, and ns.AuraRemaining turns
that into seconds left. It returns nil for a permanent buff (expirationTime 0) and
for any client that won't hand the number over -- and nil is never treated as
"about to expire", the same rule that keeps an unreadable buff from being drawn as
missing.
Threshold is a slider (1-30 minutes) rather than a hardcoded 5.
Presets moved to three columns. Nine of them in five rows wasted enough height
that the new controls would have pushed the bottom of the panel under the Close
button again.
/ub check now reports the remaining time per buff, and whether ns.db is actually
the SavedVariables table -- see the next commit for why that matters.
Co-Authored-By: Claude Opus 5 noreply@anthropic.com - Build the options panel on first show, not at load
Saved settings looked lost after /reload. They were not: SavedVariables on disk
held every tuned value (side=CENTER, iconSize=23, yOffset=-25). The panel was the
problem, and the stand-in ns.db was the cause.
A Settings canvas category needs a frame at registration, so the whole panel was
built during load -- before ADDON_LOADED, so against the stand-in defaults rather
than the loaded values. Widgets that evaluate a callback while being constructed
therefore captured defaults, and MenuUtil's dropdown, which generates its menu at
setup and again on GenerateMenu, can write its remembered selection back: the
saved "CENTER" overwritten by the stand-in's "RIGHT".
A canvas category needs a frame up front, not a populated one. The layout now
lives in buildPanel(), run once from the panel's first show, by which point ns.db
is the real SavedVariables table. That removes the whole class rather than the one
widget -- the same lazy-build shape GaruCombatText's options already use, which is
why they never had this.
Also defines OnRefresh/OnCommit/OnDefault: the modern Settings frame drives canvas
panels through those rather than the legacyrefreshfield.
The stand-in ns.db stays as a guard against indexing nil early, but nothing
depends on its values now.
Co-Authored-By: Claude Opus 5 noreply@anthropic.com - Add a centered icon-row position
Third option beside left and right: the row anchors CENTER-to-CENTER on the
nameplate, so at offset 0, 0 it sits over the player's name.
The offsets mean something slightly different per position, deliberately. Beside
the plate, xOffset is a GAP -- "distance away from the plate" -- so its sign is
flipped for LEFT and a negative value pulls the row back over the plate on either
side. Centered there is no gap to describe, so xOffset is a plain nudge, positive
right and negative left. A hint under the dropdown says so.
The db key stays "side" even though CENTER isn't one; renaming it would orphan
every saved setting for the sake of a word.
Co-Authored-By: Claude Opus 5 noreply@anthropic.com - Tighten the options layout and allow a negative horizontal offset
The panel reserved room for four custom-buff rows whether or not any existed,
leaving a hole under "No custom buffs added" and pushing the bottom controls --
the position dropdown and Run diagnostics -- down behind the Close button.
Everything below the custom list now lives in a container frame that
rebuildCustomList repositions to sit just under the list, however long it is. The
widget helpers take an optional parent so they can be built into it. With an empty
list the block starts 68px higher and the last control lands at -576 instead of
-642, well clear of the bottom edge.
Horizontal offset now runs -60..60 rather than 0..60. Negative pulls the icon row
back over the nameplate instead of away from it, and since anchorRow flips the
sign with the chosen side, it reads the same whichever side the row is on.
Co-Authored-By: Claude Opus 5 noreply@anthropic.com - Don't overwrite the Settings category ID with the addon name
Settings.OpenToCategory forwards to C_SettingsUtil.OpenSettingsPanel on 12.x,
which takes a numeric category id and throws on a string. Overwriting category.ID
with "Unbuffed" made category:GetID() return that string, so /unbuffed raised
"bad argument #1 to 'OpenSettingsPanel'" and never opened the panel.
The override is a widespread idiom (Ace3 still ships it) and exists so other
addons can open a panel by name, which worked back when OpenToCategory accepted a
string. It no longer does, so the line buys nothing here and costs the addon its
own slash command.
Co-Authored-By: Claude Opus 5 noreply@anthropic.com - Fix nil ns.db while the options panel is being built
MenuUtil generates a dropdown's menu the moment SetupMenu runs, so the isSelected
callback fired during Options.lua's main chunk -- before ADDON_LOADED, and so
before SavedVariables (and ns.db) existed. Indexing it threw on load.
Fixed at the root rather than at the call site: ns.db is now seeded with a copy of
the defaults when Core.lua loads, and ADDON_LOADED swaps in the real SavedVariables
table. A Settings canvas panel has to be built during load so it can be registered,
so any widget that evaluates a callback at construction hits the same window --
seeding closes the whole class instead of the one dropdown.
The panel resyncs against the loaded values on show, so a saved non-default side
still paints correctly the first time the panel is opened.
GaruCombatText uses the same MenuUtil conversion but is not affected: its options
window is built lazily in build(), reached only via /gct, long after ADDON_LOADED.
Co-Authored-By: Claude Opus 5 noreply@anthropic.com - Unbuffed 1.0.0: flag friendly nameplates missing a tracked buff
Pick the buffs you care about; any friendly player on screen who is missing one
gets a small row of those icons beside their nameplate. A fully buffed player
shows nothing, so every icon visible is something to cast.
Two things shape the design.
Buffs are matched by NAME, not spell id. On Vanilla-era content a buff is a
family of per-rank ids -- Power Word: Fortitude alone is 1243, 1244, 1245, 2791,
10937, 10938 -- so a single id would match one rank and report everyone else as
unbuffed. Names also let a preset carry the group version, since somebody under
Prayer of Fortitude is not missing Fortitude. An id may only ever prove that a
buff is PRESENT; absence is always decided by name.
Reading other players' auras is restricted on Midnight. Iteration throws while
auras are secret; GetUnitAuraBySpellID survives combat but only for whitelisted
ids (which is exactly the long-term raid buffs this addon is for); and a refusal
looks like "returned nothing" rather than an error. C_Secrets is used to ask
permission first, so a genuine "not buffed" can be told apart from "won't say".
Hence a three-valued check -- has it / definitely missing / no answer -- where
only a definite missing draws an icon. Guessing on a refusal would paint a
"needs buff" icon on every player in sight the moment the client clamped down.
/ub check prints, per buff, what this client actually answered.
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
This mod has no additional files