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.
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.
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.
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.
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.
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.
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
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.
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.
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.
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.
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.
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.
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(...).
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.
`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.
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.
"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.
* 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
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.
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.
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
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.
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
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.
The debuff-bar auto colour read the vanilla DebuffTypeColor global,
diverging from unitframes/buff which use C_UnitAuras.GetAuraDispelTypeColor
(the BC GlobalColor.dbc palette ClassicAPI ships). Switch buffwatch to the
same accessor so all aura displays share one palette; nil/unknown types
fall back to DEBUFF_TYPE_NONE instead of the old ad-hoc (1, .2, .2).
buff.lua RefreshBuffButton runs 50x per PLAYER_AURAS_CHANGED and
buffwatch.lua GetBuffData runs 32x per RefreshBuffBarFrame; both now use
the zero-allocation positional C_UnitAuras.UnitAura instead of the
table-allocating GetAuraDataByIndex.
buffwatch also threads the aura's dispelType out of GetBuffData onto the
frame.buffs row, so the debuff-bar colour reads it from the row instead
of re-fetching the aura with GetDebuffDataByIndex on every name change.
Cold single-lookups (hover/click tooltips) keep the readable table API.
The dispellable-indicator block re-scanned all 16 harmful slots for
every dispellable type (O(types x 16) UnitDebuff calls per refresh).
Scan the 16 slots once into a reusable set, then have the per-type loop
check the set. Zero-allocation (the set is cleared and reused within the
single RefreshUnit call).
Swap the per-frame aura-refresh loops from table-allocating
GetAuraDataByIndex/GetBuffDataByIndex/GetDebuffDataByIndex/GetUnitAuras
to the zero-allocation positional C_UnitAuras.UnitAura/UnitBuff/UnitDebuff.
unitframes: buff/debuff icon loops, the dispellable-indicator 16-slot
scan, the two GetUnitAuras indicator passes, and the custom-debuff scan.
nameplates: the per-plate debuff scan now writes positional results
straight into the reusable display buffer, dropping the per-aura tables
and the result array.
Filters change from HARMFUL|PLAYER/HARMFUL to PLAYER/nil since UnitBuff/
UnitDebuff lock the range and still honor the PLAYER predicate. Cold
single-lookups (hover/click, tooltip, totem icon, libdebuff) keep the
readable table API.
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.
Introduce pfUI.handlesHookScript boolean in pfUI.lua to signal that this fork's actionbar buttons correctly support the modern HookScript widget method. This prevents ClassicAPI's AddOnCompat shim from shadowing HookScript during actionbar load. The flag documents behavior (not identity): forks that maintain HookScript-correct actionbar code should keep the flag; forks that do not should clear it to opt into the safe compatibility fallback.
If the global GetCoinTextureString exists, use it to format money (wrapped in white color codes) for CreateGoldString. Keeps the existing numeric fallback formatting for environments without GetCoinTextureString.
Initialize pfUI.env.L["race"] by iterating C_CreatureInfo.GetRaceInfo and C_CreatureInfo.GetFactionInfo. Builds a table keyed by clientFileString containing raceName, raceID and faction (groupTag).
When "Use Raid Frames To Display Group Members" is on and in a party,
raid pet frames tried to show raidpet1..40 (nonexistent in a party)
instead of the player pet and partypet1..4.
Remap raidpet grid slots to pet / partypet<N> in UpdateVisibility,
keyed on a stable pfRaidPet<N> slot, mirroring the raid player frames.
LayoutPets now mirror-places the party pet slots, and the raid updater
runs LayoutPets when in a party (not just a raid).
Also exclude raid pet frames (label "partypet", cache_raidpet set) from
the hide_in_raid group-hiding clause so they are not hidden.
Expose the built-in IsPlayerGuid helper through pfUI.api so callers can check whether a GUID or unit token matches the local player. This keeps the pfUI API surface consistent with the underlying WoW API while making the check available through the addon namespace.
Accept "macro:<name>" in a click-cast action to run a saved macro by
name via the secure `macro` attribute (leading space after the colon is
trimmed). ClassicAPI dispatches it through RunMacro, so a conditional
addon still evaluates the macro body.
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.
GetPerfectPixel switched to UIParent:GetEffectiveScale(), which reports
the true unclamped scale (e.g. 1.42 on the larger pixelperfect presets,
since the module calls UIParent:SetScale past the uiScale cvar's 1.0
cap). At that scale 768/screenheight/scale drops below half a pixel and
the border edgeSize rounds to zero, so button borders disappeared.
The uiScale cvar's <=1.0 cap was load-bearing, not a bug: it keeps the
1px borders from vanishing. Restore the cvar-based measurement and move
pixelperfect back to its original load position.
The 1.12 client defines Blizzard's own misspelling, UKNOWNBEING
("Unknown Being"); UNKNOWNBEING does not exist and resolves to nil, so
both guards never matched. Point them at the real global.
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
Replace the hardcoded per-locale ["class"] reverse-lookup tables (localized
class name -> token) with a runtime build in GetEnvironment:
L["class"] = tInvert(FillLocalizedClassList({}))
The client's own class list supplies the localized names for whatever locale is
running, so the tables were pure duplication -- and this also covers locales the
hardcoded tables never listed. Consumers (libunitscan, panel, socialmod) are
unchanged. Drop ["class"] from every locale file.
Rewrite libdebuff's duration lookups on ClassicAPI's C_UnitAuras, whose
expirationTime carries the real, talent-modified remaining time:
- GetDuration reads the live duration of a matching aura on the player
(C_UnitAuras.GetAuraDataBySpellName) instead of L["debuffs"][effect][rank].
- GetBestAuraCast reads the active aura's expirationTime directly rather than
scanning the ownDebuffs/allAuraCasts cast tables -- a unit only ever holds one
instance of a given spell, so that is inherently the effective one.
- GetMaxRank deleted (it only fed the old GetDuration); AddPending's
known-debuff guard removed.
The public tracking tables (pfUI.libdebuff_*) are untouched. Drop the now-unused
L["debuffs"] and L["dyndebuffs"] tables from every locale.
- 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.
Nameplate totem/critter detection now uses UnitCreatureTypeID instead of
name-matching, so the L["totems"] and L["critters"] tables have no remaining
readers. Drop them from every locale file.
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.
Read the player class color from PFUI_CLASS_COLORS instead of
GetClassColor in SkinButton, SetHighlight, and SkinDropDown.
GetClassColor returns the stock pink for Shaman, but pfUI remaps it to
blue, so the button highlights and locked borders now match.
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.
Release tags often include a leading 'v' (e.g. "v9.0.18"). Previously the code split the raw tag directly which could yield a nil major version and fall back to 0.
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.
The power bar's frame level was pinned to f.power's live level
(f.power:GetFrameLevel() + 1). CreateBackdrop caches the backdrop at
f.power's level when it first builds it, but f.power's level can shift
afterward as strata changes propagate across UpdateConfig re-runs. When
it shifts below the cached backdrop, the fill drops behind the dark
backdrop and the bar renders black -- which reproduced on some clients
but not others depending on how the levels resolved.
Anchor the fill to the backdrop's own level instead
(f.power.backdrop:GetFrameLevel() + 1). The backdrop's level is fixed
once created, so the fill is always exactly one level above it
regardless of f.power's value, and the bar can never fall behind it.
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.
Fixes#33.
Clique's pfUI plugin replaces pfUI.uf.ClickAction and calls it from an
OnClick script. The move to secure attribute clicks removed that
function, so Clique stopped working.
Add the legacy Lua click path back for Clique. When Clique is loaded,
EnableScripts sets an OnClick script that runs ClickAction. When Clique
is not loaded, the frames keep the secure attribute path.
Skip the secure attributes in Clique mode. The type1 target attribute
switches the target before Clique can cast. Right-click opens the unit
menu through ClassicAPI_ToggleUnitMenu.
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.