Add "Show Spell IDs" and "Show Unit IDs" options alongside the existing
item-ID line. Spell IDs come from GameTooltip:GetSpell() on spell
tooltips and from the aura's spellId on unit-aura tooltips; unit IDs
come from C_CreatureInfo.GetCreatureID on the mouseover unit's GUID
(skipped for players and guarded when the GUID carries no entry).
Register GreedMeter's primary window on the meter dock's damage slot,
mirroring the ShaguDPS integration. GreedMeter builds its windows lazily
and supports several, so frames are resolved at call time.
A second GreedMeter window docks into the panel's other half: pfUI's
dock decides fill-vs-split by whether the threat slot is filled, so hook
GreedMeter's window add/remove to reconcile that slot and re-run the
resize. Only the first two windows fit the panel; any beyond that float.
strtrim collapsed a name like " " to "" and the empty guard then
rejected the save, but a whitespace-only name is valid in vanilla and a
common way to make a name-less macro. Save the raw text and only reject
a truly empty box.
* 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.
Add a "Show Unit Buffs" option that draws a row of buff icons above the
tooltip for mouseover units. Icons come from a ClassicAPI object pool
and are re-anchored left-to-right on each refresh; a single throttled
ticker counts down the remaining time on the visible icons.
The friend right-click menu only opens for online players, so notes
could not be edited on offline friends. The full menu is useless
offline anyway (Whisper/Invite/Target need an online player), so open
a menu of our own with just the note editor for offline friends.
Squashed from the classicapi-next branch:
- Read spell fields through ClassicAPI instead of nampower (libdebuff, swingtimer)
- Read friend and who-list class tokens from C_FriendList (socialmod, libunitscan)
- Count online friends with C_FriendList.GetNumOnlineFriends
- Add a friend notes module (modules/friendnotes.lua)
- Bump ClassicAPI minimum version to 10906
- combopoints: only ever show on the target's plate, so re-issue Show/Hide
only when the target's combo count changes, and skip non-target plates once
cleared (they start hidden) -- instead of hiding all five every tick on every
plate. plate.cpShown tracks the shown count and stays in sync with the frames.
- player class color: merge the identical ENEMY_PLAYER / FRIENDLY_PLAYER
branches into one condition.
Use C_UnitAuras.GetBuffDataByIndex(guid, 1) instead of GetUnitAuras(guid)[1]:
a passive totem's provided buff is its single helpful aura, so the buff-
filtered single-slot fetch is both more precise and cheaper than building the
full aura list to take the first entry.
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
Replace the localized L["totems"] / L["critters"] name-substring matching
with ClassicAPI's UnitCreatureTypeID (CreatureType.dbc: 8 = Critter,
11 = Totem), cached per plate and reset on reuse. Locale-independent,
covers custom/Turtle units, rejects mobs merely named "...Totem...", and
cheaper than the per-plate name loop.
Totem icons now come straight from the game instead of a hand-picked table:
passive totems self-cast their provided buff, so their single aura's icon
is read via C_UnitAuras.GetUnitAuras; active totems (Searing/Magma/Fire Nova)
carry no self-aura, so their icon is captured from UNIT_SPELLCAST_SUCCEEDED
(C_Spell.GetSpellTexture) and cached. Detection and icons read through the
plate's ClassicAPI-provided GUID (cachedGuid).
Only show "Total" when the item is split across more than one location;
a single-location item made "Total" duplicate the one breakdown line
(e.g. "Equipped: 1 / Total: 1").
Route the Bags/Bank/Equipped/Total labels through the T translation table
and add the keys to every locale file, using the Blizzard GlobalStrings as
the reference for each localized term.
The mouseover-scripts migration dropped the unit frame OnEnter/OnLeave
handlers that read C.unitframes[unit].showtooltip, so nothing consulted the
setting anymore. The engine mouseover (driven by the frame's unit attribute)
now shows the native tooltip unconditionally, so the option could not gate it
even if a reader remained.
Drop the config default, the "Enable Mouseover Tooltip" GUI checkbox, and the
now-orphaned locale string from all translation files.
Replace nampower's GetSpellIdCooldown with C_Spell.GetSpellCooldown, and
compute the remaining time from startTime plus duration (both seconds).
Drop the presence guard, since ClassicAPI is a hard dependency. The
module still needs nampower for the AURA_CAST events.
Replace nampower's IsSpellInRange with C_Spell.IsSpellInRange. This drops
the last unguarded nampower call. The ClassicAPI function returns true,
false, or nil, so test == true and == false and leave the bar unchanged
on nil.
libspell.GetSpellInfo now returns the spell ID at position 9 and caches
it with SafePack and unpack.
The action bar tracks self.spellID for macro-cast spells and shows the
tooltip with GameTooltip:SetSpellByID. Gather the slot, book type, and
spell ID from one GetSpellInfo call instead of a separate GetSpellIndex
lookup.
The per-slot popout arrows previously appeared only while the equipment
manager sidecar was open. Add a "Always Show Equipment Slot Flyouts"
option under Character -> Inventory (character.inventory.equipflyout,
off by default) that instead ties them to the paperdoll, so gear can be
swapped without opening the equipment manager.
Centralize the show/hide in a single UpdatePopouts(): by default the
arrows follow the sidecar, with the option on they follow PaperDollFrame.
Both the sidecar OnShow/OnHide and new PaperDollFrame OnShow/OnHide hooks
route through it, and the flyout hides whenever the arrows go inactive.
Hook GameTooltip:SetUnitAura to append the caster's name; every pfUI aura
tooltip (player buffs, buffwatch, unitframes) routes through it, so one hook
covers them all. Resolve the name from the live sourceUnit token, falling
back to the sourceGUID name cache (players only) when the token is gone.
Gated behind a new tooltip.aurasource option (off by default) with a matching
"Show Aura Caster" checkbox. The toggle reloads the UI, so the hook is
installed conditionally at load rather than re-checking the flag per tooltip.
* Add pfUI-native macro icon picker
Replace Blizzard's MacroPopupFrame (the name + icon dialog) with a
custom pfUI picker driven by IconDataProviderMixin, so the full spell +
item + loose icon set is available instead of the stock spell-only list.
It has a searchable, filterable icon grid and saves through
C_Macro.CreateMacro / C_Macro.EditMacro, which take the icon as a texture
string -- so arbitrary icons (including index-less INV_* item icons)
persist. The macro-frame API is identical on the modern (Turtle/Octo)
and stock-vanilla macro UIs, so the picker works on both with no client
detection.
The new macroicons module takes over the New / Change-Icon buttons and
retires the stock popup; the blizzard macro skin now only skins
MacroPopupFrame when that module is disabled.
* Gate the macro icon hook behind a config flag
Run the Blizzard_MacroUI hook only when C.disabled["macroicons"] is
"1".
* Add search to the icon pickers and fix set editing
Equipment manager:
- Move the OK button next to Cancel at the bottom-right.
- Add an icon search box in the freed bottom-left space.
- Shift the popup contents up and shrink the window after the title
was removed.
- Show the current icon when you edit a set. Keep a full icon path as
is instead of adding a second prefix, and match grid icons by name.
- Close the name/icon popup when the manager frame closes. Remove the
duplicate OnHide hook.
Both pickers now use Blizzard global strings for the labels and the
icon filters.
* Replace the row hover poll with OnLeave handlers
The gear button sits inside the set row's rectangle, so MouseIsOver(row)
is true for both. Show the gear on row enter. Hide it from a shared
OnLeave on the row and the gear when the cursor leaves the row. This
removes the per-frame OnUpdate poll.
* Handle missing C_Macro.CreateMacro API
Add guards to detect if C_Macro.CreateMacro is unavailable. The macroicons module now returns early if the API doesn't exist, and turtle-wow.lua uses the alternative Blizzard_MacroUI fallback path in such cases.
* Remove the macro picker title and shrink the window
Delete the title font string and its SetText calls. Move every
top-anchored element up 14px and shrink the window height from 498 to
484. The OK, Cancel, and search controls are bottom-anchored, so they
keep their spacing to the grid.
* bump ClassicAPI min version to 1.9.3
* Extract a shared icon picker widget
The macro icon picker and the equipment manager's name/icon popup carried
near-identical copies of the icon grid, All/Spells/Items filter, name search,
and the IconDataProvider state machine. Pull that into a single
pfUI.api.CreateIconPicker helper; each module keeps only its own frame shell,
buttons, and save flow.
Also fix the grid tooltip: pass white to GameTooltip:SetText (it defaults to
red) and derive the icon name with the case-insensitive basename key so
mixed-case provider paths show a clean name instead of the full path.
Compare each plate's guild name against a cached myGuild value instead
of calling UnitIsInMyGuild for every plate. Refresh the cache on
PLAYER_GUILD_UPDATE and PLAYER_ENTERING_WORLD. This replaces a per-plate
C call with a Lua string compare.
Hook Blizzard_MacroUI and replace UpdateMacroIconFilenames. The new
function reads each icon from IconDataProviderMixin, strips the path
prefix, and adds the name to MACRO_ICON_FILENAMES. A seen table skips
duplicate names.
Replace the cfgColorToHex function with a memoized table. The table
caches the color code for each config string on first use.
Build the code with C_ColorUtil.GenerateTextColorCode instead of
string.format. Change the spell-school colors and the format strings
to 8-character ARGB hex with the |c prefix.
Replace raw strsplit(",", ...) color parsing across action bars, chat,
nameplates, third-party skins, player, and roll with the cached
GetStringColor / GetStringColorObject helpers, dropping the per-build
string-table allocations they created.
* Migrate player castbar to ClassicAPI UNIT_SPELLCAST_* events
Drive the player cast path off ClassicAPI's synthesized, player-only
UNIT_SPELLCAST_* events instead of the vanilla SPELLCAST_* plus nampower
SPELL_{START,GO,DELAYED}_SELF mix. START/CHANNEL_START poll UnitCastingInfo/
UnitChannelInfo and stamp; STOP/CHANNEL_STOP/FAILED/INTERRUPTED clear;
SUCCEEDED drives tradeskill craft counting; DELAYED/CHANNEL_UPDATE re-poll.
Since UNIT_SPELLCAST_START fires for chained same-spell recasts, the nampower
SELF deps and their RunNextFrame co-hook-ordering workarounds are gone.
Remote target/focus bars keep SPELL_*_OTHER (the new events are player-only).
Fix the cast-start flash: StampBar now primes the fill on the stamp frame,
and ClearBar no longer snaps the bar to full (that snap lingered in the fade
tail and flashed when the next cast stamped).
Add a red flash on a cancelled cast: new appearance.castbar.failcolor,
flashed by ClearBar only when a cast was actually in progress.
Handle pushback Quartz-style: DELAYED/CHANNEL_UPDATE re-poll just the times
and accumulate the endMs shift into this.delay for the +/- indicator (cast
+X, channel -X), rather than a full restamp.
* Route target/focus castbars through remote UNIT_SPELLCAST_*
ClassicAPI now fires UNIT_SPELLCAST_* for remote tokens (target/focus/...)
via PollRemote, so drop the nampower SPELL_*_OTHER path and drive all three
bars off one event model. The gate is now token-based (arg1 == this.unitstr,
plus the arg1=="player" + UnitIsUnit case for target=self); the RunNextFrame
defer is gone since the remote poll fires after UnitCastingInfo is readable.
Cast detection is now nampower-free across every bar.
Use the event's rank payload (arg5): thread it through RefreshBar into
StampBar, which now only calls C_Spell.GetSpellSubtext as a fallback on the
retarget re-poll (PLAYER_TARGET/FOCUS_CHANGED) where no event is in hand.
* Drive nameplate castbars off UNIT_SPELLCAST_*
ClassicAPI now fires UNIT_SPELLCAST_* for nameplate tokens, so populate the
per-GUID castState cache from UNIT_SPELLCAST_{START,CHANNEL_START} (poll
PollCastInfo for the timing the payload omits) and clear it on
{STOP,CHANNEL_STOP} -- the remote poll fires STOP for natural end, interrupt,
and cancel alike. Replaces the nampower SPELL_{START,FAILED}_OTHER path; the
^nameplate arg1 gate ignores the target/focus/party fires of the same events,
and the NAME_PLATE_UNIT_ADDED seed still catches a plate spawning mid-cast.
Nameplate cast detection is now nampower-free.
* We no longer use these features from SuperWoW/Nampower
* Use secure unit attributes; remove mouseover scripts
Switch unitframes to attribute-driven clicks and remove mouseover tooltip handlers. Adds SetAttribute("unit") in UpdateVisibility and sets frame attributes (unit and type1='target') when creating frames. Removes OnEnter/OnLeave functions and their SetScript registrations and eliminates direct TargetUnit calls in ClickAction
* Use SetShown and simplify component default
Replace explicit Show/Hide conditionals in api/unitframes.lua with SetShown(...) for ressIcon, leaderIcon, lootIcon, pvpIcon, and restIcon to reduce branching and improve readability. Also remove the local shadowing of the parameter 'component' by using assignment (component = component or ""). No intended behavior changes.
* Drive unit-frame clicks via secure attributes
Replace the Lua OnClick dispatch (OnClick/ClickAction/RightClickAction) with
secure frame attributes: default type1=target / type2=menu, and EnableClickCast
now writes type/spell/macrotext/menu/target/focus attributes per button+modifier
instead of caching a clickactions table read at click time. Drops the now-dead
clickactions table and buttons list.
Also set a "unit" attribute on each frame (at creation and re-synced in
UpdateVisibility to the live token, so a party shown on the raid grid reports
partyN, not raidN) so the hovered unit resolves from the frame.
Match the target/focus/menu click keywords exactly rather than by prefix, so a
spell whose name starts with "focus"/"target"/"menu" (e.g. Focus Magic) casts
instead of being swallowed as the keyword action.
* Simplify marktracking colors and refresh per mark
Replace the hand-rolled ParseColor plus its default-color table with
GetStringColor -- the raidmarkercolor_* config keys already carry those same
defaults, so the fallback was dead code.
Restructure the refresh off observed mark tokens: UNIT_HEALTH/UNIT_MAXHEALTH
now arrive as arg1 == "markN", so refresh just that one row (UpdateRow) instead
of rescanning all eight on every nearby unit's health tick. A visibility flip
(into range / death / hp crossing 0) re-packs the rows, so UpdateRow hands off
to a full UpdateDisplay; RAID_TARGET_UPDATE / PLAYER_ENTERING_WORLD stay full
refreshes, with the 1s poll as the range-change backstop.
* not true anymore
* Poll marktracking fallback via C_Timer instead of OnUpdate
The 1s range-change safety net ran a per-frame OnUpdate that no-oped ~59 of
every 60 frames. Replace it with C_Timer.NewTicker(FALLBACK_INTERVAL,
UpdateDisplay) -- one wakeup per second off the shared timer driver -- and drop
the elapsed accumulator. The scanner frame is now purely event-driven.
* SuperWoW is now optional
* Update README feature list
Add 'Mouseover Unit Frames' and 'Click-casting' to the main features list. Update the SuperWoW entry to reflect that it tracks party/raid units on the minimap (replacing the prior SetMouseoverUnit note).
* Replace fixed-interval OnUpdate polls with C_Timer/RunNextFrame
Swap hand-rolled per-frame throttles for the modern timer primitives:
- turtle-wow: one-shot next-frame defer (self-hiding OnUpdate frame) -> RunNextFrame
- panel: clock, combat, and fps widgets -> NewTicker(1); guild roster -> NewTicker(60)
- minimap: coordinates text -> NewTicker(0.1)
- addonbuttons: one-shot init -> RunNextFrame; 5s button rescan -> NewTicker(5),
guarded by IsShown() to preserve the old "paused while hidden" behavior
Ticker callbacks reference frames via upvalue since 'this' is unbound outside
OnUpdate. Genuine per-frame work (bar fills, fades, drag) and polls already
coordinated through pfUI.throttle are left as-is.
* update pfQuest link
* Use SetShown/SetSize in panel module
Replace manual Show/Hide toggles with SetShown(not ... ) and replace SetWidth/SetHeight with SetSize in modules/panel.lua. Changes simplify toggle logic (timer, WorldMap, chat hide buttons) and unify sizing calls for timer, frames, textures, and microbutton. No behavior changes intended; purely refactor for conciseness and consistency.
* Bump ClassicAPI min version to 1.9.0
* Drive reagent counter from events instead of a polling OnUpdate
The pfReagentCounter frame ran an OnUpdate that (1) progressively rescanned
all 120 action slots one-per-100ms on any slot change and (2) recounted
reagent inventory on a 1s throttle. Replace both with direct event handling:
- ACTIONBAR_SLOT_CHANGED updates just the changed slot (arg1), full-scanning
only when arg1 is 0/nil, instead of restarting a ~12s rescan on every edit
- BAG_UPDATE_DELAYED recounts tracked reagents directly (it is already
Blizzard's coalesced bag event, so the extra 1s throttle was redundant)
- PLAYER_ENTERING_WORLD seeds the full reagent map once
UpdateSlot now seeds a new reagent's real count via GetItemCount so a freshly
placed reagent spell shows the correct number immediately. The updatecache ->
BarsUpdate render path and the IsReagentAction/GetReagentCount accessors are
unchanged.
Replace separate GetWidth/GetHeight and SetWidth/SetHeight calls with GetSize/SetSize for consistency and brevity. Updated modules: addonbuttons (store size via GetSize), chatcopy (use SetSize), easteregg (use SetSize for explosions), farmmode (use GetSize/SetSize when adjusting map size), and game_menu (use GetSize/SetSize when resizing frame).
hasTarget was always snapshotted alongside targetGuid (UnitExists("target")
next to UnitGUID("target")), so it was just targetGuid ~= nil. Drop the
field and derive it from targetGuid at both read sites; remove the now-unused
UnitExists perf-cache local.
frameState.hasTarget/targetGuid were polled via UnitExists("target") every
central-loop tick, while targetPlateGuid held the same guid but was already
event-driven -- redundant. Set frameState target fields in PLAYER_TARGET_CHANGED
(seeded on PLAYER_ENTERING_WORLD for a target held across reload), remove the
poll, and collapse targetPlateGuid into frameState.targetGuid (the castbar
frame now reads that). The central loop's only remaining per-tick poll is
GetTime().
Store the buffbar color/bordercolor/textcolor as cached read-only ColorMixins
via GetStringColorObject instead of building per-bar {r,g,b,a} tables from
GetStringColor. These color tables are only ever read (SetStatusBarColor /
SetTextColor / the border check), so sharing the cached objects is safe and
drops the intermediate locals and per-bar allocations.
GetStringColorObject's components are numbers, so fix CreateBuffBarFrame's
border check from ~= "0" (string) to ~= 0.
Route the three buffbar color config splits through GetStringColor (cached)
instead of bare strsplit. GetStringColor returns strings, so this is
behavior-preserving -- including the bordercolor ~= "0" check in
CreateBuffBarFrame -- and just adds the shared color cache and consistency
with the rest of the codebase.
Replaced numerous SetWidth/SetHeight calls with SetSize for consistency and brevity across UI code. Touched api/ui-widgets.lua, api/unitframes.lua and multiple modules (actionbar, addonbuttons, addons, afkcam, autovendor, bags, bgscore, buff, buffwatch, nameplates). Also simplified some sizing math in buff module. No functional behavior intended to change — code modernization only.
ClassicAPI polyfills UPDATE_MOUSEOVER_UNIT to fire on mouseover loss as well
as gain (src/unit/Mouseover.cpp), so the per-tick UnitGUID("mouseover") poll
in the central loop is no longer needed. Update frameState.mouseoverGuid on
the event and flag just the plates losing/gaining hover (via plateByGuid,
which holds overlays) for an immediate refresh.
- The central OnUpdate looped the entire plate pool (registry) every tick with
an IsVisible guard. Maintain a visiblePlates set via NAME_PLATE_UNIT_ADDED/
_REMOVED and iterate that instead, so hidden pool slots aren't touched. The
IsVisible guard stays as a safety net.
- Fix the global refresh propagation: it set .eventcache on the base frame,
but OnUpdate reads/clears it on the overlay (nameplate) -- so the propagation
was a no-op and global refreshes (e.g. target-change alpha/strata) fell
through to the 0.5s catch-all timer. Set plate.nameplate.eventcache and scope
to visiblePlates, so those refreshes land on the next tick as intended.
OnConfigChange still iterates the full registry (hidden plates must pick up
config changes before they next show).
Both mouseover checks relied on original.glow:IsShown() (a fragile proxy for
the hovered plate, on a Blizzard texture pfUI hides/restyles) gated by
UnitExists("mouseover"). Cache the engine mouseover unit's GUID once per
central-loop tick (frameState.mouseoverGuid) and have each plate compare its
cachedGuid against it.
More correct for the data path: the "mouseover" unit token is only chosen
for the plate whose GUID actually matches the engine's mouseover unit, so
overlapping plates can't double-match and paint the wrong unit's data. Also
cheaper -- one UnitGUID("mouseover") per tick plus a per-plate table compare,
versus the old per-plate IsShown gated on a per-tick UnitExists.
The unthrottled target castbar frame called C_NamePlate.GetNamePlateForUnit
every frame. Stash the target's GUID on PLAYER_TARGET_CHANGED (seeded on
PLAYER_ENTERING_WORLD for a target held across reload) and resolve the plate
through plateByGuid instead -- a table lookup plus a real idle skip when
there's no target. The plate is looked up per frame (not cached) so a plate
spawning/despawning while the unit stays targeted still resolves correctly.
Previously every visible plate polled C_Spell.UnitCastingInfo each throttled
tick just to detect casts. Now cast state is event-driven:
- SPELL_START_OTHER (nampower) stamps a per-GUID castState cache (spellId,
timing, channel flag from spellType); SPELL_FAILED_OTHER clears it; normal
completion expires at endTime. GetCastInfo just reads the cache, so all
call sites (castbar update, non-target detection, casting name-color) stop
polling.
- plateByGuid routes events to plates in O(1) and bounds the cache to on-screen
casters; NAME_PLATE_UNIT_ADDED seeds an already-casting unit with one poll.
- The vestigial nameplate.castUpdate flag now fires on cast start to bypass the
throttle for an immediate bar.
With the poll gone, per-tick work is a cache read + SetValue, so the dedicated
target castbar frame now runs unthrottled (every frame) for the smoothest
sweep, and the non-target nameplates_castbar default rises 50 -> 100 FPS.
Known limits: other-unit cast pushback isn't reported by nampower, and a
mob-cancelled channel with no fail event lingers until endTime.
The cursor-follow OnUpdate polled GetCursorPosition() 10x/second forever,
even with no tooltip visible. Rework it so the follower frame is created
once, hidden, and only shown while a tooltip is up -- an OnUpdate fires only
while its frame is shown, so the poll now runs solely during tooltip display.
Position the follower immediately on show to avoid a one-frame flash.
Also bump the tooltip_cursor throttle default from custom/10 FPS to the
fastest preset (50 FPS) so cursor tracking is smooth out of the box while
the Throttling tooltip knob stays available for low-end machines.
BigWigs (SpellRequests) redefines the global string:split to return a table,
and another addon clobbers the strsplit global too. pfUI.api.strsplit and the
bare strsplit callers inherited the broken versions depending on load order,
producing 'attempt to compare number with nil' from GetStringColor.
- Make pfUI.api.strsplit fully self-contained (no delegation to global
strsplit / string.split), so no override can reach it.
- Optimize the hot path: single-char delimiters use plain-text find (no
pattern compilation, no per-call char-class string); localize string.find
and string.sub.
- Route the per-frame castbar/nameplate color splits through GetStringColor,
which caches, instead of re-splitting a constant string every update.
C_UnitAuras supplies Turtle-adjusted durations directly to the aura readers,
so the L["debuffs"] database fallback for these four custom debuffs is dead.
libbagsort:Sort now accepts an opts table:
- reverse: place the first-ranked item into the last slot of the last
bag (junk fills from the opposite end)
- reversePrio: flip the category ranking (e.g. hearthstone sorts last)
Wired to two new checkboxes under Bags & Bank, both defaulting off.