Commit Graph
100 Commits
Author SHA1 Message Date
Brues a96a3dbd87 Show the About page's values inside the panel
The values anchored to the content's TOPRIGHT, which now spans the whole
panel since the panels moved to pfUIScrollFrameTemplate, so they sat 375px
past its right edge and the scroll frame clipped them. Anchor them to the
content's TOPLEFT, where the labels' offsets already measure from.
2026-10-30 11:45:08 -05:00
Brues a721f788a9 Let each cooldown frame apply its own sweep settings
The sweep options reached the frames only through the cooldown module's
CooldownFrame_SetTimer hook, which set alpha 0 for a hidden animation and
SetReverse(true) for a reversed one. With that module disabled, Show Timer
Animation and Reverse did nothing, and reverse never turned back off
without a reload. The player buff sweeps had no other path at all.

Each owner now configures its own frame. Player buffs drive their
Cooldown with SetCooldown and Clear, and only while Show Timer Animation
is on. Unit frame auras, raid aura indicators and nameplate debuffs hide
the sweep with SetDrawSwipe and SetDrawBling. Every frame with a reverse
option calls SetReverse. The hook keeps the timer text and Hide Foreign
Cooldown Animations.

Nameplate debuffs hid the sweep with alpha, which the plate's SetAlpha
copied down on every target change (ClassicAPI keeps 1.12's alpha under
default plates), so hidden sweeps came back. Their settings now come from
UpdateDebuffConfig alone.

Bag slots get a native Cooldown like bank slots, in place of the
template's indicator Model, so Reverse Cooldown Animation covers bags and
Hide Foreign Cooldown Animations no longer hides them.

The buff, unit frame, nameplate and totem options apply without a reload.

Fixes #72.
2026-10-30 10:46:00 -05:00
Brues f33a9068f2 Translate the Self quick button
Classic Era dropped QUICKBUTTON_NAME_SELF and its tooltip, so every locale
showed 4.3.4's English text. Each locale now has its own, worded after its
My actions strings.
2026-10-30 03:13:38 -05:00
Brues 187e057923 Add the Combat Log settings window and 4.3.4's default filters
"Settings" in the Combat Log tab's menu opens Blizzard's Combat Log page,
rebuilt in Lua with pfUI widgets: the filter list with copy, add and
delete, and the Message Sources, Message Types, Colors, Formatting and
Settings tabs. Combat Log Defaults restores the default filters. Closing
the window refills the log only when the active filter changed.

The log now records while ChatFrame2 is hidden, so switching tabs no
longer loses history.

The default filters are 4.3.4's: Self, Everything, What happened to me?
and Kills. Classic Era's My actions left out auras.
2026-10-30 03:02:42 -05:00
Brues 3899004957 bump CAPI min to 1.18.05 2026-10-30 02:19:25 -05:00
Brues ee73cde828 Replace the combat log with Classic Era's Blizzard_CombatLog
ChatFrame2 now shows the 4.x combat log, ported from Classic Era's
Blizzard_CombatLog as the combatlog module. ClassicAPI's COMBAT_LOG_EVENT,
CombatLogAddFilter and kept entries drive it, and changing filter refills
the frame with BackFillMessage. Quick buttons sit above the log, and its
unit, spell, raid icon and action links open Blizzard's filter menus.
Filters save account-wide in pfUI_combatlog.

The 1.12 CHAT_MSG_COMBAT_* and CHAT_MSG_SPELL_* events no longer reach
ChatFrame2. Its message groups stay saved, so disabling the module brings
the text log back.

env/combatlog_<locale>.lua hold the Classic Era GlobalStrings the log reads
that 1.12 lacks.
2026-10-30 02:19:08 -05:00
Brues 76efd5b490 Pass every AddMessage argument through the chat hook
The hook forwarded the text and seventeen named arguments. It takes them
as ... now, which ClassicAPI rewrites to unpack(arg) with arg.n, so every
argument reaches the frame's AddMessage, nils included. GetMessageInfo
returns all of them, and a hook that drops some loses them for every
frame after it.
2026-10-30 01:25:36 -05:00
Brues 393b37b8c3 Add an option to show timestamps in chat copy
"Show Timestamps In Chat Copy" (chat text settings, on by default) is read
each time chat copy opens. With it off, chat copy takes each line through
pfUI.chat.StripTimestamp, which removes the exact timestamp AddMessage
prepends: the configured color and brackets, so other bracketed text
stays.

Whispers are now recolored before the timestamp goes on, not after. The
recolor turned the stamp's closing "|r " into "|r" plus the whisper color,
so whisper lines did not match. The shown text and the saved history come
out the same.
2026-10-30 01:25:36 -05:00
Brues 7928db33bf Show the equipment manager's lists in ClassicAPI ScrollBoxes
The set list, the unused-items grid and the trigger list each drew a
fixed number of slots and moved them with a mouse-wheel offset. All three
are pfUIScrollBoxListTemplate lists now, with a scroll bar.

The set list's rows are the sets and then the "New Set" entry, set up by
one initializer. Refresh rebuilds them only when the sets change and
otherwise reinitializes the shown rows, so a row keeps its frame between
the two clicks of the native OnDoubleClick that now equips a set, in
place of the hand-timed double click. PickupEquipmentSet is called
without a presence check.

The row's gear passes mouse motion on to the row, so moving onto it no
longer leaves the row and the MouseIsOverHitRect workaround is gone. The
gear rests at half alpha and lights up on hover with an additive copy of
itself, instead of the blue square highlight.

The unused items are a grid view that keeps its scroll position on
refresh. The trigger list scrolls to a new trigger with ScrollToEnd.
2026-10-30 01:07:22 -05:00
Brues 5d8314b2d4 Scroll the config panels with ClassicAPI's scroll bar
pfUIScrollFrameTemplate (api/ui-widgets.xml) is a ScrollFrame with
pfUIScrollBarTemplate inside its right edge and a Content scroll child,
driven by ScrollUtil.InitScrollFrameWithScrollBar. The panels keep a
ScrollFrame because the engine measures its range from the shown frames
inside the content, which the hand-placed config rows rely on. The bar
follows OnScrollRangeChanged instead of an OnUpdate poll per panel.

A panel measures its range once its rows are built, and the search
results measure theirs with UpdateScrollChildRect instead of hiding,
re-attaching and scrolling a frame later. Clearing the search measures
again too.

CreateScrollFrame and CreateScrollChild have no users left, and nothing
called CreateTabFrame or CreateTabChild; all four are gone. pfQuest's
compat layer brings its own copies of the scroll helpers.
2026-10-30 00:51:16 -05:00
Brues 3987cfb8a0 Show loot history and the icon picker in ClassicAPI ScrollBoxes
pfUIScrollBoxListTemplate (api/ui-widgets.xml) is a WowScrollBoxList with
pfUIScrollBarTemplate beside it, 5px inside the frame, so a list's
backdrop goes on the frame itself. The addon list moves onto it and drops
the sibling backdrop frame.

Loot history builds a flat list of item rows and, under an expanded item,
player rows; the view sizes and indents them. The item frame array, the
player pool, the hand-placed rows and the scroll child re-attach are gone,
and the scroll position holds across roll updates.

The icon picker's FauxScrollFrame and 80 fixed buttons become a grid view
over the provider's icon indices. Picking an icon reinitializes the shown
buttons instead of redrawing the grid, and closing the popup drops the
grid's entries along with the provider.
2026-10-30 00:47:07 -05:00
Brues 796f20eadf Show chat copy and the share box in ClassicAPI's scrolling edit box
pfUIScrollingEditBoxTemplate (api/ui-widgets.xml) is ScrollingEditBoxTemplate
with pfUIScrollBarTemplate beside the text. Its mixin's SetFont gives the
box a font object of its own, since the edit box sets its font by object on
every SetText and pfUI fonts are paths. ui-widgets.lua now loads before the
XML, which needs the mixin.

The share box no longer recomputes its scroll child on every text change.

Chat copy reads each chat frame's messages with ClassicAPI's
GetMessageInfo when it opens, so the AddMessage hook, its 100-line copy of
the history and the skip of combat frames are gone. It shows every line
the frame keeps, lines from before login included. It puts the caret at
the end to scroll to the newest line instead of three OnUpdate passes, and
Escape now leaves copy mode on every chat frame.
2026-10-30 00:38:54 -05:00
Brues 26c55bddb0 Drive the rez icon from ClassicAPI's incoming resurrections
ClassicAPI's UnitHasIncomingResurrection sees every resurrection cast in
view from SMSG_SPELL_START and ends it with the cast, and
INCOMING_RESURRECT_CHANGED signals each change. libpredict's own
tracking goes: the ress tables and 60s timers, parsing Resurrection and
RES/RESNO messages, clearing on UNIT_HEALTH, and the rez branches of the
cast handlers, along with UNIT_SPELLCAST_STOP and the foreign cast
targets only rez used. libpredict still announces the player's own
resurrections to HealComm and CTRA for clients without ClassicAPI.

The native state ends with the cast, so a rez that landed would vanish
before the target accepts it. libpredict records the SPELL_RESURRECT
combat log event per GUID and keeps the offer until the target is alive
again or the 60s accept popup runs out, unlike Grid2, which cannot tell
a landed rez from a cancelled one. UnitHasResurrection answers both.

The unit frames show the icon in RefreshIndicators, refreshed by
INCOMING_RESURRECT_CHANGED on the frame's own unit and by libpredict for
offers, instead of polling libpredict every 0.1s.
2026-10-29 23:49:28 -05:00
Brues 8c22fdb850 Show the addon list in a ClassicAPI ScrollBox
The list built one row per addon in a ScrollFrame, hid the rows out of
view by hand and drove pfUI's own slider. A WowScrollBoxList keeps rows
only for the addons in view and recycles them while scrolling.

pfUIScrollBarTemplate (api/ui-widgets.xml) is a ScrollBarMixin bar with
the old slider's look: a thin track and a flat teal thumb that hides when
nothing scrolls. Its steppers exist for the mixin but stay hidden.

Rows read the addon's enabled state, so profiles enable and disable
addons directly instead of clicking every row's checkbox. A checkbox
now shows whether an addon is enabled; it showed whether it was loaded.
2026-10-29 23:04:47 -05:00
Brues 2d275c53fa Clear AFK on wake only while it is still set
/afk toggles. The overlay sent it on every click and key press, so when
the client or the server had already cleared AFK it set it again. Check
ClassicAPI's IsChatAFK first. It follows the client's /afk state at
once, where UnitIsAFK waits for the server's flag update.
2026-10-29 22:40:30 -05:00
Brues ba752056ad Stop the AFK camera when AFK clears while it spins
62e0d4eb stopped the camera only while the start delay was shown, but the
delay hides itself once the camera starts. AFK cleared during the spin
left the overlay up, and the next key press sent /afk and set AFK again.
Stop when the camera spins instead, which also keeps stop() and its
SetView(4) from running when AFK clears before the camera started.
2026-10-29 22:40:26 -05:00
Brues 1ccf87c61a Drop CreateGoldString's fallback for clients without coin icons
ClassicAPI always provides GetCoinTextureString, so the presence check
and the hand-built "g/s/c" string after it never ran.
2026-10-29 22:39:01 -05:00
Brues dc89f01dda Refresh the FPS & Lag panel tooltip while it is shown
The tooltip was built once on enter, so its memory, network and addon
figures stayed at the values from that moment. The widget's one-second
ticker now rebuilds it while the panel still owns GameTooltip. The
tooltip takes its owner as self, not this, so the ticker can call it.
2026-10-29 22:24:36 -05:00
Brues a917a70b1c Add an expand/collapse-all button to the Reputation tab
ClassicAPI 1.18's ExpandAllFactionHeaders and CollapseAllFactionHeaders
set every header in one call. The button sits on the "Faction" label's
row, in the headers' +/- column, and shows "+" while any header is
collapsed.
2026-10-29 22:24:36 -05:00
Brues 03ea3d5aa5 Clear cancelled resurrections announced over HealComm and CTRA
ParseChatMessage tested the raw message for "RessStop", which no
HealComm message is, so ParseComm's Resurrection/stop/ result was never
acted on. CTRA's RESNO was matched against the whole message, but it
carries the target's name, and the CTRA parser only recognised RES. A
cancelled rez from another player left the icon on the target's frame
until they were revived. RessStop still keeps a rez whose cast landed
and whose accept window is open.
2026-10-29 18:58:15 -05:00
Brues 2b4d33d46a Keep libpredict tracking casts when HealComm is loaded
With HealComm-1.0 present (HealComm, LunaUnitFrames, NotGrid), the
compat hook unregistered every event on libpredict.sender to stop pfUI
broadcasting a duplicate of the player's casts. Since cast detection
moved onto that frame's UNIT_SPELLCAST_* handlers, that also blinded
libpredict to every cast, so the player's own resurrections never put
the rez icon on the target's frame: ParseChatMessage drops the player's
own Resurrection echo, expecting the cast events to have tracked it.

The compat hook now only mutes the outgoing HealComm and CTRA messages.
The HealComm.SendAddonMessage hook that parsed the player's own messages
while solo goes too, since libpredict tracks its own casts directly.
2026-10-29 18:56:51 -05:00
Brues 94dcfb3dad Compute bar layout options instead of tabulating pfGridmath
pfGridmath listed every exact cols x rows grid per bar size; the only
live use was seeding the layout dropdown, which a divisor loop now does.

Everything else that read it was unreachable: every layout option is a
"cols x rows" string, so ResolveBarLayout's BarLayoutFormfactor lookup
never matched, and the actionbar module resets a stored layout that
isn't one before BarLayoutSize/BarButtonAnchor run, so their fallbacks
never fired. BarLayoutFormfactor and env/tables.lua go with it.
2026-10-29 14:16:43 -05:00
Brues 0890905eb6 Drop named-unit frames and the pfValidUnits global
CreateUnitFrame and CreateCastbar could take a character name instead of
a unit token, but every caller passes a token, so that mode and the
empty-label guards around it were dead. The OnClick TargetByName branch
could never run anyway: label was always a string, and "" is truthy.

With that gone, the aggro scan was the only reader of pfValidUnits, so
its unit list now lives in unitframes.lua and is built once at load.

/focusname resolves the name with UnitTokenFromName (exact first, then
prefix) instead of swapping the target and muting UIErrorsFrame.
2026-10-29 14:00:54 -05:00
Brues 6c76a88467 Draw unit frame bars with the native StatusBar
The custom bar sized its textures in Lua from GetWidth(), so it had to
track how ClassicAPI reports the UI scale (#71), and it stretched the bar
texture instead of cropping it. The engine's StatusBar does that layout
itself.

The background of the missing part is a second StatusBar that fills
from the other end (ClassicAPI's SetReverseFill), one frame level below
the fill, so the incoming-heal texture still draws between the two.
SetFrameLevel moves both, and the health and power bars now sit two
levels above their backdrops. SetStatusBarBackgroundTexture becomes
SetStatusBarBackgroundColor, as it only ever took a color.
2026-10-29 11:06:02 -05:00
Brues 35c32f365e Remove scaledsize fix 2026-10-29 10:38:28 -05:00
Brues 6dcb5162be bags: use ClassicAPI's native item search
C_Container.SetItemSearch drives the search box; slots dim from isFiltered on INVENTORY_SEARCH_UPDATE and on each slot update. Matches name, subclass, equip slot and equipment set names. Drops the tooltip-scanning full-text option.
2026-10-29 10:35:28 -05:00
Brues 8afafe71cf Drop RunMacroText fallback and dead actionbar spellslot branches
ClassicAPI 1.18 provides RunMacroText natively; bump CAPI min to 1.18.0. spellslot/booktype/scanmacro were only ever cleared, never set.
2026-10-29 10:35:28 -05:00
Brues 99ed538ee7 bump CAPI min to 1.17.10 2026-10-29 10:35:28 -05:00
Brues f382039f21 panel: show per-addon memory in FPS & Lag tooltip
Use UpdateAddOnMemoryUsage/GetAddOnMemoryUsage like Blizzard's MainMenuBar performance tooltip: total addon memory plus the top 3 consumers, formatted with the TOTAL_MEM/ADDON_MEM global strings. Guarded so the tooltip falls back gracefully on clients without these APIs.
2026-10-29 10:35:28 -05:00
Brues 8307dd757e Bump ClassicAPI minimum 1.17.4 2026-10-29 10:35:28 -05:00
Brues 5ab5337bbc Keep status bars true to their value on ClassicAPI 1.18.0
ClassicAPI 1.18.0 divides the UI scale out of an anchored frame's
GetWidth/GetHeight, as 4.3.4 does. The status bar still divided it out
itself, so under a UI scale below 1 the bar emptied early (at 29% health
on a 0.71 scale). Only divide on older ClassicAPI.

Fixes #71
2026-10-29 10:33:55 -05:00
Brues d15b1cd320 Only declare RunMacroText if it doesn't exist 2026-10-28 12:21:34 -05:00
Brues fe376b8e51 Keep buff timer text above the cooldown sweep 2026-10-25 20:32:49 -05:00
Brues e326cea05e Add radial sweeps to buff/debuff icon display 2026-10-25 20:26:45 -05:00
Brues d9e74b3bc5 Add per-frame reverse toggle for cooldown sweeps 2026-10-25 20:24:12 -05:00
Brues 0e428de74c Fix inactive nameplate alpha capped at 99%
CacheConfig clamped notargalpha to 0.99, so a 100% setting never reached full opacity. Clamp to 1.0 instead and detect the target plate by GUID comparison only, removing the alpha-based fallback that required the headroom.
2026-10-25 20:00:50 -05:00
Brues df21f1f26c stable: capture per-iteration button in Enable/Disable closures
Lua 5.0 shares the generic-for loop variable across iterations, so the
closures all saw the final (nil) value.
2026-10-25 12:12:05 -05:00
Brues bd7bf3f39f Size aura cooldown text to its laid-out icon
Unit frame aura icons only get their size from the layout pass, but
RefreshUnit started their timers before it ran. A cooldown text created
on a never-laid-out icon saw a height of 0 and fell back to a 16pt font,
so some buff and debuff timers overflowed their icons. (Closes #64)

- RefreshUnit stores each aura's timer and starts them after
  LayoutAuras, once every icon has its final size.
- With dynamic size on, the cooldown text re-sizes its font whenever
  its frame's height changed since the last SetTimer, so icons that
  switch between the normal and own aura size keep a matching font.
2026-10-25 11:48:24 -05:00
Brues 987c337709 Revert "Remove CLINK parsing"
This reverts commit c03b74c3d1.
2026-10-25 11:39:39 -05:00
Brues cdaf9336a3 nameplates: skip destroyed plates on PLAYER_LOGOUT
Leaving the world destroys the units before PLAYER_LOGOUT fires, so some
plates in the registry are already dead: their overlay buttons dropped
their events and lost their methods, and UnregisterAllEvents errored as a
nil method. Only silence the plates that are still alive.
2026-10-25 11:33:51 -05:00
Brues e4f0849816 Pass the draw layer as CreateTexture's second argument
CreateTexture("OVERLAY") names the texture "OVERLAY" and leaves it on
the default ARTWORK layer. Pass nil for the name so the layer applies.
2026-10-25 11:21:01 -05:00
Brues c03b74c3d1 Remove CLINK parsing 2026-10-24 23:23:16 -05:00
Brues e583ac1e83 Drive mapreveal from MAP_EXPLORATION_UPDATED and C_MapExplorationInfo 2026-10-24 21:33:12 -05:00
Brues 964c316cdc Replace Nampower combat events with ClassicAPI events
Read casts from UNIT_SPELLCAST_* and heal amounts from
COMBAT_LOG_EVENT_UNFILTERED in libpredict, swingtimer and the prowl
page switch. Drop libdebuff's AURA_CAST/DEBUFF_* bookkeeping and hook
tables, which nothing read: its aura readers already use C_UnitAuras.
Drop the unitframe BUFF_ADDED_OTHER start-time tracker, since
C_UnitAuras stamps an expiration for auras seen being applied.
2026-10-23 21:27:28 -05:00
Brues b73ed88346 Split chat faction and race icons into separate options 2026-10-23 19:33:08 -05:00
Brues b374a0914a Color equipment flyout borders by item quality 2026-10-23 17:27:37 -05:00
Brues aaddeb120a Drive innervate callout from the combat log
Read Innervate casts from COMBAT_LOG_EVENT_UNFILTERED instead of
Nampower's AURA_CAST events, taking the target name from the event.
Schedule the ready callout once Innervate's own cooldown appears on
SPELL_UPDATE_COOLDOWN; at cast time GetSpellCooldown reports only the GCD.
2026-10-23 17:03:23 -05:00
Brues 1ad78d43e9 nil 2026-10-23 16:21:05 -05:00
Brues dde4a5aa2d Merge chat class color and player level passes into one loop 2026-10-23 12:51:04 -05:00
Brues e7e1382693 Port unitframe and actionbar performance fixes from Duciel/pfUI@45f8ca6
Unitframes: only set the unit attribute when it changes, poll group frame indicators every 3s since events already cover them, and limit full refreshes on eventless frames to unit changes or once a second.

Actionbar: handle UNIT_INVENTORY_CHANGED with a light count/cooldown/usable pass instead of a full update of every button, and skip hidden buttons in the cached passes, catching them up once they become visible.
2026-10-23 12:39:07 -05:00
Brues 6aaf4f2047 Read chat sender and channel from GetCurrentChatEvent
Pull the CHAT_MSG payload once per line instead of decoding it from the
rendered text. The sender match uses arg2 rather than the name resolved
from the GUID, and channel shortening only runs on CHAT_MSG_CHANNEL with
a single anchored replacement in place of the backtracking pattern.
2026-10-23 03:12:42 -05:00
Brues 233d0dafc0 one more bump 2026-10-23 03:09:07 -05:00
Brues 95e832835e Color chat senders by GUID and drop the who-search option
The engine name-queries a chat sender before the CHAT_MSG event fires,
so GetPlayerInfoByGUID(GetCurrentChatGUID()) resolves every player
sender, including strangers in world channels. Other names in the text
still fall back to C_PlayerCache.GetPlayerInfoByName and libunitscan.

This makes the silent /who lookup for unknown classes obsolete; remove
it along with its FriendsFrame_OnEvent wrapper, config default, GUI
entry and translations.
2026-10-23 02:40:08 -05:00
Brues cae7c2d68a Guard unusable bag refresh against the bank frame not existing yet 2026-10-23 02:39:42 -05:00
Brues 8abde7beba Classify nameplate units with UnitSelectionType instead of bar color 2026-10-23 02:06:43 -05:00
Brues fbb0c4c254 Refresh unusable bag tint when item requirements change 2026-10-23 02:00:27 -05:00
Brues 42e950639f Rotate bar textures on vertical XP, rep and nameplate health bars
Vertical StatusBars stretched the bar texture's shading along the wrong axis. ClassicAPI's SetRotatesTexture turns it with the bar.
2026-10-22 23:56:51 -05:00
Brues e70c8025c2 Draw the ranged swing timer as a native center-fill StatusBar
The ranged bar faked its fills with three textures resized and re-texcoorded every frame. A StatusBar with ClassicAPI's SetFillStyle covers all of it: CENTER for hunters (reload drains to the middle, the deadzone grows back out in the warn color), STANDARD otherwise.
2026-10-22 23:43:44 -05:00
Brues 8f1322d71c Skin ClassicAPI's Interface Options frame and its option controls
SkinCollapseButton's icon no longer takes the mouse, so clicks reach the button under it.
2026-10-22 23:34:08 -05:00
Brues 935b854073 Remove dead buffdetect setting 2026-10-22 23:25:59 -05:00
Brues df16988d3f Flow unit frame auras: debuffs first on enemies, own size, spacing
Unit frame aura icons are placed by a layout pass after each aura
refresh instead of a fixed grid set at setup. Rows are as tall as
their largest icon, and the debuff block no longer uses the debuff
size to step past buff rows. (Closes #25)

- When buffs and debuffs share a corner, debuffs come first on
  neutral and hostile units.
- New Own Buff Size / Own Debuff Size (-1 = normal size) enlarge the
  player's auras, which C_UnitAuras already lists first.
- New Buff/Debuff X-Spacing and Y-Spacing set the gaps between icons
  and rows; the default of 1 keeps the old look.
- The selfdebuff tooltip remap is gone from unit frames and buffwatch:
  own auras lead the list, so the HARMFUL|PLAYER index is the HARMFUL
  index.
2026-10-22 23:24:15 -05:00
Brues 627ab8b437 Use ClassicAPI's native Cooldown frame for cooldown sweeps
ClassicAPI 1.17.1 draws the Cooldown sweep natively, as 4.3.4's
CGCooldown, so pfUI's cooldowns no longer need the indicator Model.

- Action buttons, bank slots, totems, unit frame auras and icons, and
  nameplate debuffs are Cooldown frames with CooldownFrameTemplate.
- The plain-Frame fallbacks for disabled animations are gone; the hook
  already hides the sweep with alpha 0.
- The 36px scale-and-inset realign only runs on stock indicator Models.
2026-10-22 22:47:18 -05:00
Brues c36184081a Bump min ClassicAPI to 1.17.1 2026-10-22 22:19:35 -05:00
Brues d881d73246 Use region:IsMouseOver instead of the MouseIsOver global 2026-10-22 21:37:50 -05:00
Brues b9f473c049 Test hover against the hit rect, not the bare frame rect
CreateBackdrop widens a frame's hit rect by the border, so mouse focus
and OnEnter/OnLeave cover a band that plain MouseIsOver reports as
outside. Add MouseIsOverHitRect, which passes the frame's hit rect
insets as MouseIsOver offsets, and use it at the unpadded call sites.
The XP bar's hover text follows the same OnEnter/OnLeave flag as its
fade.
2026-10-22 21:36:27 -05:00
Brues 9cb21b982b Use ClassicAPI animation groups for fades and pulses
Replace hand-rolled OnUpdate alpha loops with engine animation groups:
unit frame aggro/combat glow, tracking icon pulse, new-mail blink,
castbar fade-out, autohide, XP/rep bar fade, info box timeout and the
unlock-mode scale label. Timing no longer depends on framerate, and the
always-running glow driver and tracking OnUpdate are gone.

The XP bar tracks hover through OnEnter/OnLeave instead of MouseIsOver,
so a fade can't restart under the cursor.
2026-10-22 21:28:34 -05:00
Brues 65ad47e842 Bump min ClassicAPI to 1.17 2026-10-22 21:10:39 -05:00
Brues 50aba54289 castbar, nameplates: update the shield on mid-cast interruptible changes 2026-10-22 21:09:17 -05:00
Brues d58060b188 hoverbind: Escape clears every key bound to the button 2026-10-22 21:00:01 -05:00
Brues 340bd9be9e pagemaster: page modifiers via override bindings; hoverbind: warn on conflicts
The old pagemaster wipe block checked a setting that was migrated away and
never ran, so saved MOD-key bindings beat the paged action. Override every
key bound to ACTIONBUTTON1-12 for each enabled page modifier instead; the
saved binding set stays untouched.

Hoverbind now reports when a bind takes a key from another action, and when
an override masks the new bind.
2026-10-22 20:59:30 -05:00
Brues 1600ec333b game_menu: skin every menu button dynamically
Scan GameMenuFrame's children on load and on each show instead of a
hardcoded list, so buttons added by other addons (ClassicAPI AddOn
Options, MoveAnything, MCP, SuperMacro, HardcoreDeath, Turtle shop) get
skinned without per-addon calls.
2026-10-22 18:41:47 -05:00
Brues 471c5713c5 use GetPhysicalScreenSize to get Screen Resolution 2026-10-19 20:20:03 -05:00
Brues 5d38322b2e stable: skin every stabled pet slot the client creates 2026-10-19 01:33:09 -05:00
Brues 40dd9dd6db equipmentmanager: drag a set onto the action bars via PickupEquipmentSet 2026-09-27 00:13:27 -05:00
Brues c1eba4d295 Bump CAPI min to 1.15.10 2026-09-25 10:58:07 -05:00
Brues cbc531531c swingtimer: drive the timers from ClassicAPI's PLAYER_SWING
Replaces the Lua reconstruction of the swing clock (AUTO_ATTACK_SELF with
an extra-attack guess, parry-haste maths, Slam freeze, DBC-based reset
checks on every SPELL_GO including triggered spells). PLAYER_SWING tracks
the server's clock from packets, so triggered casts, extra attacks, parry
haste, weapon swaps and the ranged wind-up all match the server.

Refs #58
2026-09-25 10:56:01 -05:00
Brues b57fc52f9a libbagsort: group gear by equip slot
Gear was split into four quality tiers and ordered by class, subclass
and name inside each, so a helm could sit between two pairs of boots.
Gear is now one category, ordered by class, then equip slot, then
subclass, then best quality first, then name.

The slot ranks come from a table keyed by the equipLoc token, not the
token's alphabetical order (which puts chest before head) or its INVTYPE
number (which parts robe from chest). The order matches ClassicAPI's
C_Container.SortBags and comes from Baganator.
2026-09-25 10:24:09 -05:00
Brues 8268af915a energytick: let the five-second rule display as a separate bar or the old shade
The shading bar sat on top of the power bar's own fill and shrank from the
right as the window drained, which reads as part of the mana bar rather than
a countdown over it -- the same problem nameplate castbars solve by living
below the health bar instead of on it.

Show Five Second Rule Style now picks between that: a real StatusBar below
the power bar, backdrop and all, or the original translucent overlay for
anyone who prefers it. The style is read once at module load like the other
structural toggles here, not swapped live -- a /reload picks up a change.
Height and color are only meaningful for the separate-bar style, so the
GUI's Five Second Rule Height and the new appearance.castbar.fsrcolor picker
apply to it alone; the overlay is unchanged from before.

The sweep spark's own dim/solid signal (fsrEnd/fsrGain) is untouched by any
of this -- it keeps dimming through an unconfirmed window regardless of
which display, or none, is shown.

Energy/Mana Ticks -- including both five-second-rule settings -- moves from
Unit Frames > General to Unit Frames > Player, right after Powerbar: they
are C.unitframes.player-only, so General was never the right tab, and
Powerbar is the setting group they actually belong to.
2026-09-22 21:19:12 -05:00
Brues 1108746949 castbar: mark a cast that none of your interrupts can stop
ClassicAPI carries notInterruptible on C_Spell.UnitCastingInfo (arg 8) and
UnitChannelInfo (arg 7); both call sites already stepped over it positionally.
The target, focus and nameplate bars now paint it.

There is no shield art to hang on the icon -- this client ships three
CastingBar textures (Border, Flash, Spark), and Blizzard's shield arrived in
3.x -- so the state is carried by color instead: the bar and the icon's
border both take a new appearance.castbar.shieldcolor, which keeps it legible
on a bar configured without an icon. One api helper paints both so the two
modules cannot drift apart on it.

Dusty rose (.8,.45,.45) rather than a steel grey. Grey is the retail
convention, but pfUI's casting color is already a near-grey pale blue, so the
two separated on brightness alone and washed out against the bar texture. The
rose sits above failcolor in luminance and well below it in saturation, and
the two are further told apart by motion: a failed cast flashes the bar full
and fades, a shielded one fills normally and tints the icon border, which
failcolor never touches.

Not on the player's own bar. The flag is measured against the reader's own
interrupts and silences, so on your own cast it answers a question nobody
asked.

Stamped once at cast start, with the rest of the cast data, rather than
re-polled. ClassicAPI evaluates lazily from the caster's live auras but does
not yet fire the 3.3.5 UNIT_SPELLCAST_INTERRUPTIBLE / _NOT_INTERRUPTIBLE
change events, and polling per frame would undo the event-driven rework these
bars just got -- so a caster who bubbles mid-cast keeps the bar it started
with until the next one.

Two limits worth knowing when it stays dark. The value is relative to your own
kit -- with no interrupt and no silence known it is never reported, whatever
the cast -- and a creature's built-in interrupt immunity lives in
creature_template, which 1.12's SMSG_SPELL_START does not carry, so a boss
with that immunity reads interruptible while your kick still fails on it.
2026-09-13 15:19:42 -05:00
Brues 38b4100353 server agnostic <3 2026-09-12 04:36:27 -05:00
Brues f497c71523 Filter unit events to their units, rework energytick, drop macrotweak
Eight commits off classicapi_next.

ClassicAPI's RegisterUnitEvent registers for an event but only delivers it
when arg1 is one of the given units, so a handler for one unit stops waking
for every other one in the world. The 26 registrations whose unit set is
fixed at registration time now name it. The rule throughout is register the
superset and keep the handler's own check -- the filter narrows what arrives,
it does not decide what to act on. Guards that look unreachable stay put: the
filter applies only when arg1 is a string, so an event that fires with a
number or no argument is delivered as if plainly registered.

Frames whose unit changes at runtime own their subscriptions instead of
sorting events out per event. unitframes points them at the unitstr
UpdateVisibility already computes -- replacing a string concat, and on a miss
a second concat plus a UnitGUID call, for every frame on every unit event in
the world -- and a frame that is not in use drops its unit events entirely.
nameplates registers per plate against the plate's own token, which is also
the only workable shape: slots have no cap, so any nameplate1..N list would
have been a guess that fails in exactly the crowded scenes where plates
matter. marktracking names mark1 through mark8. A registration keeps its
kind, so none of these can be plain-registered first.

Both teardown paths PLAYER_LOGOUT guards -- the crash 132 -- now cover the
per-frame subscriptions: plates tear down rather than dispatching through
logout, and a unit frame takes itself off the visibility scan so it cannot
re-register what it just dropped.

marktracking also drops its once-a-second full rebuild, which ran for the
whole session whether or not a marker existed anywhere. The ticker is created
and cancelled with group membership. It is deliberately not keyed on a mark
being visible -- a marker on an out-of-range unit shows no row, and that is
the case the poll exists to catch.

nameplates gates the per-plate update against the floor across all four
throttle categories before classifying it, instead of running a GetAlpha, a
castbar IsShown, a cast lookup and up to two libthrottle:Get resolutions on
plates throttled to 10fps that were going to return anyway. Nothing that
would have updated can be turned away by a floor. The four throttles resolve
in CacheConfig, where config changes already land.

energytick sweeps the clock the server actually runs. There is one regen
timer for every power, re-armed every 2s by Player::RegenerateAll and never
touched by casting; the five-second rule changes what a tick pays, not when
it lands. The sweep is a free-running phase lock on that clock, so
Illumination refunds, potions and a Mana Spring totem on its own phase no
longer snap the spark mid-cycle, and an 80ms band keeps a correct tick from
hitching it at the wrap. The FSR window shades rather than predicting a share
of spirit the client cannot compute -- the Casting Regen item ladder is equip
auras absent from the buff list. The energy period is summed from
SPELL_AURA_MOD_ENERGY_REGEN_TIME across the spellbook and buffs, so Blade
Rush is found without GetTalentInfo(2, 16), an ordinal that does not fail
when the tree changes but reads another talent's rank.

macrotweak is gone -- ClassicAPI 1.15 covers it -- with its config entry, its
GUI block, its translations in all eight locales, and actionbar's
ButtonMacroScan, the #showtooltip scanner that fed it.
2026-09-11 00:10:51 -05:00
Brues 8e109a585a eqcompare: place the compare after the tooltip settles when Guda is loaded
Guda anchors its item tooltips with ANCHOR_NONE and its own SetPoint, and
the tooltip module then relocates every ANCHOR_NONE tooltip to its
configured spot. eqcompare picked left or right from GetLeft/GetRight
inside the Set* call -- against the position the tooltip was about to
leave -- so for an item in the left half of the screen the shopping
tooltips anchored to the right edge and rode the relocation off-screen
(vatichild/guda#41). Guda's own inventory block also widens the tooltip
after our hook has run.

With Guda present, resolve the link in the hook but hand the placement to
RunNextFrame, skipping it if the tooltip has hidden since. Guda lists
pfUI as a dependency, so it loads after us: gate on DoesAddOnExist and
flip the flag from ContinueOnAddOnLoaded. Without Guda the hook is
unchanged.
2026-09-09 13:23:53 -05:00
Brues 83f89be14a unitframes: compare indicator auras without case folding
The indicator scan lowercased the aura's name and icon on every aura of every
unit, and SetupBuffIndicators lowercased the other side to match. Both sides
come out of the same DBC records byte for byte, so the comparison already held
without any of it: the aura's name and icon are Spell.dbc's localized name and
SpellIcon.dbc's path (aura/Data.cpp fills one struct and emits it as either the
AuraData table or the positional UnitAura tuple), and the indicator record
reads those same two fields through C_Spell.GetSpellName / GetSpellTexture.

So this was two string allocations per aura per scan to reach a result it
already had -- and on Lua 5.0 that is an allocate-and-hash each time, since the
VM interns even when the string exists. RefreshUnit runs this for every unit
frame, which in a raid is 40 frames against up to 32 auras each.

Checked every rank of all 65 indicator spells in Spell.dbc: name and icon are
byte-identical across a spell's whole ladder, no case or spelling drift, so
nothing depended on the fold.

It also drops a crash path. aura/Data.cpp yields nil for the icon when a
spell's SpellIconID is 0, and icon:lower() would have errored on that; name was
guarded on the line above but icon never was. A raw compare is just false.

The equality is now load-bearing, so AddIndicator's comment says to feed it
spell ids and never a hand-written name or icon path -- a literal
"interface\icons\foo" in that table would silently match nothing.
2026-09-08 16:35:31 -05:00
Brues e59158764c unitframes: identify buff indicators by spell id, not icon path
Spell.dbc reuses icons freely across unrelated spells, so matching an aura on
its icon alone lit the indicator for the wrong buff. Blessing of Sanctuary
fired on Lightning Shield and Shadowguard, Blessing of Kings on Mage Armor and
Commanding Shout, and Totemic Power on the Blessed Sunfruit food buff -- the
last of which the code already carried a comment about. Fixes #55.

Each entry is now a spell id, resolved once into the aura's name plus its icon,
and a match needs both. Name alone is no better than icon alone: creature and
item auras reuse player spell names, so a mob's "Renew" or a trinket's
"Rejuvenation" would light a HoT indicator. Every rank of a spell carries the
same name and icon, so one id covers the whole rank ladder without listing it.

HOT_INDICATORS folds away with this. It existed to name-check exactly the three
HoTs whose icons were known to collide; that check is now what every entry
does, and the table's only remaining job -- naming a HoT's libpredict key -- is
an argument on the Renew, Regrowth and Rejuvenation entries.

Deriving the name from an id also picks up this client's renames for free. The
paladin blessings are "Hand of Freedom", "Hand of Protection" and "Greater
Blessing of Sacrifice" here, which hardcoded English names would have missed.

All 65 ids were checked against the client's Spell.dbc, SpellIcon.dbc and
SkillLineAbility.dbc: each resolves to the intended aura, with its full rank
ladder sitting on the icon the old list matched.

That check also turned up five entries matching no spell at all on this
client. ability_hunter_misdirection, spell_holy_prayerofmendingtga and
inv_misc_herb_felblossom are TBC leftovers and are gone.
spell_nature_giftofthewild was never right -- Gift of the Wild uses Mark of the
Wild's icon -- and is now covered by id 21849. ability_warrior_rallyingcry
hosts no Commanding Shout either, so that entry now points at the real spell
(45580) and works for the first time.

Unrelated to the above, this file's string.fn(s, ...) calls become s:fn(...).
2026-09-08 16:28:51 -05:00
Brues 863d99647d Draw the loot history and map overlays from real pools
Both modules had already grown a pool by hand. loothistory kept usedPlayers /
freePlayers tables with a recycle loop and a table.remove acquire; mapreveal
kept pfOverlays keyed by index, a pfOverlayMax high-water mark, and a
hide-the-tail loop. Replace both with ClassicAPI's Pools.lua backport, which
is what modules/tooltip.lua already uses for its buff icons.

The map explore icons change shape rather than just plumbing. Everything
constant -- size, scripts, mouse, frame level, the magnifying glass texture --
moves into the pool's creator instead of being re-set on every overlay on
every map update, and the acquire moves inside the visibility test. The old
loop built and configured an icon for every overlay in the zone and then hid
most of them again; with "mapexploration" off it built the whole set and hid
all of them. Now nothing is acquired for an icon that will not be shown.

The tile textures lose their pfRevealN names, since pools create anonymous
regions. They were already unique, so nothing was being clobbered -- this
only costs their labels in /fstack.
2026-09-07 18:41:54 -05:00
Brues 06b3ae8d22 cooldown: stop shadowing the global time()
`local time = time()` bound the timestamp over the top of the time function
inside that branch, so any later call in the same scope would have indexed a
number. Nothing did, but the name is a trap. Call it currentTime.
2026-09-07 18:39:28 -05:00
Brues 879d7afe83 cooldown: throttle before the work, not after
The 0.1s gate sat below the hidden-cooldown check, so every frame, for every
ticking cooldown, the update built "<parent>Cooldown" twice and did two _G
lookups with it. Lua 5.0 interns on every concat -- it allocates and hashes
even when the string already exists -- so this was allocating garbage at the
frame rate times the number of live cooldowns. A profiler run had it at 24s
of accumulated CPU.

Move the gate to the top so a non-tick frame costs one GetTime() and a
compare. The name lookup goes away entirely: pfCreateCoolDown already has the
cooldown frame, so it stashes the reference instead. That is also more
correct than deriving it from the parent's name, which silently skipped the
check for any cooldown not named "<parent>Cooldown".

The text frame and its fontstring were both created with a fixed name, so
every one of the hundreds in a UI clobbered _G.pfCooldownFrame and left it
pointing at whichever was made last. Name them after the cooldown they render
for, falling back to a counter for anonymous ones.

One behavior change: the hidden-cooldown check is now throttled too, so text
can linger up to 100ms after its cooldown frame hides. It only refreshes at
0.1s anyway, and expiry still runs through the remaining < 0 branch.
2026-09-07 18:14:33 -05:00
Brues 4eab494a01 Name the installed ClassicAPI version in the outdated popup
"cannot run on this ClassicAPI" named no version, so the player had nothing
to compare against the requirement, and it overstated the failure: pfUI does
load on an old DLL, it just throws wherever it reaches for an API that isn't
there. Put the installed version in the headline and soften it to "will not
work correctly"; the detail line then only has to state the requirement.
2026-09-07 17:55:56 -05:00
Brues bb8ce8fd63 apparently some addons can't figure out -1 for themselves 2026-09-05 07:19:58 -05:00
brues-code 75c4657ab9 ClassicAPI flavor TOCs, and drop the multi-client scaffolding (#53)
* nameplates: source totem icons from UnitCreatedBySpell

Read the totem's icon from the totem-drop spell (UnitCreatedBySpell +
GetSpellTexture) instead of the self-aura at index 1 plus a
UNIT_SPELLCAST_SUCCEEDED capture for active totems. The drop spell is a
broadcast descriptor field present for every summoned unit in range, so
it resolves immediately for passive and active totems alike and gives the
totem's own icon rather than the attack-spell proxy. Drops the
UNIT_SPELLCAST_SUCCEEDED registration and handler.

Re-read the spell each render and key the cached texture on the spell id
so an in-place totem swap (same unit, new drop spell -- no plate re-add)
refreshes the icon without needing the plate to leave and re-enter view.

* CAPI min bumped to 1.13.1

* Gracefully disable pfUI when ClassicAPI is missing

Add API_Check.lua as the first TOC entry. When the ClassicAPI DLL is
absent or below the minimum version it sets pfUI_disabled and stands up
an inert stub so the rest of the addon no-ops instead of flooding load
errors: modules and skins register their bodies into no-ops, and the
setfenv'd api/lib files run inside an environment where CreateFrame and
any missing global resolve to a null object -- so no real frames or live
handlers are created and missing API calls just return null. pfUI.lua
bails early on pfUI_disabled.

* auras: scan through GetAuraSlots instead of by-index loops

Every aura scan loop (unit frame buffs/debuffs, dispel indicators, buff and
custom indicators, player buff frame, buffwatch bars, tooltip buff row) now
enumerates a unit's auras once with C_UnitAuras.GetAuraSlots and reads each
aura by slot id via the positional C_UnitAuras.UnitAuraBySlot. The by-index
getters re-walk the aura array from slot 0 on every call, so a per-button
loop over them was quadratic in the aura count; one enumeration plus a
by-slot read per aura is linear.

pfUI.api.ScanAuraSlots(unit, filter, buf, max) wraps GetAuraSlots' fill-a-
table form (table as the 5th argument) so no vararg Lua frame is involved:
Lua 5.0 allocates an `arg` table for every vararg call, which showed up as
nameplate OnUpdate/OnEvent memory growth in the first cut of this change.

Single by-index reads in tooltip and click handlers are unchanged (one call
each, and SetUnitAura takes the same index).

Requires the ClassicAPI build that adds GetAuraSlots' fill form; on an older
DLL the 5th argument is ignored and ScanAuraSlots would read the first slot
id as the count.

* Show Faction/Race icons in chat

* Load pfUI through ClassicAPI's flavor TOCs

ClassicAPI redirects the read of pfUI\pfUI.toc to a flavored file whenever
the DLL is installed, so which TOC the client opens already answers whether
ClassicAPI is there. Split the manifest three ways and let that do the work:

  pfUI.toc             fallback, reached only when ClassicAPI is missing;
                       loads API_Check.lua and nothing else
  pfUI_ClassicAPI.toc  full addon, every non-Turtle client
  pfUI_Turtle.toc      full addon plus init\turtle.xml, on Turtle

The fallback TOC declares no SavedVariables. It used to, while API_Check.lua
reset pfUI_profiles to an empty table on the disabled path -- which truncated
the player's profiles on logout.

With the missing-DLL case handled by TOC selection, API_Check.lua drops the
null object stub that kept the other ~140 files quiet, along with
pfUI_disabled and the now unreachable early return in pfUI.lua. It keeps the
version gate, which still matters: the flavor redirect landed in ClassicAPI
v1.11.0, below the v1.13.1 pfUI needs, so an old DLL still gets served a
flavor TOC. pfUI.lua also loses a verbatim duplicate of the whole check.

Turtle-only files move to init\turtle.xml: modules\turtle-wow.lua (its
TURTLE_WOW_VERSION guard is now redundant) and the lft, turtle_shop,
barbershop, transmog and ebc skins. turtle-wow registers last instead of
75th of 84; the only ordering it relies on is pfUI.chat, registered 8th.

pfSellData moves to env\selldata.lua, listed only in pfUI_ClassicAPI.toc,
since turtle-wow.lua replaces the table wholesale on Turtle. env\tables.lua
keeps an empty declaration so sellvalue.lua has something to index when the
turtle-wow module is disabled.

The release workflow pinned PFUI_CLASSIC_API_LATEST in pfUI.lua, which has
not held that constant since it moved to API_Check.lua, so the pin was
silently doing nothing. It also switches to brues-code/packager@vCAPI, which
recognizes the _ClassicAPI and _Turtle suffixes and applies the TOC build
type filters to them.

* Split the vendor price tables into their own manifests

Turtle's pfSellData moves out of modules\turtle-wow.lua into
env\selldata_turtle.lua, matching env\selldata.lua for the stock list, and
each is pulled in by the manifest for its client: init\stock.xml from
pfUI_ClassicAPI.toc, init\turtle.xml from pfUI_Turtle.toc. Either way it
loads after init\env.xml and replaces the empty pfSellData declared there.

Turtle's prices used to be assigned inside the turtle-wow module body, which
put them on pfUI.env and skipped them entirely when that module was
disabled. At file scope they land on _G and apply either way.

* Drop the vanilla compat layer

compat\vanilla.lua named the handful of things that differed between clients
back when pfUI targeted several. Only one client remains, so every constant
had exactly one value. Inline each at its use site and delete the file,
init\compat.xml, and both TOC entries.

  COOLDOWN_FRAME_TYPE                    -> "Model"
  LOOT_BUTTON_FRAME_TYPE                 -> "LootButton"
  MINIMAP_TRACKING_FRAME                 -> _G.MiniMapTrackingFrame
  FRIENDS_NAME_LOCATION                  -> "ButtonTextNameLocation"
  EVENTS_MINIMAP_ZONE_UPDATE             -> the event list, in panel.lua
  MICRO_BUTTONS                          -> a local in panel.lua
  NAMEPLATE_OBJECTORDER                  -> a local in nameplates.lua
  ACTIONBAR_SECURE_TEMPLATE_BAR/_BUTTON  -> nil, so the argument goes away

NAMEPLATE_FRAMETYPE and PLAYER_BUFF_START_ID had no readers left.

RunMacroText moves to pfUI.lua. compat\vanilla.lua was setfenv'd into the
pfUI environment, so the function only ever existed on pfUI.env; at file
scope it lands on _G as a real export instead. Nothing in pfUI calls it, and
ClassicAPI neither defines nor looks for a RunMacroText global -- it does
the same throwaway edit box natively in src/macro/Execute.cpp and only
defers to a global RunMacro.

* bump CAPI min to 11303

* auras: uncap the self-debuff tooltip lookup

With selfdebuff on, the displayed debuff list is PLAYER-filtered while
GameTooltip:SetUnitAura indexes the unfiltered HARMFUL list, so both
handlers map one to the other by matching name + sourceGUID. That mapping
scanned slots 1..16 only.

The unfiltered harmful list is not capped at 16. Once a unit's 16 debuff
slots are full the server parks further debuffs in buff slots, and
C_UnitAuras classifies by the aura's polarity flag rather than its slot
range, so it reports those as harmful too -- verified live at 18 harmful on
a 20-aura target. Past the sixteenth the lookup found nothing and fell
through to the raw filtered index, opening the wrong tooltip or none.

Both now enumerate however many harmful auras the unit actually has, via
ScanAuraSlots, which also drops the per-index rescan the by-index accessor
was doing. Each handler gets its own slot buffer: OnEnter can fire while a
refresh is showing/hiding frames under the cursor, so sharing the refresh
buffer could clobber a scan mid-walk.

The nameplate module still collects at most 16 debuffs per plate. That one
is a display cap matching its 16 configured icon frames, not an aura-count
assumption, so it is left alone.

* bump CAPI min to 11304
2026-09-04 19:57:57 -05:00
Brues 5815b9e81e chat: let pfUI's chat colors own their alpha and apply live
Two problems with routing the native transparency slider into the pfUI panel.

RefreshBackgroundAlpha overwrote the alpha of C.chat.global.background on every
refresh, so anyone with custom colors enabled saw their configured opacity
revert on reload, tab switch and dock change. That value carries its own alpha,
is set by the shipped profiles and is shared with the meter skins, so the slider
must not own it. Skip the alpha mirror entirely when custom colors are on; the
slider still drives the panel on the default theme, which is what issue #48 was
actually about.

The colors were also only applied at module load, so the pickers needed a
/reload to show anything. Extract that into ApplyPanelColors and expose it as
pfUI.chat:UpdateConfig, which the gui resolves as U["chat"], so the three chat
color settings take effect on the spot. CreateBackdrop is re-run first to
restore the appearance theme, which is what lets toggling custom colors back
off return the panel to the global theme without a reload.
2026-09-02 11:40:20 -05:00
Brues 60f953178f nameplates: validate unitstr before filling the per-unit cache
cache.player and cache.minion were filled from unitstr before the two guards
that validate it, so a stale identifier poisoned the cache -- and because the
fill is gated on `== nil`, the wrong answer was never recomputed. Players read
back as cache.player == false until the plate was hidden and shown again, which
only appeared to fix it because pool reuse tripped the name/guid wipe.

Two paths produce a stale unitstr: OnUpdate dispatches a targetUpdate to
OnDataChanged before it refreshes plate.istarget, and frameState.mouseoverGuid
is only updated on gaining mouseover, never on losing it.

Hoist the PLAYER_TARGET_CHANGED distrust and the UnitName mismatch check above
the cache fill so an unverified unitstr leaves the cache nil for the next tick
instead of locking in a wrong answer.
2026-09-02 10:56:46 -05:00
Brues 1dc10964a2 luarc: use nested config form so diagnostics settings apply 2026-09-02 10:31:03 -05:00
Brues 0fc08e77c0 Create .pkgmeta 2026-09-02 10:26:28 -05:00
Brues 7f3795b45b Add .luarc.json for Lua 5.1 language server diagnostics 2026-09-02 10:23:48 -05:00
Brues 8eee766652 chat: apply Blizzard per-window transparency to docked backdrops
The native chat background transparency slider drives FCF_SetWindowAlpha,
which only touches the ChatFrame*Background textures pfUI hides on docked
frames, so the visible pfUI backdrop never responded to it.

Mirror the selected window's stored alpha (GetChatWindowInfo) onto the pfUI
backdrop and refresh it from RefreshChat, FCF_SetWindowAlpha and
FCF_SelectDockFrame so the native slider drives it live and persists across
/reload. Default the SetupPositions window alpha to 0.8 to keep the historical
look, and migrate existing installs whose windows still carry the old hard-0
alpha so their chat background is not suddenly transparent.

Fixes #48
2026-09-02 01:05:47 -05:00
Brues 2b5288a688 nameplates: don't run GetUnitInfo for minions 2026-08-31 16:43:38 -05:00
Brues daba4e202e Default saved variables with 'or {}' instead of overwriting
Seed the SavedVariables globals only when absent (X = X or {}) rather
than unconditionally assigning {}. Behaviorally identical under the
current load order -- a returning character's data is restored over these
between file load and ADDON_LOADED -- but expresses intent as a default
and won't clobber data if that timing ever changed.
2026-08-31 12:04:00 -05:00
Brues 98c7751416 Utilize CAPI Tracking functions
commit 8ea5c81789
Author: Brues <5278969+brues-code@users.noreply.github.com>
Date:   Mon Aug 24 23:40:01 2026 -0500

    bump CAPI min to 1.12.4

commit 39a46563a1
Author: Brues <5278969+brues-code@users.noreply.github.com>
Date:   Mon Aug 24 23:35:49 2026 -0500

    tracking: use native ClassicAPI tracking API

    Replace the hardcoded knownTrackingSpells class table and the
    icon-substring spellbook scan with ClassicAPI's native tracking
    functions:

    - RefreshSpells now enumerates GetNumTrackingTypes() / GetTrackingInfo(),
      which the DLL derives from the spellbook by tracking-aura effect. This
      drops the per-class spell/icon table and picks up server-custom
      trackers (e.g. Turtle's Find Trees) automatically.
    - Selecting a tracker in the menu uses SetTracking(index) instead of
      CastSpell(slot, BOOKTYPE_SPELL).
    - Keep the Druid Cat Form gate on Track Humanoids (5225) and the
      existing invalidSpells filter. Falls back to no entries if the tracking
      API is absent.

    Active icon (GetTrackingTexture), cancel (CancelTrackingBuff), and the
    hover tooltip (GameTooltip:SetTrackingSpell) were already native and are
    unchanged.

Fixes #42
2026-08-30 17:19:17 -05:00
Brues 8796fab6d8 libpredict: predict incoming heals for built-in click casting (#44)
Built-in click casting drives the cast through secure frame attributes,
bypassing the CastSpell/CastSpellByName/UseAction hooks that populate
spell_queue -- so the heal-prediction lookup in the SPELL_START_SELF
handler missed and no incoming heal showed.

Populate spell_queue from the cast's own rank-specific spellId (via
C_Spell.GetSpellName/GetSpellSubtext) right before the cache lookup, so
prediction works regardless of how the cast was initiated. Deriving the
rank from the actual spellId also fixes stale-rank lookups when a prior
keybind cast left a different rank of the same spell queued.
2026-08-24 21:37:41 -05:00