This change updates several text setters to use `SetFormattedText` instead of passing `string.format(...)` into `SetText`. It also switches a few regex captures from `string.find` to `string.match` for cleaner capture handling, and replaces explicit width/height setters with `SetSize` in the first-run dialog for consistency.
* chat: detect whispers before the timestamp is prepended
Whisper detection tests for the whisper colour code at position 1, but the
timestamp is prepended first, so with timestamps enabled the code is no longer
at position 1 and every whisper failed the test -- losing both the recolour and
the correct chat-history entry.
(cherry picked from commit 4b69d597631c422d9360a94ff129323288a28cc2)
* macrotweak + libpredict: inverted install guard, misspelled globals
macrotweak: _AddHistoryLine is the backup slot this block creates, so it is nil
until line 18 runs. Guarding on its truthiness meant the chat-history filter
never installed at all -- macro calls kept landing in chat input history.
libpredict: UKNOWNBEING / UNKOWNBEING are misspelled, so both resolve to nil and
the guards never matched the real UNKNOWNBEING. Neutral if the global is absent
on this client, correct if present.
Not needed: his libpredict already fixed the always-true
'event == "A" or "B"' condition (libs/libpredict.lua:828).
(cherry picked from commit 4068110dc4134823f1c09f60ca5f8959302cd14f)
* mapreveal: look the explore cache up by the key it is actually stored under
explorecaches is keyed by the plain area name (line ~160), but the hover frame
carried only a decorated 'mapFileName (area)' display string, so every lookup
missed and the hover highlight never fired. Store the plain name alongside it
and key off that; the tooltip keeps the decorated string.
(cherry picked from commit 5e6fe969a03a88c68e28614548b9d44db562ab9a)
* socialmod: don't clobber the friend-online match with the offline one
The offline match was assigned unconditionally over the online match, so a
friend coming online never had lastseen recorded.
(cherry picked from commit ad927806c46042163f84bde4db1082726bedf28b)
* swingtimer: off-hand weapons are inventory type 22, not 21
The off-hand slot accepts one-hand (13) and off-hand (22) weapons. Type 21 is
INVTYPE_WEAPONMAINHAND and can never be equipped there, so the off-hand swing
timer never recognised a real off-hand weapon.
The other two swingtimer fixes are not needed here: his hunter check already
uses UnitClassBase (locale-independent, better than our UnitClass second
return), and his generic on-next-swing detection covers Raptor Strike without
our hardcoded spell-id list.
(cherry picked from commit 9ab7f5f7224d12361ca6cd5a49181cbb4bebd387)
* superwow: compare the version numerically, not by exact string
SUPERWOW_VERSION == "1.5" silently disables the GUID-to-name combat text hook
on any release past 1.5. Currently 1.5 here, so this is forward-compat only.
Not needed: the clickthrough slash commands already go through
RegisterSlashCommand, which writes _G properly.
(cherry picked from commit 82a37752a5782479849ce7e1304c24733c2ff97a)
* api: measure a real pixel against UIParent, not the uiScale cvar
The uiScale cvar caps at 1.0 while both the pixelperfect module and the
firstrun slider push UIParent past it via SetScale, and it is ignored entirely
while useUiScale is off, so borders came out the wrong thickness on the
Huge/Large presets. Ask the frame for its effective scale instead, and guard an
unparseable gxResolution.
Also cache GetItemLinkByName and count its failures: the scan walked every id
on each call with no memory, so an unresolvable name hitched on every tooltip
hover. Raise the ceiling from 25818 to 61000 as well -- Octo/Turtle custom
items live well past the vanilla range and never resolved.
(cherry picked from commit dac2d3416aef85a4c4b71c0239d93d309e76e30b)
* init: load pixelperfect first so GetPerfectPixel caches the right scale
pixelperfect sets the UI scale that GetPerfectPixel measures against, and that
value is cached on first use. Loading it 56th baked in the previous scale.
(cherry picked from commit dd89a210330583f890a51a82b29e968f3ad36b34)
* modules: route two global overrides through _G so they leave the sandbox
pfUI.env has __index but no __newindex, so a bare global assignment inside a
RegisterModule closure is written into the sandbox table and never reaches _G.
unitxp's BattlefieldFrame_Show override therefore never fired the BG queue-pop
notification, and RaidFrame.lua never saw GROUP_REPLACE_PARTY.
The slash-command half of this fix is not needed here: RegisterSlashCommand
already writes through _G.
(cherry picked from commit 617c8320aca2465af62cf0ff5be7dceaed29a3b2)
* bags: remove the duplicate search OnHide handler
Two byte-identical frame.search OnHide handlers were installed back to back;
the second overwrote the first, so the first was dead code.
(cherry picked from commit e77650448b92e5d7f18ae58632e4531cb2813ea6)
* buffwatch: invalidate the filter cache when a skill is listed
fcache is built once per config table and never cleared, so ctrl/shift-clicking
a skill onto the whitelist or blacklist had no effect until the next reload.
* roll: actually capture the everyone-passed subject
strfind(LOOT_ROLL_ALL_PASSED, LOOT_ROLL_PASSED) has no captures, so `everyone`
was always nil, never reached the blacklist, and "Everyone has passed on: X"
was recorded as a real player passing.
Builds a sample from LOOT_ROLL_ALL_PASSED and runs the same LOOT_ROLL_PASSED
match the scanner uses, so the subject is captured the way it will actually
appear.
* skins: drop includes for two files that were never committed
init/skins.xml referenced custom_merchant.lua and arena_score.lua, neither of
which is tracked in git. Every install -- release zips included, since the
release workflow packages the repo -- throws two 'Error loading' lines at
login and ships without those two skins.
(cherry picked from commit b338b4a16d0def4ae89fde5c6026e58a796b8c00)
* map: don't re-anchor the world map to the frame GetPoint returned
Ctrl+scroll rescales the map and repositions it to keep the top-left fixed, but
it re-anchored using the relative frame GetPoint handed back. Once anything else
is anchored to WorldMapFrame that throws
WorldMapFrame:SetPoint(): <unnamed> is dependent on this
and the error aborts the rest of the zoom handler, so SetScale never runs.
Anchor to the parent instead. That is what the rest of pfUI's movable system
already assumes -- LoadMovable uses the 3-arg form and SaveMovable stores only
xpos/ypos with no relative frame.
(cherry picked from commit bf055d87fc0a04ac912c13fa874a4331f47bdfa8)
* firstrun: return after bailing on a disabled chat module
All three chat setup steps printed 'Chat module is disabled' and then carried on
into the nil pfUI.chat they had just tested for.
(cherry picked from commit 1af427e3b38bb7c13f645ae7a48b8ade3e7455a9)
* unitxp: stop the free-frame distance poller on logout
The PLAYER_LOGOUT handler stops the indicators to avoid the UnitXP crash on
exit, but in free-frame distance mode the polling runs on a separate scanner
frame that was never exposed, so the handler could not reach it and its
OnUpdate kept calling into UnitXP during teardown.
Exposes the frame as pfUI.uf.target.distanceScanner and stops it alongside the
others.
* roll: bail out on an item the client has not cached
C_Item.GetItemInfo returns nil for an item that is not cached yet, and the next
line assigns pfUI.roll.cache[itemName], which throws "table index is nil" on a
nil key. Easy to hit on a fresh login when someone rolls on an item you have
never seen.
* cooldown: return after hiding on a nil parent
Without the return it falls straight through to parent:GetName() on the nil it
just tested for.
Route the remaining hardcoded English labels through the T translation
table and register the keys in enUS:
- bags: "Sort Bags" / "Sort Bank" tooltips
- farmmode: "FARM MODE" overlay
- unitxp: "BEHIND" / "NO LOS" target indicators
- unitframes: raid group header "Group" (reuses the existing key)
- bgscore: "Battleground Frames" mover title
commit 70b1e66c2d
Author: Brues <5278969+brues-code@users.noreply.github.com>
Date: Sun Jul 26 18:30:18 2026 -0500
Use SetSize/SetShown and simplify minimap/map
commit 7b6bfe9975
Author: Brues <5278969+brues-code@users.noreply.github.com>
Date: Sat Jul 25 11:27:14 2026 -0500
Include raidpet frames in /pftest test mode
The unit-frame test toggle (showall) only previews frames that exist and
are positioned. Raidpet frames are created when raidpet.visible == "1",
but LayoutPets' collapse mode only positions pets whose raidpet<N> unit
actually exists, so solo/in test mode they stayed hidden.
Add a showall branch to LayoutPets that mirror-lays every pet cell and
shows it (bypassing collapse and the roster gate), still guarded by the
existing showpets check so nothing happens when raidpet is disabled. Call
LayoutPets from the /pftest handler so the grid updates immediately on
toggle-on and restores to the normal layout on toggle-off.
commit 4c65c38647
Author: Brues <5278969+brues-code@users.noreply.github.com>
Date: Sat Jul 25 10:34:33 2026 -0500
Remove unused RangeCache local in UnitInRange
commit 6c96bbbe6c
Author: Brues <5278969+brues-code@users.noreply.github.com>
Date: Sat Jul 25 10:34:33 2026 -0500
Use Clamp() for the two-sided clamps in ui-widgets
Replace the hand-rolled math.min/math.max and if/elseif clamp idioms in the
status bar and scroll frame with Clamp(). Equivalent for every value seen
(min <= max always holds), just clearer.
commit 830e0a0be1
Author: Brues <5278969+brues-code@users.noreply.github.com>
Date: Sat Jul 25 10:34:33 2026 -0500
Drop vanilla GetItemInfo shim for C_Item.GetItemInfo
The compat/vanilla.lua GetItemInfo override reshaped vanilla's tuple into
retail order by inserting nil at slot 4 (itemLevel), but truncated at 8
fields, dropping itemEquipLoc/itemTexture. ClassicAPI's global GetItemInfo
keeps the vanilla shape (its hook only warms the item cache), so the shim
stayed necessary but incomplete.
Remove it and point the three callers at ClassicAPI's C_Item.GetItemInfo,
which returns the full 18-field retail tuple. Positions 1-8 are identical
to the old shim output, so bags/roll are unchanged; character.lua's
itemSlot (equipLoc, field 9) was always nil under the truncating shim and
now resolves correctly for ShaguScore.
commit c0da63657d
Author: Brues <5278969+brues-code@users.noreply.github.com>
Date: Sat Jul 25 10:09:34 2026 -0500
Various cleanup
Removes version compatibility checks for TBC/Vanilla, consolidating code to target a single WoW version. Refactors repetitive SetWidth/SetHeight calls to SetSize for cleaner code. Removes duplicate function definitions from unitxp module and simplifies conditional show/hide operations using SetShown.
commit 11a6302c2d
Author: Brues <5278969+brues-code@users.noreply.github.com>
Date: Sat Jul 25 09:53:37 2026 -0500
Delegate pfUI.api.strsplit to ClassicAPI's strsplit
Replace the Lua pattern-based implementation with a thin wrapper around
ClassicAPI's C-level strsplit. Keeps the pfUI.api.strsplit entry point for
backwards compatibility with addons that call it, while dropping the
redundant reimplementation.
Behavioral note: the old version used ([^delim]+) which silently collapsed
empty fields; delegating to real strsplit now preserves them
("a,,b" -> "a", "", "b"). All in-repo callers split numeric color tuples,
version strings, or build name-keyed lookup tables, none of which are
affected by empty-field preservation.
commit 67c126eac8
Author: Brues <5278969+brues-code@users.noreply.github.com>
Date: Sat Jul 25 01:01:02 2026 -0500
Bump ClassicAPI minimum version to 1.8.0
commit a6cf157518
Author: Brues <5278969+brues-code@users.noreply.github.com>
Date: Sat Jul 25 00:49:50 2026 -0500
read spell rank via C_Spell.GetSpellSubtext
The rank string comes from the spell subtext ("Rank N"), which ClassicAPI's
C_Spell.GetSpellSubtext returns directly -- so drop nampower's
GetSpellRecField(spellId, "rank") here. The existing "Rank " gsub parse is
unchanged.
commit 4b1ba99b4f
Author: Brues <5278969+brues-code@users.noreply.github.com>
Date: Sat Jul 25 00:33:20 2026 -0500
Move player info overlay onto ClassicAPI (drop Nampower)
The haste/spell-power overlay read both values through Nampower and disabled
itself entirely without it. Move both to ClassicAPI so it works on any
ClassicAPI client:
- Spell power: GetSpellPower("net") (nampower) -> GetSpellBonusDamage(i)
(ClassicAPI, same per-school field). Merged the build+scan into one loop;
the default school seeds the tiebreak so equal schools still favor it.
- Haste: GetUnitField("player", "modCastSpeed") -> UnitSpellHaste("player"),
which returns the haste percentage directly (the (1/modCastSpeed-1)*100
conversion is now baked in, off the same UNIT_MOD_CAST_SPEED field, signed).
- Dropped the "if not GetUnitField then return" gate -- UpdateInfoText no
longer touches Nampower, so the overlay runs everywhere.
commit 7df4aa6d50
Author: Brues <5278969+brues-code@users.noreply.github.com>
Date: Sat Jul 25 00:32:44 2026 -0500
Hook the real global _G.UnitHealth for feign death
Inside a RegisterModule body, `function UnitHealth(...)` defines UnitHealth on
the pfUI environment, not the real global -- so the feign-death real-HP fix
only reached callers that resolve UnitHealth through pfUI's env, and missed
_G consumers (Blizzard frames, other addons). Hook _G.UnitHealth explicitly
(and capture oldUnitHealth from _G) so the un-gate applies everywhere.
commit b0bf2fd869
Author: Brues <5278969+brues-code@users.noreply.github.com>
Date: Fri Jul 24 21:10:58 2026 -0500
Refactor nampower module
Simplify reactive spell storage by using spell IDs instead of texture/name tables. Update to use modern C_Spell APIs (GetSpellTexture, GetSpellName) instead of manual texture paths. Consolidate SetWidth/SetHeight calls to SetSize and use print() instead of DEFAULT_CHAT_FRAME:AddMessage().
commit 0b06961333
Author: Brues <5278969+brues-code@users.noreply.github.com>
Date: Fri Jul 24 20:54:23 2026 -0500
Use GetNamePlateForUnit for target lookups; drop dead ScanGuid block
- Replace the three GetNamePlateForGUID(UnitGUID("target")) round-trips with
GetNamePlateForUnit("target"), which resolves the token to a GUID internally
-- no UnitGUID string detour, and it returns nil for no-target/no-plate so
the UnitExists guards collapse. GetNamePlateForGUID is left for raw-GUID cases.
- Remove the dead libunitscan.ScanGuid nameplate block (ScanGuid was deleted
from libunitscan long ago, so the guard was never true) -- it carried the
last GetUnitField("npcFlags") read.
commit 3e6210086e
Author: Brues <5278969+brues-code@users.noreply.github.com>
Date: Fri Jul 24 20:54:23 2026 -0500
Drop Nampower stats system and polling from unit frames
GetUnitStats now reads health/power straight from UnitHealth/UnitPower
(the descriptor fields the server broadcasts) -- for a real unit token
GetUnitField read nothing different, so the whole Nampower-vs-fallback
apparatus was measuring a distinction that no longer exists. Removed:
- The stats system: pfUI.uf.stats, pfUIStatsFrame + UpdateStatsDisplay, the
lastUnitStats change-cache, the /pfuistats slash command, and every counter
increment (event/heartbeat/earlyReturn/throttledSkip/nampower/fallback).
- All GetUnitField health reads (GetUnitStats, heal-prediction, health-gradient
color) -- collapsed to the UnitHealth/UnitHealthMax they already fell back to.
- The heartbeat-polling backstop and its fallbackThrottle/lastEventUpdate deps.
Frames now refresh on events only; eventless frames still use their own tick,
and range/aggro still run in the eventless-actions block.
commit 63001b7b0c
Author: Brues <5278969+brues-code@users.noreply.github.com>
Date: Fri Jul 24 20:09:42 2026 -0500
Move nameplates onto ClassicAPI stable nameplate tokens
ClassicAPI now assigns retail-exact, per-plate-stable "nameplateN" tokens and
fires vanilla UNIT_* events for them, so the nameplate module can key off the
token instead of Nampower/SuperWoW GUID primitives:
- Events: UNIT_AURA_GUID/UNIT_FLAGS_GUID -> UNIT_AURA/UNIT_FLAGS, matched by
the "nameplateN" token (guarded on the token prefix, since these also fire
for target/party/raid). Registered unconditionally -- no GetUnitField gate.
- Health: GetUnitField(guid, "health"/"maxHealth") -> UnitHealth/UnitHealthMax
on the plate's cached token. Same UNIT_FIELD_HEALTH the server broadcasts
(real HP on Turtle; the ~= 100 scaled-vs-real guard is unchanged).
- Combat/target in GetCombatStateColor: GetUnitField("flags") + HasFlag ->
UnitAffectingCombat(token); GetUnitField("target") + SuperWoW "<guid>target"
-> the "nameplateNtarget" chain. Dropped the now-dead HasFlag,
UNIT_FLAG_IN_COMBAT, and guidTargetTokenCache.
- GetCastInfo now takes the token directly; every caller already holds it, so
the UnitTokenFromGUID(guid) round-trip is gone (unused module-wide now).
NAME_PLATE_UNIT_ADDED caches the token as plate.nameplate.unit alongside the
GUID; REMOVED clears both. GUID is retained only as the stable cache key
(debuffCache/threatMemory/combatColorCache). Only GetUnitField("npcFlags")
remains -- it has no token/vanilla equivalent.
commit ce1c49fcbb
Author: Brues <5278969+brues-code@users.noreply.github.com>
Date: Fri Jul 24 18:20:06 2026 -0500
Make focus/focustarget event-driven via ClassicAPI unit events
ClassicAPI now fires UNIT_* (health/mana/aura/...) with arg1 == "focus" and
arg1 == "focustarget", observed per-unit like target/party/raid. Both frames
already registered those events (focus/focustarget are in pfValidUnits) and
their OnEvent already matches arg1 == label, so the 0.2s polling ticks were
pure workarounds for the missing events. Drop both ticks; the frames now
refresh on-event like target, with range/glow still on the shared 0.5s state
pass and PLAYER_FOCUS_CHANGED still driving assign/clear.
The RegisterSlashCommand helper in api/api.lua was effectively unused
(only macrotweak called it); every other command hand-rolled the
SLASH_*/SlashCmdList pair. Convert the existing manual registrations to
the helper with force=true, preserving the current always-bind behavior
while centralizing the pattern behind one code path (and its _G. and
conflict-check handling).
Left as-is: pfUI.lua's /rl, /pfui, /gm (registered before api.lua
defines the helper) and the vendored libs' debug commands.
pfUI module bodies run under pfUI.env (setfenv in LoadModule), which
proxies reads to _G via __index but has no __newindex. Bare
"SLASH_FOO = ..." assignments therefore land on pfUI.env, not on _G,
and WoW's slash dispatcher reads _G.SLASH_* directly — so the slash
command never registered. SlashCmdList[KEY] assignments happened to
work because the table is read first (falls through), then the key
write hits the real global table.
/pfunitxp, /clickthrough, /ct, /disenchantall, /dea — all silently
broken until now. Fixed by prefixing SLASH_* writes with _G.
Drop the now-vestigial expansion plumbing.
- Delete modules/thirdparty-tbc.lua + its xml Include
- Strip 10 tbc-tagged CreateConfig calls in modules/gui.lua
- Drop the expansion arg from CreateConfig() signature + the disabled-
entry rendering path that depended on it
- Drop the showdisabled GUI toggle + its default
- Simplify pfUI:RegisterModule / pfUI:RegisterSkin to (name, func) only
- Strip the leading version arg ("vanilla:tbc", etc.) from all 114
Register call sites
- Delete the pfUI.expansion variable