78 Commits

Author SHA1 Message Date
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
brues-code 7ca0e40d8b CAPI needs 1.12.1 for UnitAura changes 2026-08-24 12:05:18 -05:00
brues-code 2f93b246a1 Accidentally bumped ClassicAPI to the wrong version 2026-08-24 11:48:54 -05:00
Brues c647698f8f bump CAPI min 2026-08-24 11:07:38 -05:00
Brues 48026008b6 buffwatch: use official dispel-type colours for debuff bars
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).
2026-08-24 11:07:38 -05:00
Brues c1d96bae58 buff/buffwatch: positional aura reads in hot refresh loops
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.
2026-08-24 11:07:38 -05:00
Brues 1d44c5ec6f unitframes: scan debuff slots once for dispel indicators
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).
2026-08-24 11:07:38 -05:00
Brues 8f88dfd852 Use positional C_UnitAuras accessors in hot aura scans
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.
2026-08-24 11:07:37 -05:00
Brues a57214aeb0 Bump ClassicAPI minimum version 2026-08-24 11:07:37 -05:00
Brues 9bde843e33 utlize inline icons and CreateGoldString in repair module 2026-08-24 11:07:37 -05:00
Brues ad13e6ace3 Utilize SetFormattedText
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.
2026-08-24 11:07:37 -05:00
Brues 82c74c2db5 Skip declaring pfSellData on turtle client
This giant table is just going to be immediately replaced so there's no point in declaring it.
2026-08-21 14:59:19 -05:00
roby-brok a3cfd711fe nameplates: read friendly name colours from the class table
The class-colour write sourced r, g, b from the health bar rather than
from PFUI_CLASS_COLORS[class], which the condition tested for but never
read. So "class colours on friendly names" only produced a class colour
when friendclassc - a bar option - happened to be on as well; otherwise
the name took the generic friendly colour. In every case it also
inherited the tapped-grey and barcombatstate overrides meant for the bar,
neither of which belongs on the name.

The name now reads the class table directly.

The discarded "and PFUI_CLASS_COLORS[class]" term was dead weight: the
table carries an __index metamethod returning a grey ColorMixin for any
missing key, so it was always truthy and merely allocated a throwaway
table per evaluation. "and class" is the real guard. One behaviour change
falls out - a friendly player of an unrecognised class now takes that
grey fallback rather than the bar colour, which is what the option means.

(cherry picked from commit f880dc3528db2ba0d0936b00a4dff8deec48e6d2)
(cherry picked from commit 111d70004d4fb172f168aa48a4cb6177a7f0d393)
2026-08-21 13:31:07 -05:00
roby-brok a7ad1f073f nameplates: give the name colour a single owner
cache.namecolor was written by two writers holding unrelated quantities:
the computed health-bar colour in OnDataChanged, which class-colours
friendly player names when friendclassnamec is on, and Blizzard's own
name FontString in the OnUpdate sync. Sharing one key let either suppress
the other, so the option silently lost - names came out white with
namefightcolor off, or the plain reaction colour with it on.

It also forced a full data pass every throttled tick, because the sync
writer sets update = true whenever it writes and the two kept flipping
the key. That defeats the half-second tick gate entirely.

Compounded by nameplate.cache surviving pool reuse: OnShow re-runs the
data pass but never clears the table, which is built once at plate
creation, so a recycled plate could inherit the previous unit's name
colour.

Ownership is now explicit through cache.ownname, each writer keeps its
own key, and both are invalidated when ownership flips.

(cherry picked from commit 0a4b3a7d6ae523cf70584adbb694a8740024b7cd)
(cherry picked from commit 0df9bb9fa907349182fb03217cab6aa1777aa7d9)
2026-08-21 13:31:07 -05:00
roby-brok fe5b3b135b nameplates: use true difficulty colours for level text
The level string was brightened by +0.3 on all three channels before
display, in the levelFromDB path and again in the OnUpdate colour sync.
A flat offset desaturates toward white and costs the high tiers most,
because their green/blue channels start near zero: verydifficult
1.00/0.50/0.25 becomes 1.00/0.80/0.55 and reads as yellow, impossible
1.00/0.10/0.10 becomes 1.00/0.40/0.40 and reads as orange. Orange and
yellow ended up 0.2 apart on a single channel, so a mob 3-4 levels above
the player showed yellow and a skull-range mob showed orange.

Both offsets dropped. Ownership of the colour is now explicit through
cache.levelfromdb so the two writers no longer race: the sync block
stands down on ?? plates, and the DB path clears cache.levelcolor so the
sync re-asserts cleanly once the level resolves. That also fixes a stale
cache - nameplate.cache is built once at plate creation and survives pool
reuse, so a recycled plate could keep the previous unit's colour.

Deleting the sync block instead would be wrong: it is the only thing that
colours the level on non-?? plates, and plate.level is created with no
colour at all.

Reported by Iden via Discord.

(cherry picked from commit eaa9beac51953d2a959c67d5ff5bc50273a919c3)
(cherry picked from commit 3fdb3b23136b260d111509e24e6d7b0d0b9b1b33)
2026-08-21 13:31:07 -05:00
Brues 9c29f0c7d4 Add handlesHookScript capability flag
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.
2026-08-21 13:26:09 -05:00
Brues 685ecb4a3e Removed !!!ClassicAPI from toc dependencies
Addon is now baked into the DLL and !!!ClassicAPI will always load before pfUI so this only served to confuse users
2026-08-18 10:35:31 -05:00
Brues 8cdaf1fc00 Prefer GetCoinTextureString in CreateGoldString
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.
2026-08-18 04:38:09 -05:00
Brues bb39c9d94e Add localized race info to pfUI environment
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).
2026-08-18 04:34:34 -05:00
Brues 18059ffbb7 Class-color aura caster name in tooltips 2026-08-18 04:29:33 -05:00
Brues d75e280238 Show party pets in raidpet frames under raidforgroup
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.
2026-08-13 18:03:56 -05:00
Brues 9eb34b8f0a Revert "40-y rangecheck on by default"
This reverts commit af6893c10c.
2026-08-13 17:43:13 -05:00
Brues f8dde49f09 Add IsPlayerGuid API wrapper
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.
2026-08-13 17:32:47 -05:00
Brues 74fbbb6154 Use PFUI_CLASS_COLORS in loothistory 2026-08-12 14:33:05 -05:00
Brues af6893c10c 40-y rangecheck on by default 2026-08-12 14:25:37 -05:00
Brues be6b2ea036 Support binding a click to a named macro
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.
2026-08-12 00:33:49 -05:00
roby-brok afdb6c3862 unitframes: stop UnitHasAggro rescanning and reallocating on every call
Two costs on the hot path. It concatenated '<u>target' and '<u>targettarget'
per call per unit, though pfValidUnits never changes after load -- now built
once into a static triple list. And only positive results were cached, so the
common case (nothing has aggro) rescanned the entire unit table on every call;
negative results now cache for 0.3s, short enough that aggro still appears
promptly.
2026-08-11 02:06:28 -05:00
Brues b230a45b92 Show spell and unit IDs on tooltips
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).
2026-08-11 00:38:28 -05:00
Brues ef1fa47539 Dock GreedMeter into the chat panel
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.
2026-08-10 23:52:52 -05:00
Brues ab0ad7dcf9 Group tooltip settings into sections 2026-08-10 15:07:01 -05:00
Brues c82813df5b Use GetRGB() for NORMAL_FONT_COLOR 2026-08-10 15:05:48 -05:00
Brues 6e1f8095a2 Allow whitespace-only macro names
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.
2026-08-10 09:53:34 -05:00
Brues 9cabdf2974 Revert the pixel-perfect scale change
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.
2026-08-10 01:27:27 -05:00
Brues b6931359b0 Use the real UKNOWNBEING global in libpredict guards
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.
2026-08-10 01:13:47 -05:00
roby-brok e017dbf515 Fix guards, cache keys and comparisons that never match (#40)
* chat: detect whispers before the timestamp is prepended

Whisper detection tests for the whisper colour code at position 1, but the
timestamp is prepended first, so with timestamps enabled the code is no longer
at position 1 and every whisper failed the test -- losing both the recolour and
the correct chat-history entry.

(cherry picked from commit 4b69d597631c422d9360a94ff129323288a28cc2)

* macrotweak + libpredict: inverted install guard, misspelled globals

macrotweak: _AddHistoryLine is the backup slot this block creates, so it is nil
until line 18 runs. Guarding on its truthiness meant the chat-history filter
never installed at all -- macro calls kept landing in chat input history.

libpredict: UKNOWNBEING / UNKOWNBEING are misspelled, so both resolve to nil and
the guards never matched the real UNKNOWNBEING. Neutral if the global is absent
on this client, correct if present.

Not needed: his libpredict already fixed the always-true
'event == "A" or "B"' condition (libs/libpredict.lua:828).

(cherry picked from commit 4068110dc4134823f1c09f60ca5f8959302cd14f)

* mapreveal: look the explore cache up by the key it is actually stored under

explorecaches is keyed by the plain area name (line ~160), but the hover frame
carried only a decorated 'mapFileName (area)' display string, so every lookup
missed and the hover highlight never fired. Store the plain name alongside it
and key off that; the tooltip keeps the decorated string.

(cherry picked from commit 5e6fe969a03a88c68e28614548b9d44db562ab9a)

* socialmod: don't clobber the friend-online match with the offline one

The offline match was assigned unconditionally over the online match, so a
friend coming online never had lastseen recorded.

(cherry picked from commit ad927806c46042163f84bde4db1082726bedf28b)

* swingtimer: off-hand weapons are inventory type 22, not 21

The off-hand slot accepts one-hand (13) and off-hand (22) weapons. Type 21 is
INVTYPE_WEAPONMAINHAND and can never be equipped there, so the off-hand swing
timer never recognised a real off-hand weapon.

The other two swingtimer fixes are not needed here: his hunter check already
uses UnitClassBase (locale-independent, better than our UnitClass second
return), and his generic on-next-swing detection covers Raptor Strike without
our hardcoded spell-id list.

(cherry picked from commit 9ab7f5f7224d12361ca6cd5a49181cbb4bebd387)

* superwow: compare the version numerically, not by exact string

SUPERWOW_VERSION == "1.5" silently disables the GUID-to-name combat text hook
on any release past 1.5. Currently 1.5 here, so this is forward-compat only.

Not needed: the clickthrough slash commands already go through
RegisterSlashCommand, which writes _G properly.

(cherry picked from commit 82a37752a5782479849ce7e1304c24733c2ff97a)

* api: measure a real pixel against UIParent, not the uiScale cvar

The uiScale cvar caps at 1.0 while both the pixelperfect module and the
firstrun slider push UIParent past it via SetScale, and it is ignored entirely
while useUiScale is off, so borders came out the wrong thickness on the
Huge/Large presets. Ask the frame for its effective scale instead, and guard an
unparseable gxResolution.

Also cache GetItemLinkByName and count its failures: the scan walked every id
on each call with no memory, so an unresolvable name hitched on every tooltip
hover. Raise the ceiling from 25818 to 61000 as well -- Octo/Turtle custom
items live well past the vanilla range and never resolved.

(cherry picked from commit dac2d3416aef85a4c4b71c0239d93d309e76e30b)

* init: load pixelperfect first so GetPerfectPixel caches the right scale

pixelperfect sets the UI scale that GetPerfectPixel measures against, and that
value is cached on first use. Loading it 56th baked in the previous scale.

(cherry picked from commit dd89a210330583f890a51a82b29e968f3ad36b34)

* modules: route two global overrides through _G so they leave the sandbox

pfUI.env has __index but no __newindex, so a bare global assignment inside a
RegisterModule closure is written into the sandbox table and never reaches _G.
unitxp's BattlefieldFrame_Show override therefore never fired the BG queue-pop
notification, and RaidFrame.lua never saw GROUP_REPLACE_PARTY.

The slash-command half of this fix is not needed here: RegisterSlashCommand
already writes through _G.

(cherry picked from commit 617c8320aca2465af62cf0ff5be7dceaed29a3b2)

* bags: remove the duplicate search OnHide handler

Two byte-identical frame.search OnHide handlers were installed back to back;
the second overwrote the first, so the first was dead code.

(cherry picked from commit e77650448b92e5d7f18ae58632e4531cb2813ea6)

* buffwatch: invalidate the filter cache when a skill is listed

fcache is built once per config table and never cleared, so ctrl/shift-clicking
a skill onto the whitelist or blacklist had no effect until the next reload.

* roll: actually capture the everyone-passed subject

strfind(LOOT_ROLL_ALL_PASSED, LOOT_ROLL_PASSED) has no captures, so `everyone`
was always nil, never reached the blacklist, and "Everyone has passed on: X"
was recorded as a real player passing.

Builds a sample from LOOT_ROLL_ALL_PASSED and runs the same LOOT_ROLL_PASSED
match the scanner uses, so the subject is captured the way it will actually
appear.
2026-08-10 01:13:32 -05:00
roby-brok 69d778d6d6 Fix two dangling skin includes and four error paths (#39)
* skins: drop includes for two files that were never committed

init/skins.xml referenced custom_merchant.lua and arena_score.lua, neither of
which is tracked in git. Every install -- release zips included, since the
release workflow packages the repo -- throws two 'Error loading' lines at
login and ships without those two skins.

(cherry picked from commit b338b4a16d0def4ae89fde5c6026e58a796b8c00)

* map: don't re-anchor the world map to the frame GetPoint returned

Ctrl+scroll rescales the map and repositions it to keep the top-left fixed, but
it re-anchored using the relative frame GetPoint handed back. Once anything else
is anchored to WorldMapFrame that throws

  WorldMapFrame:SetPoint(): <unnamed> is dependent on this

and the error aborts the rest of the zoom handler, so SetScale never runs.

Anchor to the parent instead. That is what the rest of pfUI's movable system
already assumes -- LoadMovable uses the 3-arg form and SaveMovable stores only
xpos/ypos with no relative frame.

(cherry picked from commit bf055d87fc0a04ac912c13fa874a4331f47bdfa8)

* firstrun: return after bailing on a disabled chat module

All three chat setup steps printed 'Chat module is disabled' and then carried on
into the nil pfUI.chat they had just tested for.

(cherry picked from commit 1af427e3b38bb7c13f645ae7a48b8ade3e7455a9)

* unitxp: stop the free-frame distance poller on logout

The PLAYER_LOGOUT handler stops the indicators to avoid the UnitXP crash on
exit, but in free-frame distance mode the polling runs on a separate scanner
frame that was never exposed, so the handler could not reach it and its
OnUpdate kept calling into UnitXP during teardown.

Exposes the frame as pfUI.uf.target.distanceScanner and stops it alongside the
others.

* roll: bail out on an item the client has not cached

C_Item.GetItemInfo returns nil for an item that is not cached yet, and the next
line assigns pfUI.roll.cache[itemName], which throws "table index is nil" on a
nil key. Easy to hit on a fresh login when someone rolls on an item you have
never seen.

* cooldown: return after hiding on a nil parent

Without the return it falls straight through to parent:GetName() on the nil it
just tested for.
2026-08-10 01:00:47 -05:00
Brues 75a25cf614 Show a mouseover unit's buffs on the tooltip
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.
2026-08-09 23:12:20 -05:00
Brues 9daf6165fd Show a note-only menu for offline friends
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.
2026-08-09 22:47:28 -05:00
Brues 06768c985a Merge classicapi-next: friend list and spell fields on ClassicAPI
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
2026-08-09 19:14:40 -05:00
Brues 881a17e508 Build the class name lookup from FillLocalizedClassList
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.
2026-08-09 19:03:29 -05:00
Brues 4e461d766e Read debuff durations from C_UnitAuras instead of a static table
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.
2026-08-09 19:02:35 -05:00
Brues 2dfaa65168 Gate combopoint updates and fold player class-color branches
- 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.
2026-08-09 13:01:29 -05:00
Brues 97ef039d3a Fetch the totem buff icon via GetBuffDataByIndex
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.
2026-08-09 03:39:11 -05:00
Brues 25ef02705c Remove dead totems and critters locale tables
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.
2026-08-09 03:17:00 -05:00
Brues 48d1da2015 Localize hardcoded user-facing nameplate/bag/frame strings
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
2026-08-09 03:15:01 -05:00
Brues ff29e9d3c2 Detect nameplate totems and critters by creature type
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).
2026-08-09 03:15:01 -05:00
Brues f0f1c80e24 autoshift: stub shapeshifts table for pfUI-turtle compatibility
Closes #38
2026-08-08 19:59:36 -05:00
Brues 5b4cb2bc9f Hide redundant Total line in item count tooltip and localize labels
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.
2026-08-08 19:24:42 -05:00
Brues 5a3a962c90 Remove dead unitframes showtooltip setting
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.
2026-08-08 16:16:33 -05:00
Brues cd08db75d2 Color button highlights from PFUI_CLASS_COLORS
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.
2026-08-07 23:52:22 -05:00
Brues 6365f0cdb1 Read the innervate cooldown through ClassicAPI
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.
2026-08-07 18:29:37 -05:00
Brues faa801e27d Read spell range through ClassicAPI in the hunter bar
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.
2026-08-07 17:39:10 -05:00
Brues 421a103f22 Show action button spell tooltips by spell ID
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.
2026-08-06 23:08:32 -05:00
Brues ecb28fb194 Strip leading 'v' from version tags before parsing
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.
2026-08-06 22:10:21 -05:00
Brues 94268b1162 Add option to always show equipment slot flyouts
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.
2026-08-06 20:28:32 -05:00
Brues 1c7c91fdf7 Fix power bar rendering black behind its backdrop
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.
2026-08-06 19:16:49 -05:00
brues-code 7f5d18bc30 Update README to simplify project description 2026-08-06 13:29:49 -05:00
Brues 7efc3334ae Add an aura caster line to tooltips
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.
2026-08-06 02:51:08 -05:00
Brues f322fbd6c6 CancelItemTempEnchantment can't exist in vanilla without server change 2026-08-06 02:03:21 -05:00
brues-code 41b77a5728 Add the macro icon picker and improve the equipment manager (#35)
* 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.
2026-08-05 22:33:37 -05:00
Brues 06fea6c889 Cache the player guild name for nameplate coloring
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.
2026-08-05 21:43:08 -05:00
Brues 940a1adae0 Restore Clique support on unit frames
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.
2026-08-05 21:43:08 -05:00
Brues d13710c5a4 Rebuild macro icon list from the icon data provider
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.
2026-08-05 03:02:43 -05:00
Brues c3dd7cb601 Refactor player info: simplify haste and SP display
Minor readability and style cleanup.
2026-08-05 00:28:55 -05:00
Brues 9190b88974 Cache player color codes and use full ARGB hex
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.
2026-08-04 23:00:51 -05:00
Brues fe0ec82ede Route config color strings through GetStringColor cache
Replace raw strsplit(",", ...) color parsing across action bars, chat,
nameplates, third-party skins, player, and roll with the cached
GetStringColor / GetStringColorObject helpers, dropping the per-build
string-table allocations they created.
2026-08-04 22:27:43 -05:00
Brues 350b1d63ae Revert breaking change for WorldMapFrame 2026-08-04 21:29:03 -05:00
Brues c7f42491ef Update README.md 2026-08-04 16:28:08 -05:00
brues-code 6b458c9e27 Modernize cast bars, unit-frame clicks, and mark tracking with ClassicAPI (#32)
* Migrate player castbar to ClassicAPI UNIT_SPELLCAST_* events

Drive the player cast path off ClassicAPI's synthesized, player-only
UNIT_SPELLCAST_* events instead of the vanilla SPELLCAST_* plus nampower
SPELL_{START,GO,DELAYED}_SELF mix. START/CHANNEL_START poll UnitCastingInfo/
UnitChannelInfo and stamp; STOP/CHANNEL_STOP/FAILED/INTERRUPTED clear;
SUCCEEDED drives tradeskill craft counting; DELAYED/CHANNEL_UPDATE re-poll.
Since UNIT_SPELLCAST_START fires for chained same-spell recasts, the nampower
SELF deps and their RunNextFrame co-hook-ordering workarounds are gone.
Remote target/focus bars keep SPELL_*_OTHER (the new events are player-only).

Fix the cast-start flash: StampBar now primes the fill on the stamp frame,
and ClearBar no longer snaps the bar to full (that snap lingered in the fade
tail and flashed when the next cast stamped).

Add a red flash on a cancelled cast: new appearance.castbar.failcolor,
flashed by ClearBar only when a cast was actually in progress.

Handle pushback Quartz-style: DELAYED/CHANNEL_UPDATE re-poll just the times
and accumulate the endMs shift into this.delay for the +/- indicator (cast
+X, channel -X), rather than a full restamp.

* Route target/focus castbars through remote UNIT_SPELLCAST_*

ClassicAPI now fires UNIT_SPELLCAST_* for remote tokens (target/focus/...)
via PollRemote, so drop the nampower SPELL_*_OTHER path and drive all three
bars off one event model. The gate is now token-based (arg1 == this.unitstr,
plus the arg1=="player" + UnitIsUnit case for target=self); the RunNextFrame
defer is gone since the remote poll fires after UnitCastingInfo is readable.
Cast detection is now nampower-free across every bar.

Use the event's rank payload (arg5): thread it through RefreshBar into
StampBar, which now only calls C_Spell.GetSpellSubtext as a fallback on the
retarget re-poll (PLAYER_TARGET/FOCUS_CHANGED) where no event is in hand.

* Drive nameplate castbars off UNIT_SPELLCAST_*

ClassicAPI now fires UNIT_SPELLCAST_* for nameplate tokens, so populate the
per-GUID castState cache from UNIT_SPELLCAST_{START,CHANNEL_START} (poll
PollCastInfo for the timing the payload omits) and clear it on
{STOP,CHANNEL_STOP} -- the remote poll fires STOP for natural end, interrupt,
and cancel alike. Replaces the nampower SPELL_{START,FAILED}_OTHER path; the
^nameplate arg1 gate ignores the target/focus/party fires of the same events,
and the NAME_PLATE_UNIT_ADDED seed still catches a plate spawning mid-cast.
Nameplate cast detection is now nampower-free.

* We no longer use these features from SuperWoW/Nampower

* Use secure unit attributes; remove mouseover scripts

Switch unitframes to attribute-driven clicks and remove mouseover tooltip handlers. Adds SetAttribute("unit") in UpdateVisibility and sets frame attributes (unit and type1='target') when creating frames. Removes OnEnter/OnLeave functions and their SetScript registrations and eliminates direct TargetUnit calls in ClickAction

* Use SetShown and simplify component default

Replace explicit Show/Hide conditionals in api/unitframes.lua with SetShown(...) for ressIcon, leaderIcon, lootIcon, pvpIcon, and restIcon to reduce branching and improve readability. Also remove the local shadowing of the parameter 'component' by using assignment (component = component or ""). No intended behavior changes.

* Drive unit-frame clicks via secure attributes

Replace the Lua OnClick dispatch (OnClick/ClickAction/RightClickAction) with
secure frame attributes: default type1=target / type2=menu, and EnableClickCast
now writes type/spell/macrotext/menu/target/focus attributes per button+modifier
instead of caching a clickactions table read at click time. Drops the now-dead
clickactions table and buttons list.

Also set a "unit" attribute on each frame (at creation and re-synced in
UpdateVisibility to the live token, so a party shown on the raid grid reports
partyN, not raidN) so the hovered unit resolves from the frame.

Match the target/focus/menu click keywords exactly rather than by prefix, so a
spell whose name starts with "focus"/"target"/"menu" (e.g. Focus Magic) casts
instead of being swallowed as the keyword action.

* Simplify marktracking colors and refresh per mark

Replace the hand-rolled ParseColor plus its default-color table with
GetStringColor -- the raidmarkercolor_* config keys already carry those same
defaults, so the fallback was dead code.

Restructure the refresh off observed mark tokens: UNIT_HEALTH/UNIT_MAXHEALTH
now arrive as arg1 == "markN", so refresh just that one row (UpdateRow) instead
of rescanning all eight on every nearby unit's health tick. A visibility flip
(into range / death / hp crossing 0) re-packs the rows, so UpdateRow hands off
to a full UpdateDisplay; RAID_TARGET_UPDATE / PLAYER_ENTERING_WORLD stay full
refreshes, with the 1s poll as the range-change backstop.

* not true anymore

* Poll marktracking fallback via C_Timer instead of OnUpdate

The 1s range-change safety net ran a per-frame OnUpdate that no-oped ~59 of
every 60 frames. Replace it with C_Timer.NewTicker(FALLBACK_INTERVAL,
UpdateDisplay) -- one wakeup per second off the shared timer driver -- and drop
the elapsed accumulator. The scanner frame is now purely event-driven.

* SuperWoW is now optional

* Update README feature list

Add 'Mouseover Unit Frames' and 'Click-casting' to the main features list. Update the SuperWoW entry to reflect that it tracks party/raid units on the minimap (replacing the prior SetMouseoverUnit note).

* Replace fixed-interval OnUpdate polls with C_Timer/RunNextFrame

Swap hand-rolled per-frame throttles for the modern timer primitives:

- turtle-wow: one-shot next-frame defer (self-hiding OnUpdate frame) -> RunNextFrame
- panel: clock, combat, and fps widgets -> NewTicker(1); guild roster -> NewTicker(60)
- minimap: coordinates text -> NewTicker(0.1)
- addonbuttons: one-shot init -> RunNextFrame; 5s button rescan -> NewTicker(5),
  guarded by IsShown() to preserve the old "paused while hidden" behavior

Ticker callbacks reference frames via upvalue since 'this' is unbound outside
OnUpdate. Genuine per-frame work (bar fills, fades, drag) and polls already
coordinated through pfUI.throttle are left as-is.

* update pfQuest link

* Use SetShown/SetSize in panel module

Replace manual Show/Hide toggles with SetShown(not ... ) and replace SetWidth/SetHeight with SetSize in modules/panel.lua. Changes simplify toggle logic (timer, WorldMap, chat hide buttons) and unify sizing calls for timer, frames, textures, and microbutton. No behavior changes intended; purely refactor for conciseness and consistency.

* Bump ClassicAPI min version to 1.9.0

* Drive reagent counter from events instead of a polling OnUpdate

The pfReagentCounter frame ran an OnUpdate that (1) progressively rescanned
all 120 action slots one-per-100ms on any slot change and (2) recounted
reagent inventory on a 1s throttle. Replace both with direct event handling:

- ACTIONBAR_SLOT_CHANGED updates just the changed slot (arg1), full-scanning
  only when arg1 is 0/nil, instead of restarting a ~12s rescan on every edit
- BAG_UPDATE_DELAYED recounts tracked reagents directly (it is already
  Blizzard's coalesced bag event, so the extra 1s throttle was redundant)
- PLAYER_ENTERING_WORLD seeds the full reagent map once

UpdateSlot now seeds a new reagent's real count via GetItemCount so a freshly
placed reagent spell shows the correct number immediately. The updatecache ->
BarsUpdate render path and the IsReagentAction/GetReagentCount accessors are
unchanged.
2026-08-04 14:27:15 -05:00
Brues f338deebe7 UNITFRAME_SECURE_TEMPLATE is always nil in vanilla 2026-08-01 13:12:14 -05:00
77 changed files with 2166 additions and 8932 deletions
+22
View File
@@ -0,0 +1,22 @@
{
"runtime": {
"version": "Lua 5.1"
},
"diagnostics": {
"disable": ["deprecated"],
"globals": [
"this",
"event",
"arg",
"arg1",
"arg2",
"arg3",
"arg4",
"arg5",
"arg6",
"arg7",
"arg8",
"arg9"
]
}
}
+4
View File
@@ -0,0 +1,4 @@
package-as: pfUI
ignore:
- .luarc.json
+16 -14
View File
@@ -2,11 +2,11 @@
[![Octo WoW](https://img.shields.io/badge/Octo%20WoW-1.18.1-brightgreen.svg)](https://octowow.st/)
[![ClassicAPI](https://img.shields.io/badge/ClassicAPI-Required-purple.svg)](https://github.com/brues-code/ClassicAPI)
[![SuperWoW](https://img.shields.io/badge/SuperWoW-Required-purple.svg)](https://github.com/balakethelock/SuperWoW)
[![Nampower](https://img.shields.io/badge/Nampower-Required-purple.svg)](https://github.com/brues-code/nampower)
[![UnitXP](https://img.shields.io/badge/UnitXP__SP3-Optional-yellow.svg)](https://codeberg.org/konaka/UnitXP_SP3)
[![SuperWoW](https://img.shields.io/badge/SuperWoW-Optional-yellow.svg)](https://github.com/balakethelock/SuperWoW)
[![UnitXP](https://img.shields.io/badge/UnitXP__SP3-Optional-yellow.svg)](https://github.com/brues-code/UnitXP_SP3)
**A pfUI fork specifically optimized for ClassicAPI on [Octo WoW](https://octowow.st/) which requires Nampower and SuperWoW with optional UnitXP_SP3 DLL integration.**
**A pfUI fork specifically optimized for ClassicAPI on [Octo WoW](https://octowow.st/)**
This version includes significant performance improvements and DLL-enhanced features.
@@ -16,7 +16,7 @@ This version includes significant performance improvements and DLL-enhanced feat
4. Copy "pfUI" into Wow-Directory\Interface\AddOns
5. Restart Wow
## Optional DLL Enhancements
## DLL Enhancements
Since pfUI 6.0.0 includes integrations with client-side DLLs for enhanced functionality. These DLLs are permitted on Octo WoW:
@@ -36,23 +36,24 @@ Provides:
- Faster, safer profile sharing - Engine-side serialize/compress/base64 via C_EncodingUtil
- C_Timer - After / NewTicker replacing hand-rolled OnUpdate throttles
- Feign death, shapeshift and quest-item detection via real API calls
- Mouseover Unit Frames
- Click-casting
- Loot Roll History (/loothistory)
- New item highlighting
- Plenty other functions
### [SuperWoW](https://github.com/balakethelock/SuperWoW)
Provides:
- UnitPosition for distance calculations
- SetMouseoverUnit for improved targeting
### [Nampower](https://github.com/brues-code/nampower)
Provides:
- Spell queue indicator
- GCD indicator
- Reactive spell detection
- Enhanced cast information
### [UnitXP_SP3](https://codeberg.org/konaka/UnitXP_SP3)
### [SuperWoW](https://github.com/balakethelock/SuperWoW)
Provides:
- Tracks party/raid units on the minimap
### [UnitXP_SP3](https://github.com/brues-code/UnitXP_SP3)
Provides:
- Line of Sight detection
@@ -79,6 +80,7 @@ Use `/pfdll` in-game to check which DLLs are detected.
/swapfocus Toggle Focus and Target-Frame
/pftest Toggle pfUI Unitframe Test Mode
/abp Addon Button Panel
/loothistory Show Loot Roll History
## Languages
pfUI supports and contains language specific code for the following gameclients.
@@ -91,7 +93,7 @@ pfUI supports and contains language specific code for the following gameclients.
* Russian (ruRU)
## Recommended Addons
* [pfQuest](https://shagu.org/pfQuest) A simple database and quest helper
* [pfQuest](https://github.com/brues-code/pfQuest) A simple database and quest helper
* [SuperCleveRoidMacros](https://github.com/brues-code/SuperCleveRoidMacros) Supports modern macro formats
## Plugins
+30 -1
View File
@@ -204,6 +204,12 @@ function pfUI.api.UnitHasBuff(unit, name)
return C_UnitAuras.GetAuraDataBySpellName(unit, name, "HELPFUL") ~= nil or nil
end
-- [ IsPlayerGuid ]
-- Returns whether a GUID or unit token refers to the local player.
-- guid [string] A unit GUID (or unitID) to test.
-- return: [bool] true if it is the player otherwise "false"
pfUI.api.IsPlayerGuid = _G.IsPlayerGuid
-- [ GetUnbuffedRoster ]
-- Returns a comma-joined, colored list of group members missing the named aura.
-- name [string] the localized aura name to check for
@@ -397,14 +403,33 @@ end
-- Returns an itemLink for the given itemname
-- 'name' [string] name of the item
-- returns: [string] entire itemLink for the given item
local itemLinkCache = {}
local itemLinkMisses = {}
local ITEMLINK_MAX_SCANS = 2
function pfUI.api.GetItemLinkByName(name)
for itemID = 1, 25818 do
-- GetInboxItem() hands us a nil name for attachment-less mail
if not name then return end
-- cache successful resolutions so repeated lookups (e.g. per inbox click) are free
if itemLinkCache[name] then return itemLinkCache[name] end
-- Failures have to be counted, not just retried. An unresolvable name walks
-- the entire id range and finds nothing -- a visible hitch on every tooltip
-- hover. Allow a couple of attempts (rendering the tooltip caches the item,
-- so the next hover usually resolves), then stop scanning for that name.
local misses = itemLinkMisses[name] or 0
if misses >= ITEMLINK_MAX_SCANS then return end
-- Octo/Turtle custom items live well past the 25818 vanilla ceiling
for itemID = 1, 61000 do
local itemName = C_Item.GetItemNameByID(itemID)
if itemName and itemName == name then
local _, itemLink = C_Item.GetItemInfo(itemID)
itemLinkCache[name] = itemLink
return itemLink
end
end
itemLinkMisses[name] = misses + 1
end
-- [ FindItem ]
@@ -646,6 +671,10 @@ end
function pfUI.api.CreateGoldString(money)
if type(money) ~= "number" then return "-" end
if _G.GetCoinTextureString then
return "|cffffffff" .. _G.GetCoinTextureString(money) .. "|r"
end
local gold = floor(money/ 100 / 100)
local silver = floor(mod((money/100),100))
local copper = floor(mod(money,100))
+7 -1
View File
@@ -155,6 +155,7 @@ function pfUI:LoadConfig()
pfUI:UpdateConfig("appearance", "cd", "dynamicsize", "1")
pfUI:UpdateConfig("appearance", "castbar", "castbarcolor", ".7,.7,.9,.8")
pfUI:UpdateConfig("appearance", "castbar", "channelcolor", ".9,.9,.7,.8")
pfUI:UpdateConfig("appearance", "castbar", "failcolor", "1,.2,.2,.8")
pfUI:UpdateConfig("appearance", "castbar", "texture", "Interface\\AddOns\\pfUI\\img\\bar")
pfUI:UpdateConfig("appearance", "infight", "screen", "0")
pfUI:UpdateConfig("appearance", "infight", "aggro", "0")
@@ -553,7 +554,6 @@ function pfUI:LoadConfig()
pfUI:UpdateConfig("unitframes", unit, "glowaggro", "1")
pfUI:UpdateConfig("unitframes", unit, "glowcombat", "1")
pfUI:UpdateConfig("unitframes", unit, "showtooltip", "1")
pfUI:UpdateConfig("unitframes", unit, "healthcolor", "1")
pfUI:UpdateConfig("unitframes", unit, "powercolor", "1")
pfUI:UpdateConfig("unitframes", unit, "levelcolor", "1")
@@ -784,6 +784,10 @@ function pfUI:LoadConfig()
pfUI:UpdateConfig("tooltip", nil, "extguild", "1")
pfUI:UpdateConfig("tooltip", nil, "itemid", "0")
pfUI:UpdateConfig("tooltip", nil, "movespeed", "0")
pfUI:UpdateConfig("tooltip", nil, "aurasource", "0")
pfUI:UpdateConfig("tooltip", nil, "spellid", "0")
pfUI:UpdateConfig("tooltip", nil, "unitid", "0")
pfUI:UpdateConfig("tooltip", nil, "showbuffs", "0")
pfUI:UpdateConfig("tooltip", nil, "alpha", "0.8")
pfUI:UpdateConfig("tooltip", nil, "alwaysperc", "0")
pfUI:UpdateConfig("tooltip", "compare", "basestats", "1")
@@ -954,6 +958,7 @@ function pfUI:LoadConfig()
pfUI:UpdateConfig("questlog", nil, "showQuestLevels", "0")
pfUI:UpdateConfig("character", "inventory", "durability", "1")
pfUI:UpdateConfig("character", "inventory", "equipflyout", "0")
pfUI:UpdateConfig("character", "reputation", "repRequired", "1")
pfUI:UpdateConfig("thirdparty", nil, "chatbg", "1")
pfUI:UpdateConfig("thirdparty", nil, "showmeter", "0")
@@ -961,6 +966,7 @@ function pfUI:LoadConfig()
pfUI:UpdateConfig("thirdparty", "dpsmate", "dock", "0")
pfUI:UpdateConfig("thirdparty", "shagudps", "skin", "0")
pfUI:UpdateConfig("thirdparty", "shagudps", "dock", "0")
pfUI:UpdateConfig("thirdparty", "greedmeter", "dock", "0")
pfUI:UpdateConfig("thirdparty", "swstats", "skin", "0")
pfUI:UpdateConfig("thirdparty", "swstats", "dock", "0")
pfUI:UpdateConfig("thirdparty", "ktm", "skin", "0")
+250 -4
View File
@@ -587,7 +587,7 @@ end
function pfUI.api.SetHighlight(frame, cr, cg, cb)
if not frame then return end
if not cr or not cg or not cb then
cr, cg, cb = GetClassColor(UnitClassBase('player'))
cr, cg, cb = PFUI_CLASS_COLORS[UnitClassBase('player')]:GetRGB()
end
frame.cr, frame.cg, frame.cb = cr, cg, cb, ca
@@ -632,7 +632,7 @@ function pfUI.api.SkinButton(button, cr, cg, cb, icon, disableHighlight)
if not b then b = button end
if not b then return end
if not cr or not cg or not cb then
cr, cg, cb = GetClassColor(UnitClassBase('player'))
cr, cg, cb = PFUI_CLASS_COLORS[UnitClassBase('player')]:GetRGB()
end
pfUI.api.CreateBackdrop(b, nil, true)
b:SetNormalTexture("")
@@ -709,7 +709,7 @@ end
function pfUI.api.SkinRotateButton(button)
pfUI.api.CreateBackdrop(button)
local cr, cg, cb = GetClassColor(UnitClassBase('player'))
local cr, cg, cb = PFUI_CLASS_COLORS[UnitClassBase('player')]:GetRGB()
local btnW, btnH = button:GetSize()
button:SetSize(btnW - 18, btnH - 18)
@@ -907,7 +907,7 @@ function pfUI.api.SkinDropDown(frame, cr, cg, cb, useSmall)
end
if not cr or not cg or not cb then
cr, cg, cb = GetClassColor(UnitClassBase('player'))
cr, cg, cb = PFUI_CLASS_COLORS[UnitClassBase('player')]:GetRGB()
end
SetHighlight(button, cr, cg, cb)
@@ -1246,3 +1246,249 @@ function pfUI.api.SkinMoneyInputFrame(frame)
copperIcon:ClearAllPoints()
copperIcon:SetPoint("LEFT", copper_editbox, "RIGHT", 2, 0)
end
-- Shared icon picker widget. Builds a name field, a "currently selected"
-- preview, and a 10x8 icon grid backed by IconDataProviderMixin with an
-- All/Spells/Items filter and a name search. Used by the macro icon picker
-- and the equipment manager's name/icon popup. The widgets attach to
-- `parent`; the caller owns the surrounding frame, its OK/Cancel buttons,
-- and the save flow. Selection is tracked by texture PATH (not index) so it
-- survives filter changes -- a spell icon you picked still saves after you
-- switch the filter to "Items" and it drops out of the visible grid.
--
-- name global-name prefix for the scroll frame and editboxes
-- parent the popup frame to attach the widgets to
-- extraType IconDataProviderExtraType.* seed (Spellbook / Equipment)
-- nameLabelText header shown above the name field
--
-- Returns a handle:
-- .editbox name EditBox (caller wires enter/escape)
-- .search search EditBox
-- .GetIcon() current selected texture path
-- .SetIcon(path) set selection (nil -> question mark)
-- .Refresh() ensure provider, redraw grid + preview
-- .SetIconAreaShown(shown) show/hide the grid, filter, search, and labels
function pfUI.api.CreateIconPicker(name, parent, extraType, nameLabelText)
local QUESTION_MARK = "INTERFACE\\ICONS\\INV_MISC_QUESTIONMARK"
local ICON_GRID_COLS = 10
local ICON_GRID_ROWS = 8
local ICON_BTN_SIZE = 36
local ICON_BTN_PAD = 6
local provider = nil
local selectedIconPath = QUESTION_MARK
local currentFilter = "all"
local searchText = ""
local filtered = nil -- provider indices matching the search, or nil when empty
local RefreshIconGrid, RebuildFilter
local function EnsureProvider()
if not provider then
provider = CreateAndInitFromMixin(IconDataProviderMixin, extraType)
end
end
-- Normalize a texture to an uppercase basename so matching works across
-- sources: GetMacroInfo / stored set icons return "Interface\Icons\<name>"
-- while provider paths are uppercase-prefixed for base icons and
-- mixed-case for spellbook extras.
local function IconKey(path)
if type(path) ~= "string" then return path end
return string.gsub(string.upper(path), "^.*[\\/]", "")
end
parent.nameLabel = parent:CreateFontString(nil, "OVERLAY", "GameFontNormal")
parent.nameLabel:SetPoint("TOPLEFT", parent, "TOPLEFT", 14, -20)
parent.nameLabel:SetText(nameLabelText)
parent.editbox = CreateFrame("EditBox", name.."Name", parent, "InputBoxTemplate")
parent.editbox:SetSize(280, 20)
parent.editbox:SetPoint("TOPLEFT", parent, "TOPLEFT", 14, -38)
parent.editbox:SetAutoFocus(false)
parent.editbox:SetMaxLetters(16)
CreateBackdrop(parent.editbox)
parent.selectedLabel = parent:CreateFontString(nil, "OVERLAY", "GameFontNormal")
parent.selectedLabel:SetPoint("TOPRIGHT", parent, "TOPRIGHT", -14, -14)
parent.selectedLabel:SetText(ICON_SELECTION_TITLE_CURRENT)
parent.selectedLabel:SetTextColor(1, 0.82, 0)
parent.selectedPreview = CreateFrame("Frame", nil, parent)
parent.selectedPreview:SetSize(42, 42)
parent.selectedPreview:SetPoint("TOPRIGHT", parent, "TOPRIGHT", -14, -30)
CreateBackdrop(parent.selectedPreview)
parent.selectedPreview.tex = parent.selectedPreview:CreateTexture(nil, "ARTWORK")
parent.selectedPreview.tex:SetAllPoints(parent.selectedPreview)
parent.selectedPreview.tex:SetTexCoord(.08, .92, .08, .92)
parent.iconLabel = parent:CreateFontString(nil, "OVERLAY", "GameFontNormal")
parent.iconLabel:SetPoint("TOPLEFT", parent, "TOPLEFT", 14, -86)
parent.iconLabel:SetText(MACRO_POPUP_CHOOSE_ICON)
-- Icon grid + scroll
local iconScroll = CreateFrame("ScrollFrame", name.."Scroll", parent, "FauxScrollFrameTemplate")
iconScroll:SetPoint("TOPLEFT", parent, "TOPLEFT", 14, -116)
iconScroll:SetWidth(ICON_GRID_COLS * (ICON_BTN_SIZE + ICON_BTN_PAD) - ICON_BTN_PAD)
iconScroll:SetHeight(ICON_GRID_ROWS * (ICON_BTN_SIZE + ICON_BTN_PAD) - ICON_BTN_PAD)
local scrollbar = _G[name.."ScrollScrollBar"]
scrollbar:ClearAllPoints()
scrollbar:SetPoint("TOPLEFT", iconScroll, "TOPRIGHT", 8, -16)
scrollbar:SetPoint("BOTTOMLEFT", iconScroll, "BOTTOMRIGHT", 8, 16)
SkinScrollbar(scrollbar)
-- Filter dropdown: All / Spells / Items
local filterDropdown = CreateFrame("Frame", name.."Filter", parent, "UIDropDownMenuTemplate")
filterDropdown:SetPoint("TOPRIGHT", parent, "TOPRIGHT", 0, -80)
local function ApplyFilter(value)
currentFilter = value
UIDropDownMenu_SetSelectedValue(filterDropdown, value)
if provider then
if value == "spells" then provider:SetIconTypes({ IconDataProviderIconType.Spell })
elseif value == "items" then provider:SetIconTypes({ IconDataProviderIconType.Item })
else provider:SetIconTypes(nil) end
RebuildFilter()
RefreshIconGrid()
end
end
UIDropDownMenu_Initialize(filterDropdown, function()
local info
info = {}; info.text = ICON_FILTER_ALL; info.value = "all"
info.func = function() ApplyFilter("all") end
info.checked = currentFilter == "all"
UIDropDownMenu_AddButton(info)
info = {}; info.text = ICON_FILTER_SPELL; info.value = "spells"
info.func = function() ApplyFilter("spells") end
info.checked = currentFilter == "spells"
UIDropDownMenu_AddButton(info)
info = {}; info.text = ICON_FILTER_ITEM; info.value = "items"
info.func = function() ApplyFilter("items") end
info.checked = currentFilter == "items"
UIDropDownMenu_AddButton(info)
end)
UIDropDownMenu_SetWidth(120, filterDropdown)
UIDropDownMenu_SetSelectedValue(filterDropdown, "all")
SkinDropDown(filterDropdown)
local iconButtons = {}
for r = 1, ICON_GRID_ROWS do
for c = 1, ICON_GRID_COLS do
local i = (r - 1) * ICON_GRID_COLS + c
local btn = CreateFrame("Button", nil, parent)
btn:SetSize(ICON_BTN_SIZE, ICON_BTN_SIZE)
btn:SetPoint("TOPLEFT", iconScroll, "TOPLEFT", (c-1) * (ICON_BTN_SIZE + ICON_BTN_PAD), -(r-1) * (ICON_BTN_SIZE + ICON_BTN_PAD))
CreateBackdrop(btn)
btn.texture = btn:CreateTexture(nil, "ARTWORK")
btn.texture:SetAllPoints(btn)
btn.texture:SetTexCoord(.08, .92, .08, .92)
btn:SetScript("OnClick", function()
if this.iconIndex and provider then
local path = provider:GetIconByIndex(this.iconIndex)
if path then selectedIconPath = path end
RefreshIconGrid()
end
end)
btn:SetScript("OnEnter", function()
if not this.iconIndex or not provider then return end
local path = provider:GetIconByIndex(this.iconIndex)
if type(path) == "string" then
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetText(IconKey(path), 1, 1, 1)
GameTooltip:Show()
end
end)
btn:SetScript("OnLeave", GameTooltip_Hide)
iconButtons[i] = btn
end
end
-- Rebuild the search-filtered index list (provider indices whose icon
-- basename contains the search text); nil when the search box is empty.
function RebuildFilter()
EnsureProvider()
if searchText == "" then
filtered = nil
return
end
filtered = {}
for i = 1, provider:GetNumIcons() do
local path = provider:GetIconByIndex(i)
if type(path) == "string" and string.find(IconKey(path), searchText, 1, true) then
table.insert(filtered, i)
end
end
end
function RefreshIconGrid()
EnsureProvider()
local numIcons = filtered and table.getn(filtered) or provider:GetNumIcons()
local numRows = math.ceil(numIcons / ICON_GRID_COLS)
FauxScrollFrame_Update(iconScroll, numRows, ICON_GRID_ROWS, ICON_BTN_SIZE + ICON_BTN_PAD)
local offset = FauxScrollFrame_GetOffset(iconScroll)
for i = 1, ICON_GRID_ROWS * ICON_GRID_COLS do
local listIdx = i + offset * ICON_GRID_COLS
local btn = iconButtons[i]
if listIdx <= numIcons then
btn:Show()
local providerIdx = filtered and filtered[listIdx] or listIdx
btn.iconIndex = providerIdx
local path = provider:GetIconByIndex(providerIdx)
btn.texture:SetTexture(path)
if IconKey(path) == IconKey(selectedIconPath) then
btn.backdrop:SetBackdropBorderColor(1, 0.82, 0, 1)
else
btn.backdrop:SetBackdropBorderColor(pfUI.cache.er, pfUI.cache.eg, pfUI.cache.eb, pfUI.cache.ea)
end
else
btn:Hide()
btn.iconIndex = nil
end
end
parent.selectedPreview.tex:SetTexture(selectedIconPath)
end
iconScroll:SetScript("OnVerticalScroll", function()
FauxScrollFrame_OnVerticalScroll(ICON_BTN_SIZE + ICON_BTN_PAD, function() RefreshIconGrid() end)
end)
-- Search box (bottom-left) filters the grid by icon name.
parent.searchLabel = parent:CreateFontString(nil, "OVERLAY", "GameFontNormal")
parent.searchLabel:SetPoint("BOTTOMLEFT", parent, "BOTTOMLEFT", 16, 18)
parent.searchLabel:SetText(SEARCH)
parent.search = CreateFrame("EditBox", name.."Search", parent, "InputBoxTemplate")
parent.search:SetSize(180, 20)
parent.search:SetPoint("LEFT", parent.searchLabel, "RIGHT", 10, 0)
parent.search:SetAutoFocus(false)
CreateBackdrop(parent.search)
parent.search:SetScript("OnEscapePressed", function() this:ClearFocus() end)
parent.search:SetScript("OnTextChanged", function()
searchText = strupper(strtrim(this:GetText()))
RebuildFilter()
scrollbar:SetValue(0)
RefreshIconGrid()
end)
-- Release the provider when the popup closes so its icon cache GCs.
parent:HookScript("OnHide", function()
if provider then provider:Release(); provider = nil end
end)
local handle = { editbox = parent.editbox, search = parent.search }
function handle.GetIcon() return selectedIconPath end
function handle.SetIcon(path) selectedIconPath = path or QUESTION_MARK end
function handle.Refresh() RefreshIconGrid() end
function handle.SetIconAreaShown(shown)
local method = shown and "Show" or "Hide"
iconScroll[method](iconScroll)
for _, b in ipairs(iconButtons) do b[method](b) end
parent.iconLabel[method](parent.iconLabel)
parent.selectedLabel[method](parent.selectedLabel)
parent.selectedPreview[method](parent.selectedPreview)
filterDropdown[method](filterDropdown)
parent.searchLabel[method](parent.searchLabel)
parent.search[method](parent.search)
end
return handle
end
+217 -234
View File
@@ -140,32 +140,43 @@ function pfUI.api.GetUnitStats(unitstr)
end
local aggrodata = { }
local aggroScan = nil -- static { u, t, tt } triples, built once so the hot scan allocates nothing
function pfUI.api.UnitHasAggro(unit)
-- Only cache positive results to allow instant detection when aggro changes
if aggrodata[unit] and aggrodata[unit].state > 0 and GetTime() < aggrodata[unit].check + 1 then
return aggrodata[unit].state
local now = GetTime()
local data = aggrodata[unit]
-- Cache positive results 1s (so aggro clears fast) AND negative results 0.3s
-- (so we don't rescan the whole unit table on every call while nothing has aggro).
if data and now < data.check + (data.state > 0 and 1 or 0.3) then
return data.state
end
aggrodata[unit] = aggrodata[unit] or { }
aggrodata[unit].check = GetTime()
aggrodata[unit].state = 0
if not data then data = { }; aggrodata[unit] = data end
data.check = now
data.state = 0
if UnitExists(unit) and UnitIsFriend(unit, "player") then
for u in pairs(pfValidUnits) do
local t = u .. "target"
local tt = t .. "target"
if UnitExists(t) and UnitIsUnit(t, unit) and UnitCanAttack(u, unit) then
aggrodata[unit].state = aggrodata[unit].state + 1
-- pfValidUnits never changes after load, so precompute the "<u>target" /
-- "<u>targettarget" token strings once instead of concatenating per call.
if not aggroScan then
aggroScan = {}
for u in pairs(pfValidUnits) do
local t = u .. "target"
table.insert(aggroScan, { u = u, t = t, tt = t .. "target" })
end
end
if UnitExists(tt) and UnitIsUnit(tt, unit) and UnitCanAttack(t, unit) then
aggrodata[unit].state = aggrodata[unit].state + 1
for i = 1, table.getn(aggroScan) do
local s = aggroScan[i]
if UnitExists(s.t) and UnitIsUnit(s.t, unit) and UnitCanAttack(s.u, unit) then
data.state = data.state + 1
end
if UnitExists(s.tt) and UnitIsUnit(s.tt, unit) and UnitCanAttack(s.t, unit) then
data.state = data.state + 1
end
end
end
return aggrodata[unit].state
return data.state
end
pfUI.uf.glow = CreateFrame("Frame", nil, UIParent)
@@ -194,8 +205,13 @@ function pfUI.uf:UpdateVisibility()
-- cache result of strsub to avoid repeating calls
if not self.cache_raid then
if strsub(self:GetName(),0,6) == "pfRaid" then
self.cache_raid = tonumber(strsub(self:GetName(),7,8)) or 0
local name = self:GetName()
if strsub(name,0,9) == "pfRaidPet" then
-- pet grid slot (pfRaidPet1..40); used to mirror party pets below
self.cache_raid = 0
self.cache_raidpet = tonumber(strsub(name,10)) or 0
elseif strsub(name,0,6) == "pfRaid" then
self.cache_raid = tonumber(strsub(name,7,8)) or 0
else
self.cache_raid = 0
end
@@ -227,6 +243,26 @@ function pfUI.uf:UpdateVisibility()
end
end
-- show raidpet frames as party pets when a party is shown as a raid grid
if self.cache_raidpet then
if not IsInRaid() and IsInGroup() and C.unitframes.raidforgroup == "1" then
local id = self.cache_raidpet
if id == 1 then
-- grid slot 1 mirrors the player, so its pet is the player's pet
self.id = ""
self.label = "pet"
elseif id <= 5 then
self.id = id - 1
self.label = "partypet"
end
-- reset to regular raidpet unitstrings after leaving party mode
elseif self.label == "pet" or self.label == "partypet" then
self.id = self.cache_raidpet
self.label = "raidpet"
end
end
-- display every unit as player while pfUI.uf.showall is set
if pfUI.uf.showall then
self._label = self._label or self.label
@@ -238,6 +274,7 @@ function pfUI.uf:UpdateVisibility()
end
local unitstr = string.format("%s%s", self.label or "", self.id or "")
self:SetAttribute("unit", unitstr ~= "" and unitstr or nil)
local visibility = string.format("[target=%s,exists] show; hide", unitstr)
-- Group frames are redundant when the group is already shown as a raid grid:
@@ -254,9 +291,10 @@ function pfUI.uf:UpdateVisibility()
-- frame shall not be visible
visibility = "hide"
self.visible = nil
elseif hide_group and self.cache_raid == 0 and self.label and strsub(self.label,0,5) == "party" then
-- hide group while shown as a raid grid and option is set (raid frames
-- carry label "party" under raidforgroup, so exclude them by cache_raid)
elseif hide_group and self.cache_raid == 0 and not self.cache_raidpet and self.label and strsub(self.label,0,5) == "party" then
-- hide group while shown as a raid grid and option is set (raid player
-- frames carry label "party" and raid pet frames carry label "partypet"
-- under raidforgroup, so exclude them by cache_raid / cache_raidpet)
visibility = "hide"
self.visible = nil
elseif ( self.fname == "Group0" or self.fname == "PartyPet0" or self.fname == "Party0Target" )
@@ -401,7 +439,7 @@ function pfUI.uf:UpdateConfig()
if tonumber(f.config.pheight) < 0 then f.power:Hide() end
pfUI.api.CreateBackdrop(f.power, default_border)
f.power.bar:SetFrameLevel(f.power:GetFrameLevel() + 1)
f.power.bar:SetFrameLevel(f.power.backdrop:GetFrameLevel() + 1)
f.power.bar:SetStatusBarTexture(pfUI.media[f.config.pbartexture])
f.power.bar:SetAllPoints(f.power)
@@ -1110,11 +1148,7 @@ function pfUI.uf.OnUpdate()
-- update ressurections
local ress = libpredict:UnitHasIncomingResurrection(unit)
if ress and UnitIsDeadOrGhost(unit) then
this.ressIcon:Show()
else
this.ressIcon:Hide()
end
this.ressIcon:SetShown(ress and UnitIsDeadOrGhost(unit))
end
end
end
@@ -1177,52 +1211,6 @@ function pfUI.uf.OnUpdate()
end
end
function pfUI.uf.OnEnter()
if not this.label then return end
local unitstr = this.label .. this.id
if UnitExists(unitstr) then
SetMouseoverUnit(unitstr)
end
if this.config.showtooltip == "0" then return end
GameTooltip_SetDefaultAnchor(GameTooltip, this)
GameTooltip:SetUnit(this.label .. this.id)
GameTooltip:Show()
end
function pfUI.uf.OnLeave()
SetMouseoverUnit("")
GameTooltip:FadeOut()
end
function pfUI.uf.OnClick()
if not this.label and this.unitname then
TargetByName(this.unitname, true)
else
pfUI.uf:ClickAction(arg1)
end
end
function pfUI.uf:RightClickAction(unit)
if unit == "player" then
ToggleDropDownMenu(1, nil, PlayerFrameDropDown, "cursor")
elseif unit == "target" then
ToggleDropDownMenu(1, nil, TargetFrameDropDown, "cursor")
elseif unit == "pet" then
ToggleDropDownMenu(1, nil, PetFrameDropDown, "cursor")
elseif unit == "party" or strfind(unit, "party%d") then
ToggleDropDownMenu(1, nil, getglobal("PartyMemberFrame" .. this.id .. "DropDown"), "cursor")
elseif unit == "raid" or strfind(unit, "raid%d") then
local name = this.lastUnit
local unitstr = this.label .. this.id
FriendsDropDown.displayMode = "MENU"
FriendsDropDown.initialize = function() UnitPopup_ShowMenu(_G[UIDROPDOWNMENU_OPEN_MENU], "PARTY", unitstr, name, id) end
ToggleDropDownMenu(1, nil, FriendsDropDown, "cursor")
end
end
function pfUI.uf:EnableEvents()
local f = self
@@ -1258,17 +1246,72 @@ function pfUI.uf:EnableEvents()
'MiddleButtonUp', 'Button4Up', 'Button5Up')
end
-- ============================================================
-- Clique compatibility
-- Clique's pfUI plugin replaces pfUI.uf.ClickAction and expects an
-- OnClick script to call it (with `this` = frame, `arg1` = button). The
-- secure attribute path replaced that dispatch, so when Clique is loaded
-- EnableScripts routes clicks through this legacy Lua path instead. The
-- secure path is skipped there because type1="target" switches the target
-- before Clique can cast.
-- ============================================================
function pfUI.uf.OnClick()
if not this.label and this.unitname then
TargetByName(this.unitname, true)
else
pfUI.uf:ClickAction(arg1)
end
end
function pfUI.uf:ClickAction(button)
local label = this.label or ""
local id = this.id or ""
local unitstr = label .. id
if SpellIsTargeting() and button == "RightButton" then
SpellStopTargeting()
return
end
if SpellIsTargeting() and button == "LeftButton" then
SpellTargetUnit(unitstr)
elseif CursorHasItem() then
DropItemOnUnit(unitstr)
end
-- right-click opens the standard unit menu (ClassicAPI resolves the type)
if button == "RightButton" then
ClassicAPI_ToggleUnitMenu(unitstr)
return
end
-- drop food on petframe
if label == "pet" and CursorHasItem() then
if UnitClassBase("player") == "HUNTER" then
DropItemOnUnit("pet")
return
end
end
-- default click
TargetUnit(unitstr)
end
function pfUI.uf:EnableScripts()
local f = self
f:SetScript("OnClick", pfUI.uf.OnClick)
if IsAddOnLoaded("Clique") then
f:SetScript("OnClick", pfUI.uf.OnClick)
else
f:SetAttribute("type1", "target")
f:SetAttribute("type2", "menu")
f:EnableClickCast()
end
f:SetScript("OnShow", pfUI.uf.OnShow)
f:SetScript("OnEvent", pfUI.uf.OnEvent)
f:SetScript("OnUpdate", pfUI.uf.OnUpdate)
f:SetScript("OnEnter", pfUI.uf.OnEnter)
f:SetScript("OnLeave", pfUI.uf.OnLeave)
f:EnableClickCast()
-- add frame to visibility refresh handler
visibilityscan.frames[f] = true
@@ -1296,7 +1339,7 @@ function pfUI.uf:CreateUnitFrame(unit, id, config, tick)
unit, id = "target", ""
end
local f = CreateFrame("Button", "pf" .. fname, UIParent, UNITFRAME_SECURE_TEMPLATE)
local f = CreateFrame("Button", "pf" .. fname, UIParent)
-- add unitframe functions
f.UpdateFrameSize = pfUI.uf.UpdateFrameSize
@@ -1403,7 +1446,7 @@ function pfUI.uf:CreateUnitFrame(unit, id, config, tick)
f.group:SetFont(pfUI.font_unit, 8, "OUTLINE")
f.group:SetTextColor(1,1,1,.8)
f.group:SetHeight(16)
f.group:SetText("Group " .. group)
f.group:SetText(T["Group"] .. " " .. group)
f.group:Hide()
end
@@ -1423,6 +1466,10 @@ function pfUI.uf:CreateUnitFrame(unit, id, config, tick)
f:Hide()
end
if f.label ~= "" then
f:SetAttribute("unit", f.label .. f.id)
end
-- register frame for clique
_G.ClickCastFrames = ClickCastFrames or {}
ClickCastFrames[f] = true
@@ -1477,11 +1524,7 @@ function pfUI.uf:RefreshIndicators(unit)
local unitstr = unit.label .. unit.id
if unit.leaderIcon then -- Leader Icon
if unit.config.leadericon == "1" and UnitIsPartyLeader(unitstr) and IsInGroup() then
unit.leaderIcon:Show()
else
unit.leaderIcon:Hide()
end
unit.leaderIcon:SetShown(unit.config.leadericon == "1" and UnitIsPartyLeader(unitstr) and IsInGroup())
end
if unit.lootIcon then -- Loot Icon
@@ -1491,29 +1534,16 @@ function pfUI.uf:RefreshIndicators(unit)
-- no third return value here.. but leaving this as a hint
local method, group, raid = GetLootMethod()
local name = group and UnitName(group == 0 and "player" or "party"..group) or raid and UnitName("raid"..raid) or nil
if name and name == UnitName(unitstr) then
unit.lootIcon:Show()
else
unit.lootIcon:Hide()
end
unit.lootIcon:SetShown(name and name == UnitName(unitstr))
end
end
if unit.pvpIcon then -- PvP Icon
if unit.config.showPVP == "1" and UnitIsPVP(unitstr) then
unit.pvpIcon:Show()
else
unit.pvpIcon:Hide()
end
unit.pvpIcon:SetShown(unit.config.showPVP == "1" and UnitIsPVP(unitstr))
end
if unit.restIcon and unit:GetName() == "pfPlayer" then -- Rest Icon
if C.unitframes.player.showRest == "1" and UnitIsUnit(unitstr, "player") and IsResting() then
unit.restIcon:Show()
else
unit.restIcon:Hide()
end
unit.restIcon:SetShown(C.unitframes.player.showRest == "1" and UnitIsUnit(unitstr, "player") and IsResting())
end
if unit.happinessIcon and unit:GetName() == "pfPet" then -- Happiness Icon
@@ -1583,7 +1613,7 @@ function pfUI.uf:RefreshUnit(unit, component)
if not unit.hp then return end
if not unit.power then return end
if not unit.id then unit.id = "" end
local component = component or ""
component = component or ""
-- don't update scanner activity
if unit.label == "target" or unit.label == "targettarget" or unit.label == "targettargettarget" then
@@ -1608,39 +1638,40 @@ function pfUI.uf:RefreshUnit(unit, component)
for i=1, unit.config.bufflimit do
if not unit.buffs[i] then break end
local aura = C_UnitAuras.GetBuffDataByIndex(unitstr, i)
-- positional UnitBuff allocates nothing (vs a table per icon per refresh)
local name, icon, count, _, duration, expirationTime, _, _, _, spellId = C_UnitAuras.UnitBuff(unitstr, i)
if aura then
unit.buffs[i].texture:SetTexture(aura.icon)
if name then
unit.buffs[i].texture:SetTexture(icon)
unit.buffs[i]:Show()
if aura.applications > 1 then
unit.buffs[i].stacks:SetText(aura.applications)
if count > 1 then
unit.buffs[i].stacks:SetText(count)
else
unit.buffs[i].stacks:SetText("")
end
if aura.expirationTime > 0 then
if expirationTime > 0 then
-- pfUI's cooldown text falls into a 2^32 wraparound branch when start > GetTime(),
-- which happens for talent-extended buffs where the real duration exceeds aura.duration
-- (the Spell.dbc base). Anchor start to now in that case to keep the remaining math sane.
-- which happens for talent-extended buffs where the real duration exceeds the
-- Spell.dbc base. Anchor start to now in that case to keep the remaining math sane.
local now = GetTime()
local start = aura.expirationTime - aura.duration
local duration = aura.duration
if start > now or duration <= 0 then
start, duration = now, aura.expirationTime - now
local start = expirationTime - duration
local dur = duration
if start > now or dur <= 0 then
start, dur = now, expirationTime - now
end
if duration > 0 then
CooldownFrame_SetTimer(unit.buffs[i].cd, start, duration, 1)
if dur > 0 then
CooldownFrame_SetTimer(unit.buffs[i].cd, start, dur, 1)
else
CooldownFrame_SetTimer(unit.buffs[i].cd, 0, 0, 0)
end
elseif aura.duration > 0 then
elseif duration > 0 then
local guid = UnitGUID(unitstr)
local guidStarts = guid and pfUI.uf.aura_starts[guid]
local start = guidStarts and guidStarts[aura.spellId]
local start = guidStarts and guidStarts[spellId]
if start then
CooldownFrame_SetTimer(unit.buffs[i].cd, start, aura.duration, 1)
CooldownFrame_SetTimer(unit.buffs[i].cd, start, duration, 1)
else
CooldownFrame_SetTimer(unit.buffs[i].cd, 0, 0, 0)
end
@@ -1711,10 +1742,11 @@ function pfUI.uf:RefreshUnit(unit, component)
-- selfdebuff narrows to player-cast harmful auras via the PLAYER filter.
-- Player-frame debuffs aren't gated on it (it'd hide most party-applied
-- effects on you).
local filter = (unit.label ~= "player" and selfdebuff == "1") and "HARMFUL|PLAYER" or "HARMFUL"
local aura = C_UnitAuras.GetAuraDataByIndex(unitstr, i, filter)
if aura then
texture, stacks, dtype = aura.icon, aura.applications, aura.dispelName
-- positional UnitDebuff allocates nothing; PLAYER predicate honored for selfdebuff
local filter = (unit.label ~= "player" and selfdebuff == "1") and "PLAYER" or nil
local name, icon, count, dispelType, duration, expirationTime = C_UnitAuras.UnitDebuff(unitstr, i, filter)
if name then
texture, stacks, dtype = icon, count, dispelType
else
texture, stacks, dtype = nil, 0, nil
end
@@ -1727,18 +1759,18 @@ function pfUI.uf:RefreshUnit(unit, component)
if texture then
unit.debuffs[i]:Show()
if aura and aura.expirationTime > 0 then
if expirationTime > 0 then
-- Cap start to now so talent-extended debuffs (expirationTime past
-- the dbc base duration) don't push start into the future and trip
-- CooldownFrame_SetTimer's 2^32-wraparound branch.
local now = GetTime()
local start = aura.expirationTime - aura.duration
local duration = aura.duration
if start > now or duration <= 0 then
start, duration = now, aura.expirationTime - now
local start = expirationTime - duration
local dur = duration
if start > now or dur <= 0 then
start, dur = now, expirationTime - now
end
if duration > 0 then
CooldownFrame_SetTimer(unit.debuffs[i].cd, start, duration, 1)
if dur > 0 then
CooldownFrame_SetTimer(unit.debuffs[i].cd, start, dur, 1)
else
CooldownFrame_SetTimer(unit.debuffs[i].cd, 0, 0, 0)
end
@@ -1796,6 +1828,17 @@ function pfUI.uf:RefreshUnit(unit, component)
end
end
-- Scan the 16 harmful slots once into a reusable set, instead of
-- re-scanning all 16 for every dispellable type below. Reused across
-- frames (populated and consumed within this single RefreshUnit call).
local present = pfUI.uf.dispelPresent or {}
pfUI.uf.dispelPresent = present
for k in pairs(present) do present[k] = nil end
for i=1,16 do
local name, _, _, dispelType = C_UnitAuras.UnitDebuff(unitstr, i)
if name and dispelType and dispelType ~= "" then present[dispelType] = true end
end
for _, debuff in pairs(unit.dispellable) do
indicator[debuff] = indicator[debuff] or CreateFrame("Frame", nil, indicator)
indicator[debuff]:SetParent(indicator)
@@ -1835,15 +1878,7 @@ function pfUI.uf:RefreshUnit(unit, component)
indicator[debuff].disp = indicator.disp
end
indicator[debuff].visible = nil
for i=1,16 do
local a = C_UnitAuras.GetDebuffDataByIndex(unitstr, i)
local dtype = a and a.dispelName
if dtype == debuff then
indicator[debuff].visible = true
end
end
indicator[debuff].visible = present[debuff]
if indicator[debuff].visible then
indicator[debuff]:Show()
@@ -1890,50 +1925,58 @@ function pfUI.uf:RefreshUnit(unit, component)
local pos = 1
if table.getn(unit.indicators) > 0 then
for _, aura in ipairs(C_UnitAuras.GetUnitAuras(unitstr, "HELPFUL")) do
local texLower = string.lower(aura.icon)
local timeleft = aura.expirationTime > 0 and (aura.expirationTime - GetTime()) or nil
local i = 1
while true do
local name, icon, count, _, _, expirationTime = C_UnitAuras.UnitBuff(unitstr, i)
if not name then break end
local texLower = string.lower(icon)
local timeleft = expirationTime > 0 and (expirationTime - GetTime()) or nil
for _, filter in pairs(unit.indicators) do
if filter == texLower then
local hot = HOT_INDICATORS[texLower]
if hot and string.lower(aura.name) ~= hot.name then
if hot and string.lower(name) ~= hot.name then
break -- texture matches but name disambiguates (e.g. shared icon)
end
if hot then
local start, duration, prediction = libpredict:GetHotDuration(unitstr, hot.predict)
pfUI.uf:AddIcon(unit, pos, aura.icon, timeleft or prediction, aura.applications, tonumber(start), tonumber(duration))
pfUI.uf:AddIcon(unit, pos, icon, timeleft or prediction, count, tonumber(start), tonumber(duration))
else
pfUI.uf:AddIcon(unit, pos, aura.icon, timeleft, aura.applications)
pfUI.uf:AddIcon(unit, pos, icon, timeleft, count)
end
pos = pos + 1
break
end
end
i = i + 1
end
end
if table.getn(unit.indicator_custom) > 0 then
for _, aura in ipairs(C_UnitAuras.GetUnitAuras(unitstr, "HELPFUL")) do
local timeleft = aura.expirationTime > 0 and (aura.expirationTime - GetTime()) or nil
local lowerName = string.lower(aura.name)
local ai = 1
while true do
local name, icon, count, _, _, expirationTime = C_UnitAuras.UnitBuff(unitstr, ai)
if not name then break end
local timeleft = expirationTime > 0 and (expirationTime - GetTime()) or nil
local lowerName = string.lower(name)
for _, filter in pairs(unit.indicator_custom) do
if filter == lowerName then
pfUI.uf:AddIcon(unit, pos, aura.icon, timeleft, aura.applications)
pfUI.uf:AddIcon(unit, pos, icon, timeleft, count)
pos = pos + 1
break
end
end
ai = ai + 1
end
local debuffFilter = unit.config.selfdebuff == "1" and "HARMFUL|PLAYER" or "HARMFUL"
local debuffFilter = unit.config.selfdebuff == "1" and "PLAYER" or nil
for i=1,16 do -- scan for custom debuffs
local aura = C_UnitAuras.GetAuraDataByIndex(unitstr, i, debuffFilter)
if aura then
local timeleft = aura.expirationTime > 0 and (aura.expirationTime - GetTime()) or nil
local name, icon, count, _, _, expirationTime = C_UnitAuras.UnitDebuff(unitstr, i, debuffFilter)
if name then
local timeleft = expirationTime > 0 and (expirationTime - GetTime()) or nil
for _, filter in pairs(unit.indicator_custom) do
if filter == string.lower(aura.name) then
pfUI.uf:AddIcon(unit, pos, aura.icon, timeleft, aura.applications)
if filter == string.lower(name) then
pfUI.uf:AddIcon(unit, pos, icon, timeleft, count)
pos = pos + 1
break
end
@@ -2126,14 +2169,6 @@ function pfUI.uf:RefreshUnit(unit, component)
end
end
local buttons = {
[1] = "LeftButton",
[2] = "RightButton",
[3] = "MiddleButton",
[4] = "Button4",
[5] = "Button5",
}
local modifiers = {
[""] = "",
["alt"] = "_alt",
@@ -2143,86 +2178,34 @@ local modifiers = {
function pfUI.uf:EnableClickCast()
if self.config.clickcast ~= "1" then return end
for bid, button in pairs(buttons) do
for bid = 1, 5 do -- LeftButton, RightButton, MiddleButton, Button4, Button5
for modifier, mconf in pairs(modifiers) do
local bconf = bid == 1 and "" or bid
if pfUI_config.unitframes["clickcast"..bconf..mconf] ~= "" then
-- fill clickaction table
self.clickactions = self.clickactions or {}
self.clickactions[modifier..button] = pfUI_config.unitframes["clickcast"..bconf..mconf]
local action = pfUI_config.unitframes["clickcast"..bconf..mconf]
if action and action ~= "" then
local prefix = modifier ~= "" and (modifier .. "-") or ""
local low = string.lower(action)
if low == "menu" then
self:SetAttribute(prefix .. "type" .. bid, "menu")
elseif low == "target" then
self:SetAttribute(prefix .. "type" .. bid, "target")
elseif low == "focus" then
self:SetAttribute(prefix .. "type" .. bid, "focus")
elseif string.find(low, "^macro:") then
self:SetAttribute(prefix .. "type" .. bid, "macro")
self:SetAttribute(prefix .. "macro" .. bid, string.gsub(string.sub(action, 7), "^%s+", ""))
elseif string.find(action, "^/") then
self:SetAttribute(prefix .. "type" .. bid, "macro")
self:SetAttribute(prefix .. "macrotext" .. bid, action)
else
self:SetAttribute(prefix .. "type" .. bid, "spell")
self:SetAttribute(prefix .. "spell" .. bid, action)
end
end
end
end
end
function pfUI.uf:ClickAction(button)
local label = this.label or ""
local id = this.id or ""
local unitstr = label .. id
local showmenu = button == "RightButton" and true or nil
if SpellIsTargeting() and button == "RightButton" then
SpellStopTargeting()
return
end
if SpellIsTargeting() and button == "LeftButton" then
SpellTargetUnit(unitstr)
elseif CursorHasItem() then
DropItemOnUnit(unitstr)
end
-- run click casting if enabled
local modstring = ""
modstring = IsAltKeyDown() and modstring.."alt" or modstring
modstring = IsControlKeyDown() and modstring.."ctrl" or modstring
modstring = IsShiftKeyDown() and modstring.."shift" or modstring
modstring = modstring..button
if this.clickactions and this.clickactions[modstring] then
local action_lower = string.lower(this.clickactions[modstring])
if string.find(action_lower, "^menu") then
-- show menu
showmenu = true
elseif string.find(action_lower, "^target") then
-- target unit
showmenu = nil
else
-- run click cast action
local is_macro = string.find(this.clickactions[modstring], "^%/(.+)")
local tswitch = UnitIsUnit(unitstr, "target")
TargetUnit(unitstr)
if is_macro then
RunMacroText(this.clickactions[modstring])
else
CastSpellByName(this.clickactions[modstring])
end
if not tswitch then TargetLastTarget() end
return
end
end
-- dropdown menus
if showmenu then
pfUI.uf:RightClickAction(label)
return
end
-- drop food on petframe
if label == "pet" and CursorHasItem() then
local playerClass = UnitClassBase("player")
if playerClass == "HUNTER" then
DropItemOnUnit("pet")
return
end
end
-- default click
TargetUnit(unitstr)
end
function pfUI.uf:AddIcon(frame, pos, icon, timeleft, stacks, start, duration)
local showtime = frame.config.indicator_time == "1" and true or nil
local showstacks = frame.config.indicator_stacks == "1" and true or nil
-1
View File
@@ -25,7 +25,6 @@ PLAYER_BUFF_START_ID = -1
ACTIONBAR_SECURE_TEMPLATE_BAR = nil
ACTIONBAR_SECURE_TEMPLATE_BUTTON = nil
UNITFRAME_SECURE_TEMPLATE = nil
--[[ Vanilla API Extensions ]]--
-1065
View File
File diff suppressed because it is too large Load Diff
-1067
View File
File diff suppressed because it is too large Load Diff
-1060
View File
File diff suppressed because it is too large Load Diff
-1056
View File
File diff suppressed because it is too large Load Diff
-1026
View File
File diff suppressed because it is too large Load Diff
-1063
View File
File diff suppressed because it is too large Load Diff
-1080
View File
File diff suppressed because it is too large Load Diff
+6
View File
@@ -63,6 +63,12 @@ for i=1,40 do
pfValidUnits["nameplate" .. i .. "target"] = true
end
if TURTLE_WOW_VERSION then -- We already know this table is going to be replaced by turtle-wow.lua, so there's no point in declaring it.
pfSellData = {} -- create empty just incase user has turtle-wow module disabled.
return
end
-- This table makes me sick.
-- itemid = buyPrice (copper). Sell price comes from C_Item.GetItemSellPriceByID;
-- this table only carries the curated vendor-buy price (which isn't a static
-- engine field — it's collected from real vendor encounters).
+4 -1
View File
@@ -51,10 +51,12 @@ pfUI_translation["deDE"] = {
["Auto Sell Grey Items"] = nil,
["Average Per Hour"] = nil,
["Background Color"] = nil,
["Bags"] = "Taschen",
["Bags & Bank"] = nil,
["Bags Border Size"] = nil,
["Bagslots Per Row"] = nil,
["Bagspace"] = nil,
["Bank"] = "Bank",
["Banker"] = nil,
["Bankslots Per Row"] = nil,
["Bar Background"] = nil,
@@ -290,7 +292,6 @@ pfUI_translation["deDE"] = {
["Enable Mana Ticks"] = nil,
["Enable Micro Bar"] = nil,
["Enable Mouselook With Right Click"] = nil,
["Enable Mouseover Tooltip"] = nil,
["Enable Movable Bags"] = nil,
["Enable Offscreen Frame Positions"] = nil,
["Enable Overlap"] = nil,
@@ -311,6 +312,7 @@ pfUI_translation["deDE"] = {
["Encode"] = nil,
["Ended"] = nil,
["Energy Color"] = nil,
["Equipped"] = "Angelegt",
["Equipped Item Color"] = nil,
["Estimate Debuffs"] = nil,
["Estimate Enemy Health Points"] = nil,
@@ -819,6 +821,7 @@ pfUI_translation["deDE"] = {
["Top Actionbar"] = nil,
["Top Left"] = nil,
["Top Right"] = nil,
["Total"] = "Gesamt",
["Total Gold"] = nil,
["Totem Direction"] = nil,
["Totem Icons"] = nil,
+10 -1
View File
@@ -52,15 +52,19 @@ pfUI_translation["enUS"] = {
["Auto Sell Grey Items"] = nil,
["Average Per Hour"] = nil,
["Background Color"] = nil,
["Bags"] = nil,
["Bags & Bank"] = nil,
["Bags Border Size"] = nil,
["Bagslots Per Row"] = nil,
["Bagspace"] = nil,
["Bank"] = nil,
["Banker"] = nil,
["Bankslots Per Row"] = nil,
["Bar Background"] = nil,
["Bar Texture"] = nil,
["Battleground Frames"] = nil,
["Battleground Statistics"] = nil,
["BEHIND"] = nil,
["Blacklist"] = nil,
["Blinding Powder"] = nil,
["Blue Border On Friendly Players"] = nil,
@@ -293,7 +297,6 @@ pfUI_translation["enUS"] = {
["Enable Mana Ticks"] = nil,
["Enable Micro Bar"] = nil,
["Enable Mouselook With Right Click"] = nil,
["Enable Mouseover Tooltip"] = nil,
["Enable Movable Bags"] = nil,
["Enable Offscreen Frame Positions"] = nil,
["Enable Overlap"] = nil,
@@ -314,6 +317,7 @@ pfUI_translation["enUS"] = {
["Encode"] = nil,
["Ended"] = nil,
["Energy Color"] = nil,
["Equipped"] = nil,
["Equipped Item Color"] = nil,
["Estimate Debuffs"] = nil,
["Estimate Enemy Health Points"] = nil,
@@ -326,6 +330,7 @@ pfUI_translation["enUS"] = {
["Export"] = nil,
["Face"] = nil,
["Fade To Custom Color"] = nil,
["FARM MODE"] = nil,
["Fast"] = nil,
["Filter Mode"] = nil,
["Finish"] = nil,
@@ -536,6 +541,7 @@ pfUI_translation["enUS"] = {
["Next Memory Cleanup"] = nil,
["No"] = nil,
["No Anchor"] = nil,
["NO LOS"] = nil,
["None"] = nil,
["Not a valid button!"] = nil,
["No tracking spell active"] = nil,
@@ -771,6 +777,8 @@ pfUI_translation["enUS"] = {
["Small"] = nil,
["Some settings need to reload the UI to take effect.\nDo you want to reload now?"] = nil,
["Some settings need to reload the UI to take effect.\nDo you want to reloadUI now?"] = nil,
["Sort Bags"] = nil,
["Sort Bank"] = nil,
["Sort Order"] = nil,
["Soul Shard"] = nil,
["Soulshard Counter"] = nil,
@@ -835,6 +843,7 @@ pfUI_translation["enUS"] = {
["Top Actionbar"] = nil,
["Top Left"] = nil,
["Top Right"] = nil,
["Total"] = nil,
["Total Gold"] = nil,
["Totem Direction"] = nil,
["Totem Icons"] = nil,
+4 -1
View File
@@ -51,10 +51,12 @@ pfUI_translation["esES"] = {
["Auto Sell Grey Items"] = "Vender objetos grises automáticamente",
["Average Per Hour"] = "Promedio por hora",
["Background Color"] = "Color del fondo",
["Bags"] = "Bolsas",
["Bags & Bank"] = "Bolsas y banco",
["Bags Border Size"] = "Tamaño del borde de las bolsas",
["Bagslots Per Row"] = "Casillas de la bolsa por fila",
["Bagspace"] = "Huecos en las bolsas",
["Bank"] = "Banco",
["Banker"] = "Banquero",
["Bankslots Per Row"] = "Casillas del banco por fila",
["Bar Background"] = "Fondo de la barra",
@@ -290,7 +292,6 @@ pfUI_translation["esES"] = {
["Enable Mana Ticks"] = "Activar el indicador de pulsos del maná",
["Enable Micro Bar"] = "Activar microbarra",
["Enable Mouselook With Right Click"] = "Mirar usando el puntero con clic derecho",
["Enable Mouseover Tooltip"] = "Activar la descripción emergente al pasar el puntero",
["Enable Movable Bags"] = "Permitir mover las bolsas",
["Enable Offscreen Frame Positions"] = "Permitir mover los marcos fuera de la pantalla",
["Enable Overlap"] = "Permitir superposición",
@@ -311,6 +312,7 @@ pfUI_translation["esES"] = {
["Encode"] = "Codificar",
["Ended"] = "Terminado",
["Energy Color"] = "Color de energía",
["Equipped"] = "Equipado",
["Equipped Item Color"] = "Color del objeto equipado",
["Estimate Debuffs"] = "Estimar los perjuicios",
["Estimate Enemy Health Points"] = "Estimar los puntos de salud del enemigo",
@@ -819,6 +821,7 @@ pfUI_translation["esES"] = {
["Top Actionbar"] = "Barra de acción superior",
["Top Left"] = "Superior derecha",
["Top Right"] = "Superior izquierda",
["Total"] = "Total",
["Total Gold"] = "Oro Total",
["Totem Direction"] = "Dirección del tótem",
["Totem Icons"] = "Iconos de los tótems",
+4 -1
View File
@@ -51,10 +51,12 @@ pfUI_translation["frFR"] = {
["Auto Sell Grey Items"] = "Vente automatique des objets gris",
["Average Per Hour"] = "Moyenne par heure",
["Background Color"] = "Couleur de l'arrière-plan",
["Bags"] = "Sacs",
["Bags & Bank"] = "Sacs et Banque",
["Bags Border Size"] = "Taille de la bordure des Sacs",
["Bagslots Per Row"] = "Emplacements des sacs par rangée",
["Bagspace"] = "Place libre des sacs",
["Bank"] = "Banque",
["Banker"] = "Banquier",
["Bankslots Per Row"] = "Emplacements de la banque par rangée",
["Bar Background"] = "Arrière plan de la barre",
@@ -290,7 +292,6 @@ pfUI_translation["frFR"] = {
["Enable Mana Ticks"] = "Activer les Ticks de mana",
["Enable Micro Bar"] = "Activer la barre des menus miniature",
["Enable Mouselook With Right Click"] = "Activer le déplacement de la caméra avec le clic droit",
["Enable Mouseover Tooltip"] = "Activer l'infobulle au passage de la souris",
["Enable Movable Bags"] = "Activer les sacs mobiles",
["Enable Offscreen Frame Positions"] = "Activer le positionnement des cadres en dehors de l'écran",
["Enable Overlap"] = "Activer la superposition",
@@ -311,6 +312,7 @@ pfUI_translation["frFR"] = {
["Encode"] = "Encoder",
["Ended"] = "Terminé",
["Energy Color"] = "Couleur de l'energie",
["Equipped"] = "Équipé",
["Equipped Item Color"] = "Couleur de l'objet équipé",
["Estimate Debuffs"] = "Estimer les Affaiblissements",
["Estimate Enemy Health Points"] = "Estimer les points de vie ennemis",
@@ -819,6 +821,7 @@ pfUI_translation["frFR"] = {
["Top Actionbar"] = "Barre d'action supérieure",
["Top Left"] = "Haut Gauche",
["Top Right"] = "Haut Droit",
["Total"] = "Total",
["Total Gold"] = "Or Total",
["Totem Direction"] = "Direction des totems",
["Totem Icons"] = "Icones des totems",
+4 -1
View File
@@ -51,10 +51,12 @@ pfUI_translation["koKR"] = {
["Auto Sell Grey Items"] = "회색아이템 자동 판매",
["Average Per Hour"] = nil,
["Background Color"] = "백그라운드 색상",
["Bags"] = "가방",
["Bags & Bank"] = "가방&은행",
["Bags Border Size"] = "가방 테두리 크기",
["Bagslots Per Row"] = nil,
["Bagspace"] = "가방 공간",
["Bank"] = "은행",
["Banker"] = nil,
["Bankslots Per Row"] = nil,
["Bar Background"] = nil,
@@ -290,7 +292,6 @@ pfUI_translation["koKR"] = {
["Enable Mana Ticks"] = nil,
["Enable Micro Bar"] = "미니바 보기(블리자드 마이크로바)",
["Enable Mouselook With Right Click"] = "오른쪽 클릭 Mouselook 사용",
["Enable Mouseover Tooltip"] = nil,
["Enable Movable Bags"] = nil,
["Enable Offscreen Frame Positions"] = "오프스크린 프레임 위치 활성화",
["Enable Overlap"] = "겹쳐서 표시",
@@ -311,6 +312,7 @@ pfUI_translation["koKR"] = {
["Encode"] = nil,
["Ended"] = nil,
["Energy Color"] = nil,
["Equipped"] = "착용 중",
["Equipped Item Color"] = nil,
["Estimate Debuffs"] = nil,
["Estimate Enemy Health Points"] = nil,
@@ -819,6 +821,7 @@ pfUI_translation["koKR"] = {
["Top Actionbar"] = nil,
["Top Left"] = nil,
["Top Right"] = nil,
["Total"] = "",
["Total Gold"] = "총 금",
["Totem Direction"] = nil,
["Totem Icons"] = nil,
+4 -1
View File
@@ -51,10 +51,12 @@ pfUI_translation["ruRU"] = {
["Auto Sell Grey Items"] = "Автоматическая продажа серых предметов",
["Average Per Hour"] = "Среднее за час",
["Background Color"] = "Цвет фона",
["Bags"] = "Сумки",
["Bags & Bank"] = "Сумки & Банк",
["Bags Border Size"] = "Размер границы сумок",
["Bagslots Per Row"] = "Количество ячеек в сумке на строку",
["Bagspace"] = "Место в сумках",
["Bank"] = "Банк",
["Banker"] = "Банкир",
["Bankslots Per Row"] = "Количество ячеек в банке на строку",
["Bar Background"] = "Фон панели",
@@ -290,7 +292,6 @@ pfUI_translation["ruRU"] = {
["Enable Mana Ticks"] = "Включить восполнение маны",
["Enable Micro Bar"] = "Включить панель микро меню",
["Enable Mouselook With Right Click"] = "Включить нажатие правой кнопки мыши на индикаторе здоровья",
["Enable Mouseover Tooltip"] = "Включить подсказку при наведении мыши",
["Enable Movable Bags"] = "Включить подвижные сумки",
["Enable Offscreen Frame Positions"] = "Включить расположение окон за пределами экрана",
["Enable Overlap"] = "Включить перекрытие",
@@ -311,6 +312,7 @@ pfUI_translation["ruRU"] = {
["Encode"] = "Кодировать",
["Ended"] = "Окончание",
["Energy Color"] = "Цвет энергии",
["Equipped"] = "Экипировано",
["Equipped Item Color"] = "Цвет экипированных предметов",
["Estimate Debuffs"] = "Оценка дебафов",
["Estimate Enemy Health Points"] = "Оценка очков здоровья врага",
@@ -819,6 +821,7 @@ pfUI_translation["ruRU"] = {
["Top Actionbar"] = "Верхняя",
["Top Left"] = "Сверху слева",
["Top Right"] = "Сверху справа",
["Total"] = "Всего",
["Total Gold"] = "Всего золота",
["Totem Direction"] = "Направление тотемов",
["Totem Icons"] = "Иконки тотемов",
+4 -1
View File
@@ -51,10 +51,12 @@ pfUI_translation["zhCN"] = {
["Auto Sell Grey Items"] = "自动贩卖灰色物品",
["Average Per Hour"] = "平均每小时",
["Background Color"] = "背景颜色",
["Bags"] = "背包",
["Bags & Bank"] = "背包和银行",
["Bags Border Size"] = "背包边框大小",
["Bagslots Per Row"] = "每行包裹槽数量",
["Bagspace"] = "背包空间",
["Bank"] = "银行",
["Banker"] = "工会银行角色",
["Bankslots Per Row"] = "每行银行槽数量",
["Bar Background"] = "动作条背景",
@@ -290,7 +292,6 @@ pfUI_translation["zhCN"] = {
["Enable Mana Ticks"] = "显示法力值刻度",
["Enable Micro Bar"] = "显示菜单栏",
["Enable Mouselook With Right Click"] = "启动右键移动镜头",
["Enable Mouseover Tooltip"] = "启用鼠标悬停工具提示",
["Enable Movable Bags"] = "启用可移动包裹",
["Enable Offscreen Frame Positions"] = "允许本插件所有框体移出屏幕边缘",
["Enable Overlap"] = "叠加显示",
@@ -311,6 +312,7 @@ pfUI_translation["zhCN"] = {
["Encode"] = "编码",
["Ended"] = "结束",
["Energy Color"] = "能量值颜色",
["Equipped"] = "已装备",
["Equipped Item Color"] = "已装备物品颜色",
["Estimate Debuffs"] = "预估Debuffs",
["Estimate Enemy Health Points"] = "预估敌人的生命值",
@@ -820,6 +822,7 @@ pfUI_translation["zhCN"] = {
["Top Actionbar"] = "上方动作条",
["Top Left"] = "左上方",
["Top Right"] = "右上方",
["Total"] = "总计",
["Total Gold"] = "总黄金",
["Totem Direction"] = "图腾方向",
["Totem Icons"] = "图腾图标",
+4 -1
View File
@@ -51,10 +51,12 @@ pfUI_translation["zhTW"] = {
["Auto Sell Grey Items"] = "自動販賣灰色物品",
["Average Per Hour"] = "平均每小時",
["Background Color"] = "背景顏色",
["Bags"] = "背包",
["Bags & Bank"] = "背包和銀行",
["Bags Border Size"] = "背包邊框大小",
["Bagslots Per Row"] = "每行包裹槽數量",
["Bagspace"] = "背包空間",
["Bank"] = "銀行",
["Banker"] = "工會銀行角色",
["Bankslots Per Row"] = "每行銀行槽數量",
["Bar Background"] = nil,
@@ -290,7 +292,6 @@ pfUI_translation["zhTW"] = {
["Enable Mana Ticks"] = nil,
["Enable Micro Bar"] = "顯示功能表列",
["Enable Mouselook With Right Click"] = "啟動右鍵移動鏡頭",
["Enable Mouseover Tooltip"] = "啟用滑鼠懸停工具提示",
["Enable Movable Bags"] = "啟用可移動包裹",
["Enable Offscreen Frame Positions"] = "啟用螢幕框架固定",
["Enable Overlap"] = "疊加顯示",
@@ -311,6 +312,7 @@ pfUI_translation["zhTW"] = {
["Encode"] = "編碼",
["Ended"] = nil,
["Energy Color"] = nil,
["Equipped"] = "已裝備",
["Equipped Item Color"] = nil,
["Estimate Debuffs"] = nil,
["Estimate Enemy Health Points"] = nil,
@@ -819,6 +821,7 @@ pfUI_translation["zhTW"] = {
["Top Actionbar"] = nil,
["Top Left"] = "左上方",
["Top Right"] = "右上方",
["Total"] = "全部",
["Total Gold"] = "總金量",
["Totem Direction"] = nil,
["Totem Icons"] = nil,
+2
View File
@@ -71,6 +71,7 @@
<Include file="..\modules\energytick.lua"/>
<Include file="..\modules\totems.lua"/>
<Include file="..\modules\macrotweak.lua"/>
<Include file="..\modules\macroicons.lua"/>
<Include file="..\modules\turtle-wow.lua"/>
<Include file="..\modules\superwow.lua"/>
<Include file="..\modules\innervatecall.lua"/>
@@ -80,4 +81,5 @@
<Include file="..\modules\equipmentmanager.lua"/>
<Include file="..\modules\loothistory.lua"/>
<Include file="..\modules\newitem.lua"/>
<Include file="..\modules\friendnotes.lua"/>
</Ui>
-2
View File
@@ -41,8 +41,6 @@
<!-- Turtle WoW -->
<Include file="..\skins\blizzard\barbershop.lua"/>
<Include file="..\skins\blizzard\transmog.lua"/>
<Include file="..\skins\blizzard\custom_merchant.lua"/>
<Include file="..\skins\blizzard\arena_score.lua"/>
<Include file="..\skins\blizzard\ebc.lua"/>
</Ui>
+26 -67
View File
@@ -376,11 +376,9 @@ local function GetDebuffSlotMap(guid)
local texture = libdebuff:GetSpellIcon(spellId)
local stacks = (auraApps and auraApps[auraSlot] or 0) + 1
local dtype = nil
if GetSpellRecField then
local dispelId = GetSpellRecField(spellId, "dispel")
if dispelId and dispelId > 0 then
dtype = dispelTypeMap[dispelId]
end
local dispelId = C_Spell.GetSpellDispelType(spellId)
if dispelId and dispelId > 0 then
dtype = dispelTypeMap[dispelId]
end
map[displaySlot] = {
auraSlot = auraSlot,
@@ -593,37 +591,13 @@ end
-- DURATION FUNCTIONS
-- ============================================================================
-- Report the live, talent-accurate duration of a matching aura on the player.
-- ClassicAPI's C_UnitAuras carries the real (mod-folded) duration straight from
-- the engine, so there's no static duration table to maintain. Returns 0 when
-- the player has no such aura (e.g. a debuff that only ever lands on enemies).
function libdebuff:GetDuration(effect, rank)
if L["debuffs"][effect] then
local rank = rank and tonumber((string.gsub(rank, RANK, ""))) or 0
local rank = L["debuffs"][effect][rank] and rank or libdebuff:GetMaxRank(effect)
local duration = L["debuffs"][effect][rank]
if effect == L["dyndebuffs"]["Demoralizing Shout"] then
local _,_,_,_,count = GetTalentInfo(2,1)
if count and count > 0 then duration = duration + ( duration / 100 * (count*10)) end
elseif effect == L["dyndebuffs"]["Shadow Word: Pain"] then
local _,_,_,_,count = GetTalentInfo(3,4)
if count and count > 0 then duration = duration + count * 3 end
elseif effect == L["dyndebuffs"]["Frostbolt"] then
local _,_,_,_,count = GetTalentInfo(3,7)
if count and count > 0 then duration = duration + count end
elseif effect == L["dyndebuffs"]["Gouge"] then
local _,_,_,_,count = GetTalentInfo(3,3)
if count and count > 0 then duration = duration + (count*.5) end
end
return duration
else
return 0
end
end
function libdebuff:GetMaxRank(effect)
local max = 0
for id in pairs(L["debuffs"][effect]) do
if id > max then max = id end
end
return max
local aura = C_UnitAuras.GetAuraDataBySpellName("player", effect)
return aura and aura.duration or 0
end
function libdebuff:UpdateDuration(unit, unitlevel, effect, duration)
@@ -649,7 +623,6 @@ libdebuff.objects = {}
function libdebuff:AddPending(unit, unitlevel, effect, duration, caster, rank)
if not unit or duration <= 0 then return end
if not L["debuffs"][effect] then return end
if libdebuff.pending[3] then return end
libdebuff.pending[1] = unit
@@ -728,39 +701,25 @@ end
-- API: GetBestAuraCast (for libpredict HoT tracking)
-- ============================================================================
-- Report the active aura for spellName on `guid`, read straight from
-- C_UnitAuras. expirationTime is the true, caster-modified remaining time, so
-- no cast-event bookkeeping is needed -- and a unit only ever holds one instance
-- of a given spell, so this is inherently the "best" one.
-- Returns (startTime, duration, timeleft, rank, casterGuid) or nil.
function libdebuff:GetBestAuraCast(guid, spellName)
if not guid or not spellName then return nil end
-- Check ownDebuffs first (for our casts)
if ownDebuffs[guid] and ownDebuffs[guid][spellName] then
local data = ownDebuffs[guid][spellName]
local timeleft = (data.startTime + data.duration) - GetTime()
if timeleft > 0 then
return data.startTime, data.duration, timeleft, data.rank, GetPlayerGuid()
end
end
-- Check allAuraCasts (for any caster)
if allAuraCasts[guid] and allAuraCasts[guid][spellName] then
local bestData = nil
local bestCaster = nil
local bestTimeleft = 0
for casterGuid, data in pairs(allAuraCasts[guid][spellName]) do
local timeleft = (data.startTime + data.duration) - GetTime()
if timeleft > bestTimeleft then
bestTimeleft = timeleft
bestData = data
bestCaster = casterGuid
end
end
if bestData and bestTimeleft > 0 then
return bestData.startTime, bestData.duration, bestTimeleft, bestData.rank, bestCaster
end
end
return nil
local aura = C_UnitAuras.GetAuraDataBySpellName(guid, spellName)
if not aura or not aura.expirationTime or aura.expirationTime == 0 then return nil end
local timeleft = aura.expirationTime - GetTime()
if timeleft <= 0 then return nil end
local duration = (aura.duration and aura.duration > 0) and aura.duration or timeleft
local startTime = aura.expirationTime - duration
local rank = aura.spellId and tonumber((string.gsub(C_Spell.GetSpellSubtext(aura.spellId) or "", RANK, ""))) or nil
return startTime, duration, timeleft, rank, aura.sourceGUID
end
-- ============================================================================
+4 -1
View File
@@ -168,6 +168,9 @@ pfUI.libdebuff_spell_start_self_hooks["libpredict"] = function(spellId, casterGu
return
end
spell_queue[1] = spellName
spell_queue[2] = spellName .. (C_Spell.GetSpellSubtext(spellId) or "")
if spell_queue[1] == spellName and cache[spell_queue[2]] then
local amount = cache[spell_queue[2]][1]
local casttime = castTime
@@ -1222,7 +1225,7 @@ libpredict.sender:SetScript("OnEvent", function()
end)
function libpredict:GetHotDuration(unit, spell)
if unit == UNKNOWNOBJECT or unit == UNKOWNBEING then return end
if unit == UNKNOWNOBJECT or unit == UKNOWNBEING then return end
-- NEW: Try libdebuff first (Nampower AURA_CAST events)
if pfUI.api.libdebuff and pfUI.api.libdebuff.GetBestAuraCast then
+6 -5
View File
@@ -83,12 +83,13 @@ end
-- [number] Casting time of the spell in milliseconds
-- [number] Minimum range from the target required to cast the spell
-- [number] Maximum range from the target at which you can cast the spell
-- [number] The numeric spell-id of the spell
-- [number] Spell's index in the book
-- [number] The type of the spellbook that the spell is in
-- [number] The numeric spell-id of the spell
local spellinfo = {}
function libspell.GetSpellInfo(index, bookType)
local cache = spellinfo[index]
if cache then return cache[1], cache[2], cache[3], cache[4], cache[5], cache[6], cache[7], cache[8] end
if cache then return unpack(cache) end
local slot
if type(index) == "string" then
@@ -108,10 +109,10 @@ function libspell.GetSpellInfo(index, bookType)
-- ClassicAPI's GetSpellInfo returns: name, rank, icon, cost, isFunnel, powerType,
-- castTime(ms), minRange, maxRange, spellID. Keep libspell's historical positional
-- shape (castingTime at 4, ranges at 5/6, slot+bookType at 7/8).
local name, rank, icon, _, _, _, castingTime, minRange, maxRange = GetSpellInfo(slot, bookType)
local name, rank, icon, _, _, _, castingTime, minRange, maxRange, spellId = GetSpellInfo(slot, bookType)
spellinfo[index] = { name, rank, icon, castingTime, minRange, maxRange, slot, bookType }
return name, rank, icon, castingTime, minRange, maxRange, slot, bookType
spellinfo[index] = SafePack(name, rank, icon, castingTime, minRange, maxRange, slot, bookType, spellId)
return name, rank, icon, castingTime, minRange, maxRange, slot, bookType, spellId
end
-- Reset all spell caches whenever new spells are learned/unlearned
+10 -11
View File
@@ -96,13 +96,12 @@ libunitscan:SetScript("OnEvent", function()
RememberByUnit("player", name, class)
elseif event == "FRIENDLIST_UPDATE" then
local name, class, level
for i = 1, GetNumFriends() do
name, level, class = GetFriendInfo(i)
class = L["class"][class] or nil
-- friendlist updates due to friend going off-line return level 0, let's not overwrite good older values
level = level > 0 and level or nil
AddData("players", name, class, level)
local info = C_FriendList.GetFriendInfoByIndex(i)
if info then
local level = info.level > 0 and info.level or nil
AddData("players", info.name, info.classFilename, level)
end
end
elseif event == "GUILD_ROSTER_UPDATE" then
@@ -137,11 +136,11 @@ libunitscan:SetScript("OnEvent", function()
end
elseif event == "WHO_LIST_UPDATE" or event == "CHAT_MSG_SYSTEM" then
local name, class, level, guild, _
for i = 1, GetNumWhoResults() do
name, guild, level, _, class, _ = GetWhoInfo(i)
class = L["class"][class] or nil
AddData("players", name, class, level, nil, guild)
for i = 1, C_FriendList.GetNumWhoResults() do
local info = C_FriendList.GetWhoInfo(i)
if info then
AddData("players", info.fullName, info.filename, info.level, nil, info.fullGuildName)
end
end
elseif event == "UPDATE_MOUSEOVER_UNIT" or event == "PLAYER_TARGET_CHANGED" or event == "NAME_PLATE_UNIT_ADDED" then
+46 -63
View File
@@ -371,12 +371,12 @@ pfUI:RegisterModule("actionbar", function ()
-- active conditional dynamically, instead of being frozen to the first
-- statically-scanned spell.
if CleveRoids and CleveRoids.IsManagedAction and CleveRoids.IsManagedAction(self.id) then
self.spellslot, self.booktype = nil, nil
self.spellslot, self.booktype, self.spellID = nil, nil, nil
return
end
local kind, slot = GetActionInfo(self.id)
self.spellslot, self.booktype = nil, nil
self.spellslot, self.booktype, self.spellID = nil, nil, nil
if kind == 'macro' then
local name, _, body = GetMacroInfo(slot)
@@ -414,10 +414,7 @@ pfUI:RegisterModule("actionbar", function ()
end
if match then
local _, _, spell, rank = string.find(match, '(.+)%((.+)%)')
spell = spell or match
self.spellslot, self.booktype = libspell.GetSpellIndex(spell, rank)
self.spellslot, self.booktype, self.spellID = select(7, libspell.GetSpellInfo(match))
if self.spellslot and self.spellslot > 0 then return end
end
end
@@ -426,7 +423,7 @@ pfUI:RegisterModule("actionbar", function ()
end
local function ButtonEnter(self)
local self = self or this
self = self or this
-- indicate that dragging could get enabled
drag_await = true
@@ -444,6 +441,8 @@ pfUI:RegisterModule("actionbar", function ()
else
GameTooltip:SetPetAction(self.id)
end
elseif self.spellID then
GameTooltip:SetSpellByID(self.spellID)
elseif self.spellslot and self.booktype then
GameTooltip:SetSpell(self.spellslot, self.booktype)
else
@@ -454,7 +453,7 @@ pfUI:RegisterModule("actionbar", function ()
end
local function ButtonLeave(self)
local self = self or this
self = self or this
-- no longer wait for a drag event
drag_await = nil
@@ -467,7 +466,7 @@ pfUI:RegisterModule("actionbar", function ()
local grid, sid, id, bar, active, texture, _
local function ButtonSlotUpdate(self)
if not self then return end
local self = self or this
self = self or this
sid = self.id -- 1 to 120
-- reset shared variables
@@ -586,7 +585,7 @@ pfUI:RegisterModule("actionbar", function ()
local sid, usable, oom, _
local function ButtonUsableUpdate(self)
local self = self or this
self = self or this
sid = self.id -- 1 to 120
if self.bar == 11 then
@@ -600,17 +599,17 @@ pfUI:RegisterModule("actionbar", function ()
-- update usable [out-of-range = 1, oom = 2, not-usable = 3, default = 0]
if self.outofrange and C.bars.glowrange == "1" then
if self.vertexstate ~= 1 then
self.icon:SetVertexColor(self.rangeColor[1], self.rangeColor[2], self.rangeColor[3], self.rangeColor[4])
self.icon:SetVertexColor(self.rangeColor:GetRGBA())
self.vertexstate = 1
end
elseif oom and C.bars.showoom == "1" then
if self.vertexstate ~= 2 then
self.icon:SetVertexColor(self.oomColor[1], self.oomColor[2], self.oomColor[3], self.oomColor[4])
self.icon:SetVertexColor(self.oomColor:GetRGBA())
self.vertexstate = 2
end
elseif not usable and C.bars.showna == "1" then
if self.vertexstate ~= 3 then
self.icon:SetVertexColor(self.naColor[1], self.naColor[2], self.naColor[3], self.naColor[4])
self.icon:SetVertexColor(self.naColor:GetRGBA())
self.vertexstate = 3
end
else
@@ -622,7 +621,7 @@ pfUI:RegisterModule("actionbar", function ()
end
local function ButtonRangeUpdate(self)
local self = self or this
self = self or this
-- update range display
if C.bars.glowrange == "1" and self.bar ~= 11 and self.bar ~= 12 and HasAction(self.id) and ActionHasRange(self.id) and IsActionInRange(self.id) == 0 then
@@ -700,7 +699,7 @@ pfUI:RegisterModule("actionbar", function ()
end
local function BarsEvent(self)
local self = self or this
self = self or this
-- refresh only specific slots
if event == "ACTIONBAR_SLOT_CHANGED" and arg1 and arg1 ~= 0 then
@@ -992,13 +991,13 @@ pfUI:RegisterModule("actionbar", function ()
local font_offset = tonumber(C.bars.font_offset)
local macro_size = tonumber(C.bars.macro_size)
local macro_color = { strsplit(",", C.bars.macro_color) }
local macro_color = { GetStringColor(C.bars.macro_color) }
local count_size = tonumber(C.bars.count_size)
local count_color = { strsplit(",", C.bars.count_color) }
local count_color = { GetStringColor(C.bars.count_color) }
local bind_size = tonumber(C.bars.bind_size)
local bind_color = { strsplit(",", C.bars.bind_color) }
local bind_color = { GetStringColor(C.bars.bind_color) }
local cd_size = tonumber(C.bars.cd_size)
@@ -1157,17 +1156,17 @@ pfUI:RegisterModule("actionbar", function ()
end
-- range glow color
f.rangeColor = { strsplit(",", C.bars.rangecolor) }
f.rangeColor = GetStringColorObject(C.bars.rangecolor)
-- out of mana color
f.oomColor = { strsplit(",", C.bars.oomcolor) }
f.oomColor = GetStringColorObject(C.bars.oomcolor)
-- not usable color
f.naColor = { strsplit(",", C.bars.nacolor) }
f.naColor = GetStringColorObject(C.bars.nacolor)
-- equipped color
if f.equipped then
f.equipped:SetTexture(strsplit(",", C.bars.eqcolor))
f.equipped:SetTexture(GetStringColor(C.bars.eqcolor))
end
-- general appearance
@@ -1546,7 +1545,7 @@ pfUI:RegisterModule("actionbar", function ()
-- via GetActionInfo + C_Spell.GetSpellReagents. Macro actions and
-- bag-item actions are skipped (their reagent resolution would need a
-- macro-body parse / item-effect lookup that we don't bother with).
local UpdateSlot = function(slot)
local function UpdateSlot(slot)
local newID = nil
if HasAction(slot) then
local kind, spellID = GetActionInfo(slot)
@@ -1561,60 +1560,44 @@ pfUI:RegisterModule("actionbar", function ()
if reagent_slots[slot] ~= newID then
reagent_slots[slot] = newID
if newID then
reagent_counts[newID] = reagent_counts[newID] or 0
reagent_counts[newID] = reagent_counts[newID] or C_Item.GetItemCount(newID)
end
updatecache[slot] = true
end
end
-- Recount every tracked reagent and flag its buttons for a refresh.
local RecountReagents = function()
for itemID in pairs(reagent_counts) do
reagent_counts[itemID] = C_Item.GetItemCount(itemID)
end
for slot in pairs(reagent_slots) do
updatecache[slot] = true
end
end
local reagentcounter = CreateFrame("Frame", "pfReagentCounter", UIParent)
reagentcounter:RegisterEvent("PLAYER_ENTERING_WORLD")
reagentcounter:RegisterEvent("ACTIONBAR_SLOT_CHANGED")
reagentcounter:RegisterEvent("BAG_UPDATE_DELAYED")
reagentcounter:SetScript("OnEvent", function()
if event == "BAG_UPDATE_DELAYED" then
this.event = true
if event == "ACTIONBAR_SLOT_CHANGED" then
-- arg1 is the changed slot; 0 (or nil) means "all slots"
if arg1 and arg1 > 0 then
UpdateSlot(arg1)
else
for slot = 1, 120 do UpdateSlot(slot) end
end
elseif event == "BAG_UPDATE_DELAYED" then
-- inventory changed: refresh the counts we already track
RecountReagents()
else
this.scan = 1
-- PLAYER_ENTERING_WORLD: seed the full reagent map (UpdateSlot seeds
-- each new reagent's count; a BAG_UPDATE_DELAYED follows during login)
for slot = 1, 120 do UpdateSlot(slot) end
end
end)
-- Reagent counter update with throttle for performance optimization
reagentcounter:SetScript("OnUpdate", function()
-- Throttle entire function to 10 FPS for smooth scanning
if (this.tick_update or 0) > GetTime() then return end
this.tick_update = GetTime() + 0.1
-- scan one action slot per update
if this.scan and this.scan <= 120 then
UpdateSlot(this.scan)
this.scan = this.scan + 1
end
-- trigger reagent count updates after action scans
if this.scan and this.scan >= 120 then
this.event = true
this.scan = nil
end
-- queue events to fire only once per second
if not this.event then return end
if ( this.tick or 1) > GetTime() then return else this.tick = GetTime() + 1 end
-- scan for all reagent item counts
for itemID in pairs(reagent_counts) do
reagent_counts[itemID] = C_Item.GetItemCount(itemID)
end
-- update all actionbar buttons
for slot in pairs(reagent_slots) do
updatecache[slot] = true
end
-- remove event trigger
this.event = nil
end)
function IsReagentAction(slot)
return reagent_slots[slot] and true or nil
end
+16 -13
View File
@@ -397,22 +397,25 @@ pfUI:RegisterModule("addonbuttons", function ()
pfUI.addonbuttons:ProcessButtons()
end)
pfUI.addonbuttons:SetScript("OnUpdate", function()
-- Initial setup on the next frame, once other addons' minimap buttons exist
RunNextFrame(function()
-- check if the panel should be shown by default
if not this.initialized then
if C.abuttons.showdefault == "1" and GetNumButtons() > 0 then
pfUI.addonbuttons:Show()
else
pfUI.addonbuttons:Hide()
end
-- update all buttons
pfUI.addonbuttons:ProcessButtons()
this.initialized = true
if C.abuttons.showdefault == "1" and GetNumButtons() > 0 then
pfUI.addonbuttons:Show()
else
pfUI.addonbuttons:Hide()
end
-- throttle updates to once per 5 seconds
if ( this.tick or 1) > GetTime() then return else this.tick = GetTime() + 5 end
-- update all buttons and apply workarounds
pfUI.addonbuttons:ProcessButtons()
for k, v in pairs(pfUI.addonbuttons.overrides) do
_G[k] = v
end
end)
-- Rescan minimap buttons every 5 seconds while the panel is shown
C_Timer.NewTicker(5, function()
if not pfUI.addonbuttons:IsShown() then return end
-- reload/rescan minimap buttons
pfUI.addonbuttons:ProcessButtons()
+5
View File
@@ -2,6 +2,11 @@ pfUI:RegisterModule("autoshift", function ()
pfUI.autoshift = CreateFrame("Frame")
pfUI.autoshift:RegisterEvent("UI_ERROR_MESSAGE")
-- Empty stub kept only so Turtle WoW's pfUI-turtle addon doesn't error when
-- its obsolete moonkin autoshift module iterates pfUI.autoshift.shapeshifts.
-- This fork drives autoshift off GetShapeshiftFormID() and no longer uses it.
pfUI.autoshift.shapeshifts = {}
pfUI.autoshift.scanString = string.gsub(SPELL_FAILED_ONLY_SHAPESHIFT, "%%s", "(.+)")
pfUI.autoshift.errors = { SPELL_FAILED_NOT_MOUNTED, ERR_ATTACK_MOUNTED, ERR_TAXIPLAYERALREADYMOUNTED,
+2 -17
View File
@@ -972,7 +972,7 @@ pfUI:RegisterModule("bags", function ()
frame.sort.backdrop:SetBackdropBorderColor(1,1,.25,1)
frame.sort.texture:SetVertexColor(1,1,.25,1)
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetText("Sort Bags")
GameTooltip:SetText(T["Sort Bags"])
GameTooltip:Show()
end)
@@ -1085,21 +1085,6 @@ pfUI:RegisterModule("bags", function ()
frame.search.edit:ClearFocus()
end)
frame.search:SetScript("OnHide", function()
frame.search.edit:SetText(T["Search"])
for bag = -2, 11 do
if pfUI.bags[bag] then
local bagsize = GetContainerNumSlots(bag)
if bag == -2 and pfUI.bag.showKeyring == true then bagsize = GetKeyRingSize() end
for slot = 1, bagsize do
if pfUI.bags[bag] and pfUI.bags[bag].slots[slot] then
pfUI.bags[bag].slots[slot].frame:SetAlpha(1)
end
end
end
end
end)
frame.search.edit:SetScript("OnMouseUp", function()
if arg1 == "RightButton" then
this:ClearFocus()
@@ -1207,7 +1192,7 @@ pfUI:RegisterModule("bags", function ()
frame.sort.backdrop:SetBackdropBorderColor(1,1,.25,1)
frame.sort.texture:SetVertexColor(1,1,.25,1)
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetText("Sort Bank")
GameTooltip:SetText(T["Sort Bank"])
GameTooltip:Show()
end)
+1 -1
View File
@@ -32,7 +32,7 @@ pfUI:RegisterModule("bgscore", function ()
-- Title label
local title = mover:CreateFontString(nil, "OVERLAY")
title:SetFont("Fonts\\FRIZQT__.TTF", 14, "OUTLINE")
title:SetText("Battleground Frames")
title:SetText(T["Battleground Frames"])
title:SetPoint("TOP", mover, "TOP", 0, -2)
-- BG score preview text
+9 -15
View File
@@ -23,7 +23,7 @@ pfUI:RegisterModule("buff", function ()
CreateBackdropShadow(buff)
end
local aura = C_UnitAuras.GetAuraDataByIndex("player", buff.id, buff.btype)
local name, icon, count, dispelType, _, expirationTime, _, _, _, spellId = C_UnitAuras.UnitAura("player", buff.id, buff.btype)
--detect weapon buffs
if buff.btype == "HELPFUL" and ((C.buffs.separateweapons == "0" and buff.gid <= pfUI.buff.wepbuffs.count) or (pfUI.buff.wepbuffs.count > 0 and buff.weapon ~= nil)) then
@@ -55,16 +55,16 @@ pfUI:RegisterModule("buff", function ()
buff.texture:SetTexture(GetInventoryItemTexture("player", 17))
buff.backdrop:SetBackdropBorderColor(GetItemQualityColor(GetInventoryItemQuality("player", 17) or 1))
end
elseif aura and (( buff.btype == "HARMFUL" and C.buffs.debuffs == "1" ) or ( buff.btype == "HELPFUL" and C.buffs.buffs == "1" )) then
elseif name and (( buff.btype == "HARMFUL" and C.buffs.debuffs == "1" ) or ( buff.btype == "HELPFUL" and C.buffs.buffs == "1" )) then
-- Set Buff Texture and Border
buff.mode = buff.btype
buff.expirationTime = aura.expirationTime
buff.stackCount = aura.applications
buff.spellId = aura.spellId
buff.texture:SetTexture(aura.icon)
buff.expirationTime = expirationTime
buff.stackCount = count
buff.spellId = spellId
buff.texture:SetTexture(icon)
if buff.btype == "HARMFUL" then
local dispelColor = C_UnitAuras.GetAuraDispelTypeColor(aura.dispelName)
local dispelColor = C_UnitAuras.GetAuraDispelTypeColor(dispelType)
buff.backdrop:SetBackdropBorderColor(dispelColor:GetRGBA())
else
buff.backdrop:SetBackdropBorderColor(br,bg,bb,ba)
@@ -138,16 +138,10 @@ pfUI:RegisterModule("buff", function ()
end
end)
buff:SetScript("OnLeave", function()
GameTooltip:Hide()
end)
buff:SetScript("OnLeave", GameTooltip_Hide)
buff:SetScript("OnClick", function()
if CancelItemTempEnchantment and this.mode and this.mode == "MAINHAND" then
CancelItemTempEnchantment(1)
elseif CancelItemTempEnchantment and this.mode and this.mode == "OFFHAND" then
CancelItemTempEnchantment(2)
elseif this.spellId then
if this.spellId then
C_Spell.CancelSpellByID(this.spellId)
end
end)
+12 -11
View File
@@ -75,10 +75,10 @@ pfUI:RegisterModule("buffwatch", function ()
local function GetBuffData(unit, id, type, selfdebuff)
local filter = (selfdebuff and type == "HARMFUL") and "HARMFUL|PLAYER" or type
local aura = C_UnitAuras.GetAuraDataByIndex(unit, id, filter)
if not aura then return end
local remaining = aura.expirationTime > 0 and (aura.expirationTime - GetTime()) or 0
return remaining, aura.icon, aura.name, aura.applications
local name, icon, count, dispelType, _, expirationTime = C_UnitAuras.UnitAura(unit, id, filter)
if not name then return end
local remaining = expirationTime > 0 and (expirationTime - GetTime()) or 0
return remaining, icon, name, count, dispelType
end
local function StatusBarOnClick()
@@ -90,12 +90,14 @@ pfUI:RegisterModule("buffwatch", function ()
if val == skill then return end
end
config.whitelist = config.whitelist .. "#" .. skill
fcache[tostring(config)] = nil -- invalidate so the new entry takes effect immediately
DEFAULT_CHAT_FRAME:AddMessage("|cff33ffcc" .. skill .. "|r" .. T["is now whitelisted."])
elseif IsShiftKeyDown() then
for _, val in pairs({strsplit("#", config.blacklist)}) do
if val == skill then return end
end
config.blacklist = config.blacklist .. "#" .. skill
fcache[tostring(config)] = nil -- invalidate so the new entry takes effect immediately
DEFAULT_CHAT_FRAME:AddMessage("|cff33ffcc" .. skill .. "|r" .. T["is now blacklisted."])
end
elseif this.parent.unit == "player" then
@@ -234,7 +236,7 @@ pfUI:RegisterModule("buffwatch", function ()
local selfdebuff = frame.config.selfdebuff == "1"
for i=1,32 do
local timeleft, texture, name, stacks = GetBuffData(frame.unit, i, frame.type, selfdebuff)
local timeleft, texture, name, stacks, dtype = GetBuffData(frame.unit, i, frame.type, selfdebuff)
timeleft = timeleft or 0
if texture and name and name ~= "" and BuffIsVisible(frame.config, name) then
@@ -243,12 +245,14 @@ pfUI:RegisterModule("buffwatch", function ()
frame.buffs[i][3] = name
frame.buffs[i][4] = texture
frame.buffs[i][5] = stacks
frame.buffs[i][6] = dtype
else
frame.buffs[i][1] = 0
frame.buffs[i][2] = nil
frame.buffs[i][3] = nil
frame.buffs[i][4] = nil
frame.buffs[i][5] = 0
frame.buffs[i][6] = nil
end
end
@@ -292,12 +296,9 @@ pfUI:RegisterModule("buffwatch", function ()
-- calculate dynamic auto color
local r, g, b
if frame.type == "HARMFUL" then
r, g, b = 1, .2, .2
local a = C_UnitAuras.GetDebuffDataByIndex(frame.unit, data[2])
local dtype = a and a.dispelName
if dtype and DebuffTypeColor[dtype] then
r,g,b = DebuffTypeColor[dtype].r,DebuffTypeColor[dtype].g,DebuffTypeColor[dtype].b
end
-- official Blizzard dispel-type colors (matches unitframes/buff);
-- nil/unknown type falls back to the DEBUFF_TYPE_NONE colour
r, g, b = C_UnitAuras.GetAuraDispelTypeColor(data[6] or ""):GetRGBA()
else
r,g,b = str2rgb(data[3])
end
+113 -138
View File
@@ -16,9 +16,13 @@ pfUI:RegisterModule("castbar", function ()
end
end
-- Clear cast state on the bar. Shows the bar full for one frame, then
-- OnUpdate fades it out.
local function ClearBar(cb)
-- Clear cast state on the bar and start the fade-out. On a normal end the bar
-- is left at its current fill (a completed cast is already ~full; nothing
-- snaps to full, which used to flash for a frame when the next cast stamped
-- the bar). When `failed` is set for a cast that was actually in progress, the
-- bar flashes full red before fading — the cancelled-cast indicator.
local function ClearBar(cb, failed)
local wasActive = cb.endTime ~= nil
cb.startTime, cb.endTime, cb.isChannel = nil, nil, nil
cb.activeName, cb.spellID = nil, nil
cb.isTradeskill = nil
@@ -26,8 +30,11 @@ pfUI:RegisterModule("castbar", function ()
cb.tradeskillSingleMs, cb.currentCraftStart = nil, nil
cb.lastMax = nil
cb.delay = 0
cb.bar:SetMinMaxValues(1, 100)
cb.bar:SetValue(100)
if failed and wasActive then
cb.bar:SetStatusBarColor(GetStringColor(C.appearance.castbar.failcolor))
cb.bar:SetMinMaxValues(0, 1)
cb.bar:SetValue(1)
end
if cb.bar.spark then cb.bar.spark:Hide() end
cb.fadeout = 1
end
@@ -38,7 +45,7 @@ pfUI:RegisterModule("castbar", function ()
if not cb.tradeskillTotal or not cb.activeName or not cb.showname then return end
local remaining = cb.tradeskillTotal - (cb.tradeskillCompleted or 0)
if remaining > 1 then
cb.bar.left:SetText(string.format("%s (%d)", cb.activeName, remaining))
cb.bar.left:SetFormattedText("%s (%d)", cb.activeName, remaining)
else
cb.bar.left:SetText(cb.activeName)
end
@@ -65,8 +72,8 @@ pfUI:RegisterModule("castbar", function ()
end
-- Mark the start of craft 2..N within an active merge (called from the
-- SPELLCAST_START / SPELL_START_SELF handlers). Resets the per-craft
-- spark to the left edge of the bar.
-- UNIT_SPELLCAST_START handler). Resets the per-craft spark to the left
-- edge of the bar.
local function StartTradeskillCraft(cb)
cb.currentCraftStart = GetTime() * 1000
local remaining = cb.tradeskillTotal - (cb.tradeskillCompleted or 0)
@@ -79,7 +86,7 @@ pfUI:RegisterModule("castbar", function ()
-- Stamp the bar with cast data and render text/icon/lag once. OnUpdate
-- then animates the fill from this state without touching C_Spell.
local function StampBar(cb, name, tex, startMs, endMs, spellID, isChannel, delayMs, isTradeskill)
local function StampBar(cb, name, tex, startMs, endMs, spellID, isChannel, delayMs, isTradeskill, rank)
cb.startTime = startMs
cb.endTime = endMs
cb.isChannel = isChannel
@@ -92,10 +99,13 @@ pfUI:RegisterModule("castbar", function ()
cb.bar:SetStatusBarColor(GetStringColor(C.appearance.castbar[isChannel and "channelcolor" or "castbarcolor"]))
local rank = ""
if spellID then
-- Rank: prefer the value the UNIT_SPELLCAST_* event delivered (arg5, passed
-- through by RefreshBar). Only the retarget re-poll has no event in hand, so
-- it falls back to a lookup.
if not rank and spellID then
rank = C_Spell.GetSpellSubtext(spellID) or ""
end
rank = rank or ""
local spellname = (cb.showname and name) and (name .. " ") or ""
local rankstr = (cb.showrank and rank ~= "") and string.format("|cffaaffcc[%s]|r", rank) or ""
cb.bar.left:SetText(spellname .. rankstr)
@@ -122,11 +132,21 @@ pfUI:RegisterModule("castbar", function ()
cb.bar:SetMinMaxValues(0, duration)
cb.lastMax = duration
-- Prime the fill on this frame. StampBar otherwise leaves the previous
-- value in place (ClearBar, run on the prior cast's STOP, leaves it full),
-- so the bar would flash full for the frame between here and the next
-- OnUpdate tick. Reset the throttle too so the timer text updates promptly.
local nowSec = GetTime()
local cur = isChannel and (endMs / 1000 - nowSec) or (nowSec - startMs / 1000)
if cur < 0 then cur = 0 elseif cur > duration then cur = duration end
cb.bar:SetValue(cur)
cb.tick = 0
end
-- One-shot poll: read C_Spell for the bar's unit, stamp or clear. Called
-- from event handlers (cast start, target/focus change), never per-frame.
local function RefreshBar(cb)
local function RefreshBar(cb, rank)
local query = cb.unitstr ~= "" and cb.unitstr or cb.unitname
if not query or (cb.unitstr ~= "" and not UnitExists(cb.unitstr)) then
ClearBar(cb)
@@ -149,7 +169,7 @@ pfUI:RegisterModule("castbar", function ()
end
end
if name and startMs and endMs then
StampBar(cb, name, tex, startMs, endMs, spellID, isChan, delayMs, isTradeskill)
StampBar(cb, name, tex, startMs, endMs, spellID, isChan, delayMs, isTradeskill, rank)
else
ClearBar(cb)
end
@@ -294,35 +314,27 @@ pfUI:RegisterModule("castbar", function ()
end
end)
-- Cast lifecycle events. Player bars react to vanilla SPELLCAST_*; non-
-- player bars also react to Nampower SPELL_*_OTHER (gated by the
-- NP_EnableSpell{Start,Go}Events CVars, enabled by libdebuff) plus the
-- retarget event. Player events also feed non-player bars for the
-- target=self case.
cb:RegisterEvent("SPELLCAST_START")
cb:RegisterEvent("SPELLCAST_STOP")
cb:RegisterEvent("SPELLCAST_FAILED")
cb:RegisterEvent("SPELLCAST_INTERRUPTED")
cb:RegisterEvent("SPELLCAST_CHANNEL_START")
cb:RegisterEvent("SPELLCAST_CHANNEL_STOP")
cb:RegisterEvent("SPELLCAST_CHANNEL_UPDATE")
cb:RegisterEvent("SPELL_DELAYED_SELF")
-- Chained same-spell recasts never run the client cast path (the 1.12
-- engine short-circuits at spellID == current-cast), so vanilla
-- SPELLCAST_START never fires for them. nampower's SPELL_START_SELF
-- (server-driven) is the only signal that shows them.
cb:RegisterEvent("SPELL_START_SELF")
if unitstr == "player" then
cb:RegisterEvent("SPELL_GO_SELF")
end
if unitstr ~= "player" and unitstr ~= "" then
cb:RegisterEvent("SPELL_START_OTHER")
cb:RegisterEvent("SPELL_FAILED_OTHER")
if unitstr == "target" then
cb:RegisterEvent("PLAYER_TARGET_CHANGED")
elseif unitstr == "focus" then
cb:RegisterEvent("PLAYER_FOCUS_CHANGED")
end
-- Cast lifecycle, entirely on ClassicAPI's UNIT_SPELLCAST_* events. They
-- fire per unit token: arg1=="player" for the player's own casts, and the
-- remote token(s) ("target", "focus", ...) for other units -- so one set of
-- events drives every bar with no Nampower dependency. The handler routes an
-- event to this bar when arg1 matches its unit, or -- since the player's own
-- casts only ever fire arg1=="player" -- when the bar's unit resolves to the
-- player (target=self). PLAYER_TARGET/FOCUS_CHANGED re-polls so a unit
-- already mid-cast when it becomes the target/focus still shows.
cb:RegisterEvent("UNIT_SPELLCAST_START")
cb:RegisterEvent("UNIT_SPELLCAST_STOP")
cb:RegisterEvent("UNIT_SPELLCAST_FAILED")
cb:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED")
cb:RegisterEvent("UNIT_SPELLCAST_DELAYED")
cb:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED")
cb:RegisterEvent("UNIT_SPELLCAST_CHANNEL_START")
cb:RegisterEvent("UNIT_SPELLCAST_CHANNEL_STOP")
cb:RegisterEvent("UNIT_SPELLCAST_CHANNEL_UPDATE")
if unitstr == "target" then
cb:RegisterEvent("PLAYER_TARGET_CHANGED")
elseif unitstr == "focus" then
cb:RegisterEvent("PLAYER_FOCUS_CHANGED")
end
cb:SetScript("OnEvent", function()
@@ -333,83 +345,33 @@ pfUI:RegisterModule("castbar", function ()
return
end
if event == "SPELL_START_OTHER" then
-- arg3=casterGuid. Defer one frame so ClassicAPI's UnitChannelInfo
-- can see the engine's +0x228 broadcast for remote-unit channels
-- (the cohook+packet handler runs in the same frame; the broadcast
-- propagates after).
if arg3 == UnitGUID(unit) then
local target = this
RunNextFrame(function() RefreshBar(target) end)
end
-- UNIT_SPELLCAST_* fire per unit token (arg1). Handle an event when it's
-- for this bar's unit, or -- since the player's own casts only ever fire
-- arg1=="player" -- when this bar's unit currently resolves to the player
-- (target=self / focus=self).
-- Args: arg1=unit, arg2=castGUID, arg3=spellID, arg4=name, arg5=rank.
if arg1 ~= unit and not (arg1 == "player" and UnitIsUnit(unit, "player")) then
return
end
if event == "SPELL_FAILED_OTHER" then
if arg1 == UnitGUID(unit) then ClearBar(this) end
return
end
-- Vanilla SPELLCAST_* + SPELL_DELAYED_SELF fire only for the local
-- player. Non-player bars handle them only when their unit currently
-- resolves to the player (target=self / focus=self).
if not UnitIsUnit(unit, 'player') then return end
if event == "SPELLCAST_START" or event == "SPELLCAST_CHANNEL_START" then
if event == "UNIT_SPELLCAST_START" or event == "UNIT_SPELLCAST_CHANNEL_START" then
-- START also fires per craft in a same-spell chain, so an active merge
-- just resyncs the current craft's spark/label instead of restamping.
if this.tradeskillTotal then
-- Mid-chain craft N+1 of N. Keep the merged bar; resync the spark
-- to the new craft's start and refresh the "(N)" count label.
StartTradeskillCraft(this)
else
RefreshBar(this)
RefreshBar(this, arg5)
if this.isTradeskill and (this.pendingTradeskillCount or 0) > 1
and C.castbar.player.mergetradeskill == "1" then
EnterTradeskillMerge(this, this.startTime, this.endTime, this.pendingTradeskillCount)
end
this.pendingTradeskillCount = nil
end
elseif event == "SPELL_START_SELF" then
-- Catches chained same-spell recasts (no SPELLCAST_START fires) —
-- including tradeskill chaining where craft 2..N reuse one spell.
-- Defer one frame so ClassicAPI's SMSG_SPELL_START co-hook has
-- stamped g_cast before we poll, regardless of co-hook order.
if this.tradeskillTotal then
StartTradeskillCraft(this)
else
local target = this
RunNextFrame(function()
-- Re-check: SPELL_START_SELF (nampower co-hook) fires before vanilla
-- SPELLCAST_START on the same packet, so SPELLCAST_START may have
-- entered merge in this same frame. Don't restamp over it.
if target.tradeskillTotal then
StartTradeskillCraft(target)
return
end
RefreshBar(target)
if target.unitstr == "player" and target.isTradeskill
and (target.pendingTradeskillCount or 0) > 1
and C.castbar.player.mergetradeskill == "1" then
EnterTradeskillMerge(target, target.startTime, target.endTime, target.pendingTradeskillCount)
end
target.pendingTradeskillCount = nil
end)
end
elseif event == "SPELLCAST_CHANNEL_STOP" then
-- A channel's stop can arrive after a following cast already claimed
-- the bar (channel->cast transition); only clear if a channel is
-- actually being shown, so it doesn't wipe an active cast bar.
if this.isChannel then ClearBar(this) end
elseif event == "SPELLCAST_STOP" then
-- During a tradeskill chain, SPELL_GO_SELF already counted this craft
-- and either cleared the bar (chain done) or kept it running. Only a
-- non-merge cast clears here.
if not this.tradeskillTotal then ClearBar(this) end
elseif event == "SPELLCAST_FAILED" or event == "SPELLCAST_INTERRUPTED" then
ClearBar(this)
elseif event == "SPELL_GO_SELF" then
-- arg2 = spellId. In a tradeskill merge, count each successful craft
-- and clear when the chain is done.
if this.tradeskillTotal and arg2 == this.tradeskillSpellID then
elseif event == "UNIT_SPELLCAST_SUCCEEDED" then
-- Per-craft completion during a tradeskill merge (arg3 = spellID):
-- count and clear when the chain is done. A no-op for normal casts,
-- which are cleared by UNIT_SPELLCAST_STOP.
if this.tradeskillTotal and arg3 == this.tradeskillSpellID then
this.tradeskillCompleted = (this.tradeskillCompleted or 0) + 1
if this.tradeskillCompleted >= this.tradeskillTotal then
ClearBar(this)
@@ -417,33 +379,46 @@ pfUI:RegisterModule("castbar", function ()
UpdateTradeskillLabel(this)
end
end
elseif event == "SPELL_DELAYED_SELF" then
-- Cast pushback. nampower's event carries the delay (arg2); apply it
-- locally rather than re-polling, so the bar doesn't depend on
-- ClassicAPI's SMSG_SPELL_DELAYED co-hook having bumped g_cast before
-- this fires (co-hook order vs nampower is not guaranteed).
if not this.endTime or not arg2 then return end
local delayMs = tonumber(arg2) or 0
if delayMs > 0 then
this.delay = (this.delay or 0) + delayMs / 1000
this.endTime = this.endTime + delayMs
local newDuration = (this.endTime - this.startTime) / 1000
this.bar:SetMinMaxValues(0, newDuration)
this.lastMax = newDuration
end
elseif event == "SPELLCAST_CHANNEL_UPDATE" then
-- Channel pushback. ClassicAPI doesn't track channel delay in
-- g_channel, so we adjust endTime + delay locally and resize the
-- bar so OnUpdate animates against the new total.
if not this.endTime or not arg1 then return end
local newEndMs = GetTime() * 1000 + arg1
local diff = this.endTime - newEndMs
if diff > 50 then
this.delay = (this.delay or 0) + diff / 1000
this.endTime = newEndMs
local newDuration = (this.endTime - this.startTime) / 1000
this.bar:SetMinMaxValues(0, newDuration)
this.lastMax = newDuration
elseif event == "UNIT_SPELLCAST_CHANNEL_STOP" then
-- A channel's stop can arrive after a following cast already claimed
-- the bar (channel->cast transition); only clear if a channel is
-- actually being shown, so it doesn't wipe an active cast bar.
if this.isChannel then ClearBar(this) end
elseif event == "UNIT_SPELLCAST_STOP" then
-- STOP fires between crafts in a merge too (each craft is a new cast);
-- UNIT_SPELLCAST_SUCCEEDED owns the count, so only a non-merge cast
-- clears here.
if not this.tradeskillTotal then ClearBar(this) end
elseif event == "UNIT_SPELLCAST_FAILED" or event == "UNIT_SPELLCAST_INTERRUPTED" then
ClearBar(this, true)
elseif event == "UNIT_SPELLCAST_DELAYED" or event == "UNIT_SPELLCAST_CHANNEL_UPDATE" then
-- Pushback: a cast delayed later or a channel shortened. Following
-- Quartz, re-poll just the times and accumulate the shift into a running
-- this.delay for the +/- indicator, rather than a full restamp (which
-- would reset that total and re-render icon/text). ClassicAPI moves
-- endMs (SpellDelayed_h bumps g_cast.endMs; the MSG_CHANNEL_UPDATE
-- co-hook rewrites g_channel.endMs) while startMs stays put, so we diff
-- endMs. delay is a positive magnitude; OnUpdate signs it "+" for casts
-- and "-" for channels. Skipped during a tradeskill merge.
if this.endTime and not this.tradeskillTotal then
local query = this.unitstr ~= "" and this.unitstr or this.unitname
local startMs, endMs
if this.isChannel then
local _, _, _, s, e = C_Spell.UnitChannelInfo(query)
startMs, endMs = s, e
else
local _, _, _, s, e = C_Spell.UnitCastingInfo(query)
startMs, endMs = s, e
end
if startMs and endMs then
local shift = this.isChannel and (this.endTime - endMs) or (endMs - this.endTime)
this.delay = (this.delay or 0) + shift / 1000
this.startTime = startMs
this.endTime = endMs
local newDuration = (endMs - startMs) / 1000
this.bar:SetMinMaxValues(0, newDuration)
this.lastMax = newDuration
end
end
end
end)
@@ -489,10 +464,10 @@ pfUI:RegisterModule("castbar", function ()
UpdateMovable(pfUI.castbar.player)
-- Tradeskill merge: hook DoTradeSkill so the player castbar knows the
-- requested count before the first SPELLCAST_START fires. Always-on hook
-- (the config knob is read at event time so toggling takes effect on the
-- next craft without a /reload). DoTradeSkill is synchronous; the server
-- roundtrip to SPELLCAST_START gives us plenty of time after this hook.
-- requested count before the first UNIT_SPELLCAST_START fires. Always-on
-- hook (the config knob is read at event time so toggling takes effect on
-- the next craft without a /reload). DoTradeSkill is synchronous; the
-- server roundtrip to UNIT_SPELLCAST_START gives us plenty of time.
hooksecurefunc("DoTradeSkill", function(index, num)
if pfUI.castbar.player then
pfUI.castbar.player.pendingTradeskillCount = tonumber(num) or 1
+72 -15
View File
@@ -82,10 +82,10 @@ pfUI:RegisterModule("chat", function ()
end
if C.chat.global.custombg == "1" then
local r, g, b, a = strsplit(",", C.chat.global.background)
local r, g, b, a = GetStringColor(C.chat.global.background)
pfUI.chat.left.backdrop:SetBackdropColor(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
local r, g, b, a = strsplit(",", C.chat.global.border)
local r, g, b, a = GetStringColor(C.chat.global.border)
pfUI.chat.left.backdrop:SetBackdropBorderColor(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
end
@@ -257,10 +257,10 @@ pfUI:RegisterModule("chat", function ()
end
if C.chat.global.custombg == "1" then
local r, g, b, a = strsplit(",", C.chat.global.background)
local r, g, b, a = GetStringColor(C.chat.global.background)
pfUI.chat.right.backdrop:SetBackdropColor(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
local r, g, b, a = strsplit(",", C.chat.global.border)
local r, g, b, a = GetStringColor(C.chat.global.border)
pfUI.chat.right.backdrop:SetBackdropBorderColor(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
end
@@ -296,6 +296,54 @@ pfUI:RegisterModule("chat", function ()
end
end
-- [ Chat Background Alpha ]
-- Blizzard's per-window transparency slider drives FCF_SetWindowAlpha, which only
-- touches the ChatFrame*Background textures that pfUI hides on docked frames. Mirror
-- the window's stored alpha onto the visible pfUI backdrop so the native slider
-- controls it directly. The slider is authoritative for the panel's alpha.
local function ApplyChatBackgroundAlpha(panel, frame)
if not (panel and panel.backdrop and frame) then return end
local _, _, _, _, _, alpha = GetChatWindowInfo(frame:GetID())
alpha = tonumber(alpha)
if not alpha then return end
local r, g, b = panel.backdrop:GetBackdropColor()
panel.backdrop:SetBackdropColor(r, g, b, alpha)
end
function pfUI.chat:RefreshBackgroundAlpha()
-- left panel follows the currently selected docked tab
local selected = SELECTED_CHAT_FRAME
if not (selected and selected:GetParent() == pfUI.chat.left) then
selected = ChatFrame1
end
ApplyChatBackgroundAlpha(pfUI.chat.left, selected)
if C.chat.right.enable == "1" then
ApplyChatBackgroundAlpha(pfUI.chat.right, ChatFrame3)
end
end
function pfUI.chat:MigrateBackgroundAlpha()
-- Runs once per character. The previous SetupPositions default stored a hard 0 window
-- alpha, which now renders the pfUI backdrop fully transparent. Restore the historical
-- 0.8 look for any pfUI-managed window still sitting at 0. Only existing installs that
-- already ran chat setup carry that legacy 0; fresh installs get 0.8 from SetupPositions.
if pfUI_init.chatbgalpha then return end
if not pfUI_init["chat_position"] then return end
pfUI_init.chatbgalpha = true
local frames = { ChatFrame1, ChatFrame2 }
if C.chat.right.enable == "1" then table.insert(frames, ChatFrame3) end
for _, frame in ipairs(frames) do
local _, _, _, _, _, alpha = GetChatWindowInfo(frame:GetID())
alpha = tonumber(alpha)
if alpha and alpha <= 0 then
FCF_SetWindowAlpha(frame, 0.8)
end
end
end
function pfUI.chat:RefreshChat()
local panelheight = C.global.font_size*1.5 + default_border*2 + 2
@@ -447,9 +495,13 @@ pfUI:RegisterModule("chat", function ()
for index, value in pairs(DOCKED_CHAT_FRAMES) do
FCF_UpdateButtonSide(value)
end
pfUI.chat:RefreshBackgroundAlpha()
end
hooksecurefunc("FCF_SaveDock", pfUI.chat.RefreshChat)
hooksecurefunc("FCF_SetWindowAlpha", function() pfUI.chat:RefreshBackgroundAlpha() end)
hooksecurefunc("FCF_SelectDockFrame", function() pfUI.chat:RefreshBackgroundAlpha() end)
if C.chat.global.tabmouse == "1" then
pfUI.chat.mouseovertab = CreateFrame("Frame")
@@ -500,7 +552,7 @@ pfUI:RegisterModule("chat", function ()
FCF_SetLocked(ChatFrame1, 1)
FCF_SetWindowName(ChatFrame1, GENERAL)
FCF_SetWindowColor(ChatFrame1, 0, 0, 0)
FCF_SetWindowAlpha(ChatFrame1, 0)
FCF_SetWindowAlpha(ChatFrame1, 0.8)
FCF_SetChatWindowFontSize(ChatFrame1, 12)
ChatFrame1:SetUserPlaced(1)
@@ -508,7 +560,7 @@ pfUI:RegisterModule("chat", function ()
FCF_SetLocked(ChatFrame2, 1)
FCF_SetWindowName(ChatFrame2, COMBAT_LOG)
FCF_SetWindowColor(ChatFrame2, 0, 0, 0)
FCF_SetWindowAlpha(ChatFrame2, 0)
FCF_SetWindowAlpha(ChatFrame2, 0.8)
FCF_SetChatWindowFontSize(ChatFrame2, 12)
ChatFrame2:SetUserPlaced(1)
@@ -518,7 +570,7 @@ pfUI:RegisterModule("chat", function ()
FCF_SetLocked(ChatFrame3, 1)
FCF_SetWindowName(ChatFrame3, T["Loot & Spam"])
FCF_SetWindowColor(ChatFrame3, 0, 0, 0)
FCF_SetWindowAlpha(ChatFrame3, 0)
FCF_SetWindowAlpha(ChatFrame3, 0.8)
FCF_SetChatWindowFontSize(ChatFrame3, 12)
FCF_UnDockFrame(ChatFrame3)
FCF_SetTabPosition(ChatFrame3, 0)
@@ -566,6 +618,9 @@ pfUI:RegisterModule("chat", function ()
end
pfUI.chat:SetScript("OnEvent", function()
-- restore legacy chat windows stuck at 0 alpha before anything reads it
pfUI.chat:MigrateBackgroundAlpha()
-- set the default chat
FCF_SelectDockFrame(SELECTED_CHAT_FRAME)
@@ -675,7 +730,7 @@ pfUI:RegisterModule("chat", function ()
end
-- read and parse whisper color settings
local cr, cg, cb, ca = strsplit(",", C.chat.global.whisper)
local cr, cg, cb, ca = GetStringColor(C.chat.global.whisper)
cr, cg, cb = tonumber(cr), tonumber(cg), tonumber(cb)
local wcol = rgbhex(cr, cg, cb)
@@ -706,10 +761,10 @@ pfUI:RegisterModule("chat", function ()
_G.CHAT_WHISPER_INFORM_GET = '[W]' .. default
end
local r,g,b,a = strsplit(",", C.chat.text.timecolor)
local r,g,b,a = GetStringColor(C.chat.text.timecolor)
local timecolorhex = rgbhex(r,g,b,a)
local r,g,b = strsplit(",", C.chat.text.unknowncolor)
local r,g,b = GetStringColor(C.chat.text.unknowncolor)
local unknowncolorhex = rgbhex(r,g,b)
-- Suppress FriendsFrame's WHO_LIST_UPDATE handling for our DLL-issued
@@ -810,23 +865,25 @@ pfUI:RegisterModule("chat", function ()
end
end
-- detect the whisper prefix BEFORE prepending a timestamp; the timestamp
-- pushes the whisper colour code off position 1 and the find below fails
local isWhisper = C.chat.global.whispermod == "1" and string.find(text, wcol, 1) == 1
-- show timestamp in chat
if C.chat.text.time == "1" then
text = timecolorhex .. tleft .. date(C.chat.text.timeformat) .. tright .. "|r " .. text
end
-- save chat history
if C.chat.global.whispermod == "1" and string.find(text, wcol, 1) == 1 then
if isWhisper then
SaveChatHistory(frame:GetID(), string.gsub(text, wcol, ""), cr, cg, cb)
else
SaveChatHistory(frame:GetID(), text, a1, a2, a3)
end
if C.chat.global.whispermod == "1" then
if isWhisper then
-- patch incoming whisper string to match the colors
if string.find(text, wcol, 1) == 1 then
text = string.gsub(text, "|r", "|r" .. wcol)
end
text = string.gsub(text, "|r", "|r" .. wcol)
end
frame:HookAddMessage(text, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17)
+6 -6
View File
@@ -1,15 +1,15 @@
pfUI:RegisterModule("cooldown", function ()
-- cache values
local lowcolor = {strsplit(",", C.appearance.cd.lowcolor)}
local normalcolor = {strsplit(",", C.appearance.cd.normalcolor)}
local minutecolor = {strsplit(",", C.appearance.cd.minutecolor)}
local hourcolor = {strsplit(",", C.appearance.cd.hourcolor)}
local daycolor = {strsplit(",", C.appearance.cd.daycolor)}
-- local lowcolor = {strsplit(",", C.appearance.cd.lowcolor)}
-- local normalcolor = {strsplit(",", C.appearance.cd.normalcolor)}
-- local minutecolor = {strsplit(",", C.appearance.cd.minutecolor)}
-- local hourcolor = {strsplit(",", C.appearance.cd.hourcolor)}
-- local daycolor = {strsplit(",", C.appearance.cd.daycolor)}
local parent, parent_name
local function pfCooldownOnUpdate()
parent = this:GetParent()
if not parent then this:Hide() end
if not parent then this:Hide() return end
parent_name = parent:GetName()
-- avoid to set cooldowns on invalid frames
+94 -270
View File
@@ -1,11 +1,10 @@
-- Equipment Manager module
-- Backport of the 4.3.4 GearManagerDialog UI on top of ClassicAPI's
-- C_EquipmentSet.* API. Adds a 6th tab to CharacterFrame.
-- Backport of the 4.3.4 GearManagerDialog UI on top of ClassicAPI's C_EquipmentSet.* API.
pfUI:RegisterModule("equipmentmanager", function()
if not C_EquipmentSet or not C_EquipmentSet.CanUseEquipmentSets() then return end
pfUI.equipmentmanager = pfUI.equipmentmanager or {}
pfUI.equipmentmanager = {}
local SET_ROW_HEIGHT = 36
@@ -14,6 +13,7 @@ pfUI:RegisterModule("equipmentmanager", function()
local pendingAction = nil -- "new" | "save" | "rename" — what the popups apply to
local slotOverlays = {} -- [invSlotID] = ignored-overlay texture on the character slot
local popoutButtons = {} -- popout arrow buttons; toggled with the EM frame
local UpdatePopouts -- forward decl; assigned once popouts + flyout exist
-- Pending ignored-slot toggles per set: a slotID here means the
-- effective state is flipped from what's persisted. Committed on
@@ -55,7 +55,7 @@ pfUI:RegisterModule("equipmentmanager", function()
local function EquipSet(setID)
if not setID then return end
if C_EquipmentSet.EquipmentSetContainsLockedItems(setID) then
UIErrorsFrame:AddMessage(ERR_CLIENT_LOCKED_OUT or "Locked items in set", 1, .1, .1, 1)
UIErrorsFrame:AddMessage(ERR_CLIENT_LOCKED_OUT, 1, .1, .1, 1)
return
end
ClearCursor()
@@ -68,8 +68,7 @@ pfUI:RegisterModule("equipmentmanager", function()
-- ============================================================
local frame = CreateFrame("Frame", "pfEquipmentManagerFrame", CharacterFrame)
frame:SetWidth(220)
frame:SetHeight(350)
frame:SetSize(220, 350)
frame:SetFrameStrata("HIGH")
frame:SetScript("OnShow", function()
this:ClearAllPoints()
@@ -78,7 +77,7 @@ pfUI:RegisterModule("equipmentmanager", function()
else
frame:SetPoint("TOPLEFT", CharacterFrame, "TOPRIGHT", 0, 0)
end
for _, b in ipairs(popoutButtons) do b:Show() end
UpdatePopouts()
end)
CreateBackdrop(frame, nil, nil, .9)
CreateBackdropShadow(frame)
@@ -86,7 +85,7 @@ pfUI:RegisterModule("equipmentmanager", function()
frame.title = frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
frame.title:SetPoint("TOP", frame, "TOP", 0, -10)
frame.title:SetText(T["Equipment Manager"] or "Equipment Manager")
frame.title:SetText(EQUIPMENT_MANAGER)
local closeBtn = CreateFrame("Button", nil, frame, "UIPanelCloseButton")
closeBtn:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -4, -4)
@@ -98,15 +97,14 @@ pfUI:RegisterModule("equipmentmanager", function()
-- ============================================================
local toggleBtn = CreateFrame("Button", "pfEqMgrToggleButton", PaperDollFrame)
toggleBtn:SetWidth(28)
toggleBtn:SetHeight(28)
toggleBtn:SetSize(28, 28)
toggleBtn:SetPoint("BOTTOM", CharacterHandsSlot, "TOP", 0, 4)
toggleBtn:SetNormalTexture(pfUI.path.."\\img\\UI-GearManager-Button")
toggleBtn:SetPushedTexture(pfUI.path.."\\img\\UI-GearManager-Button-Pushed")
toggleBtn:SetHighlightTexture("Interface\\Buttons\\ButtonHilight-Square", "ADD")
toggleBtn:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetText(T["Equipment Manager"] or "Equipment Manager")
GameTooltip:SetText(PAPERDOLL_EQUIPMENTMANAGER)
GameTooltip:Show()
end)
toggleBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)
@@ -126,13 +124,13 @@ pfUI:RegisterModule("equipmentmanager", function()
-- ============================================================
local btnEquip = CreateFrame("Button", "pfEqMgrEquip", frame, "UIPanelButtonTemplate")
btnEquip:SetWidth(86); btnEquip:SetHeight(22); btnEquip:SetText(T["Equip"] or "Equip")
btnEquip:SetSize(86, 22); btnEquip:SetText(EQUIPSET_EQUIP)
btnEquip:SetPoint("TOPLEFT", frame, "TOPLEFT", 16, -30)
SkinButton(btnEquip)
btnEquip:SetScript("OnClick", function() EquipSet(selectedSetID) end)
local btnSave = CreateFrame("Button", "pfEqMgrSave", frame, "UIPanelButtonTemplate")
btnSave:SetWidth(86); btnSave:SetHeight(22); btnSave:SetText(T["Save"] or "Save")
btnSave:SetSize(86, 22); btnSave:SetText(SAVE)
btnSave:SetPoint("LEFT", btnEquip, "RIGHT", 6, 0)
SkinButton(btnSave)
btnSave:SetScript("OnClick", function()
@@ -141,7 +139,7 @@ pfUI:RegisterModule("equipmentmanager", function()
if not name then return end
local targetID = selectedSetID
StaticPopupDialogs["PFUI_EQMGR_SAVE_CONFIRM"] = {
text = string.format(T["Would you like to save the equipment set '%s'?"] or "Would you like to save the equipment set '%s'?", name),
text = string.format(CONFIRM_SAVE_EQUIPMENT_SET, name),
button1 = YES, button2 = NO,
OnAccept = function()
C_EquipmentSet.ClearIgnoredSlotsForSave()
@@ -166,7 +164,7 @@ pfUI:RegisterModule("equipmentmanager", function()
local rowMenu = CreateFrame("Frame", "pfEqMgrRowMenu", UIParent)
rowMenu:SetFrameStrata("DIALOG")
rowMenu:SetWidth(140); rowMenu:SetHeight(50)
rowMenu:SetSize(140, 50);
rowMenu:Hide()
CreateBackdrop(rowMenu, nil, nil, .95)
CreateBackdropShadow(rowMenu)
@@ -185,9 +183,9 @@ pfUI:RegisterModule("equipmentmanager", function()
tinsert(UISpecialFrames, "pfEqMgrRowMenu") -- Escape closes it
rowMenu.changeBtn = CreateFrame("Button", nil, rowMenu, "UIPanelButtonTemplate")
rowMenu.changeBtn:SetWidth(130); rowMenu.changeBtn:SetHeight(20)
rowMenu.changeBtn:SetSize(130, 20);
rowMenu.changeBtn:SetPoint("TOPLEFT", rowMenu, "TOPLEFT", 5, -3)
rowMenu.changeBtn:SetText(T["Change Name/Icon"] or "Change Name/Icon")
rowMenu.changeBtn:SetText(EQUIPMENT_SET_EDIT)
SkinButton(rowMenu.changeBtn)
rowMenu.changeBtn:SetScript("OnClick", function()
rowMenu:Hide()
@@ -198,9 +196,9 @@ pfUI:RegisterModule("equipmentmanager", function()
end)
rowMenu.deleteBtn = CreateFrame("Button", nil, rowMenu, "UIPanelButtonTemplate")
rowMenu.deleteBtn:SetWidth(130); rowMenu.deleteBtn:SetHeight(20)
rowMenu.deleteBtn:SetSize(130, 20);
rowMenu.deleteBtn:SetPoint("TOP", rowMenu.changeBtn, "BOTTOM", 0, -2)
rowMenu.deleteBtn:SetText(T["Delete"] or "Delete")
rowMenu.deleteBtn:SetText(DELETE)
SkinButton(rowMenu.deleteBtn)
rowMenu.deleteBtn:SetScript("OnClick", function()
rowMenu:Hide()
@@ -208,7 +206,7 @@ pfUI:RegisterModule("equipmentmanager", function()
local targetID = rowMenu.targetSetID
local name = C_EquipmentSet.GetEquipmentSetInfo(targetID)
StaticPopupDialogs["PFUI_EQMGR_DELETE"] = {
text = string.format(T["Delete equipment set '%s'?"] or "Delete equipment set '%s'?", name or "?"),
text = string.format(CONFIRM_DELETE_EQUIPMENT_SET, name or "?"),
button1 = YES, button2 = NO,
OnAccept = function()
pendingIgnoredToggles[targetID] = nil
@@ -229,9 +227,8 @@ pfUI:RegisterModule("equipmentmanager", function()
local LIST_ROW_STRIDE = SET_ROW_HEIGHT + 2
local listFrame = CreateFrame("Frame", nil, frame)
listFrame:SetPoint("TOPLEFT", frame, "TOPLEFT", 16, -58)
listFrame:SetWidth(180)
-- Height fits N rows + (N-1) inter-row gaps + 3px top/bottom padding.
listFrame:SetHeight(LIST_VISIBLE_ROWS * LIST_ROW_STRIDE + 4)
listFrame:SetSize(180, LIST_VISIBLE_ROWS * LIST_ROW_STRIDE + 4)
CreateBackdrop(listFrame, nil, nil, .75)
-- Mouse-wheel scroll: list of [sets..., newSetRow] is virtualized
@@ -247,15 +244,13 @@ pfUI:RegisterModule("equipmentmanager", function()
local setRows = {}
local function CreateSetRow()
local row = CreateFrame("Button", nil, listFrame)
row:SetWidth(170)
row:SetHeight(SET_ROW_HEIGHT)
row:SetSize(170, SET_ROW_HEIGHT)
-- Position is set per-Refresh based on the row's visible slot;
-- start anchored to avoid uninitialized geometry before first Refresh.
row:SetPoint("TOPLEFT", listFrame, "TOPLEFT", 5, -3)
row.icon = row:CreateTexture(nil, "ARTWORK")
row.icon:SetWidth(30)
row.icon:SetHeight(30)
row.icon:SetSize(30, 30)
row.icon:SetPoint("LEFT", row, "LEFT", 2, 0)
row.icon:SetTexCoord(.08, .92, .08, .92)
@@ -270,7 +265,7 @@ pfUI:RegisterModule("equipmentmanager", function()
row.highlight:Hide()
row.gear = CreateFrame("Button", nil, row)
row.gear:SetWidth(16); row.gear:SetHeight(16)
row.gear:SetSize(16, 16);
row.gear:SetPoint("RIGHT", row, "RIGHT", -4, 0)
row.gear.tex = row.gear:CreateTexture(nil, "ARTWORK")
row.gear.tex:SetAllPoints(row.gear)
@@ -306,27 +301,27 @@ pfUI:RegisterModule("equipmentmanager", function()
pfUI.equipmentmanager.Refresh()
end)
-- Hover handling: OnLeave fires when the cursor moves onto a child
-- (the gear button becomes the topmost mouse target). Use an
-- OnUpdate poll so the gear stays shown while the cursor is over
-- the row OR the gear itself.
-- Show the gear and tooltip while the cursor is over the row or its
-- gear child. The gear sits inside the row's rectangle, so a single
-- MouseIsOver(row) check covers both. OnLeave on the row and the gear
-- catches every exit path, so no OnUpdate poll is needed.
local function HideRowHover()
if MouseIsOver(row) then return end
GameTooltip:Hide()
row.gear:Hide()
end
row:SetScript("OnEnter", function()
if not this.setID then return end
local name = C_EquipmentSet.GetEquipmentSetInfo(this.setID)
if not row.setID then return end
local name = C_EquipmentSet.GetEquipmentSetInfo(row.setID)
if name then
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetOwner(row, "ANCHOR_RIGHT")
GameTooltip:SetEquipmentSet(name)
GameTooltip:Show()
end
row.gear:Show()
this:SetScript("OnUpdate", function()
if not MouseIsOver(this) and not MouseIsOver(row.gear) then
this:SetScript("OnUpdate", nil)
GameTooltip:Hide()
row.gear:Hide()
end
end)
end)
row:SetScript("OnLeave", HideRowHover)
row.gear:SetScript("OnLeave", HideRowHover)
return row
end
@@ -338,10 +333,10 @@ pfUI:RegisterModule("equipmentmanager", function()
-- specific set index (row[i] always shows ids[i]).
local newSetRow = CreateFrame("Button", nil, listFrame)
newSetRow:SetWidth(170); newSetRow:SetHeight(SET_ROW_HEIGHT)
newSetRow:SetSize(170, SET_ROW_HEIGHT);
newSetRow.icon = newSetRow:CreateTexture(nil, "ARTWORK")
newSetRow.icon:SetWidth(24); newSetRow.icon:SetHeight(24)
newSetRow.icon:SetSize(24, 24);
newSetRow.icon:SetPoint("LEFT", newSetRow, "LEFT", 5, 0)
newSetRow.icon:SetTexture(pfUI.path.."\\img\\Character-Plus")
@@ -349,7 +344,7 @@ pfUI:RegisterModule("equipmentmanager", function()
newSetRow.text:SetPoint("LEFT", newSetRow.icon, "RIGHT", 8, 0)
newSetRow.text:SetPoint("RIGHT", newSetRow, "RIGHT", -4, 0)
newSetRow.text:SetJustifyH("LEFT")
newSetRow.text:SetText(T["New Set"] or "New Set")
newSetRow.text:SetText(PAPERDOLL_NEWEQUIPMENTSET)
newSetRow.text:SetTextColor(0.2, 1, 0.2)
newSetRow.highlight = newSetRow:CreateTexture(nil, "BACKGROUND")
@@ -362,8 +357,7 @@ pfUI:RegisterModule("equipmentmanager", function()
local function MakeButton(name, label, parent, anchor, ax, ay, width)
local b = CreateFrame("Button", name, parent, "UIPanelButtonTemplate")
b:SetWidth(width or 70)
b:SetHeight(22)
b:SetSize(width or 70, 22)
b:SetText(label)
b:SetPoint("TOPLEFT", anchor, "BOTTOMLEFT", ax, ay)
SkinButton(b)
@@ -376,8 +370,7 @@ pfUI:RegisterModule("equipmentmanager", function()
local namePopup = CreateFrame("Frame", "pfEqMgrNamePopup", UIParent)
namePopup:SetFrameStrata("DIALOG")
namePopup:SetWidth(472)
namePopup:SetHeight(498)
namePopup:SetSize(472, 484)
namePopup:SetPoint("CENTER", UIParent, "CENTER")
namePopup:Hide()
CreateBackdrop(namePopup, nil, nil, .9)
@@ -388,200 +381,28 @@ pfUI:RegisterModule("equipmentmanager", function()
namePopup:SetScript("OnDragStart", function() this:StartMoving() end)
namePopup:SetScript("OnDragStop", function() this:StopMovingOrSizing() end)
namePopup.title = namePopup:CreateFontString(nil, "OVERLAY", "GameFontNormal")
namePopup.title:SetPoint("TOP", namePopup, "TOP", 0, -10)
namePopup.title:SetText(T["Save Set"] or "Save Set")
-- Equipment seed leads the grid with gear-relevant item icons. The widget
-- owns the name field, preview, grid, filter, and search; this module keeps
-- the OK/Cancel buttons and the create/save/rename flow below.
local iconPicker = CreateIconPicker("pfEqMgrIcon", namePopup,
IconDataProviderExtraType.Equipment, GEARSETS_POPUP_TEXT)
namePopup.nameLabel = namePopup:CreateFontString(nil, "OVERLAY", "GameFontNormal")
namePopup.nameLabel:SetPoint("TOPLEFT", namePopup, "TOPLEFT", 14, -34)
namePopup.nameLabel:SetText(T["Enter Set Name (Max 16 Characters):"] or "Enter Set Name (Max 16 Characters):")
local btnPopupOK = MakeButton("pfEqMgrPopupOK", OKAY, namePopup, namePopup, 14, -340, 80)
namePopup.editbox = CreateFrame("EditBox", "pfEqMgrNameEdit", namePopup, "InputBoxTemplate")
namePopup.editbox:SetWidth(280)
namePopup.editbox:SetHeight(20)
namePopup.editbox:SetPoint("TOPLEFT", namePopup, "TOPLEFT", 14, -52)
namePopup.editbox:SetAutoFocus(false)
namePopup.editbox:SetMaxLetters(16)
CreateBackdrop(namePopup.editbox)
namePopup.selectedLabel = namePopup:CreateFontString(nil, "OVERLAY", "GameFontNormal")
namePopup.selectedLabel:SetPoint("TOPRIGHT", namePopup, "TOPRIGHT", -14, -28)
namePopup.selectedLabel:SetText(T["Currently Selected"] or "Currently Selected")
namePopup.selectedLabel:SetTextColor(1, 0.82, 0)
namePopup.selectedPreview = CreateFrame("Frame", nil, namePopup)
namePopup.selectedPreview:SetWidth(42)
namePopup.selectedPreview:SetHeight(42)
namePopup.selectedPreview:SetPoint("TOPRIGHT", namePopup, "TOPRIGHT", -14, -44)
CreateBackdrop(namePopup.selectedPreview)
namePopup.selectedPreview.tex = namePopup.selectedPreview:CreateTexture(nil, "ARTWORK")
namePopup.selectedPreview.tex:SetAllPoints(namePopup.selectedPreview)
namePopup.selectedPreview.tex:SetTexCoord(.08, .92, .08, .92)
namePopup.iconLabel = namePopup:CreateFontString(nil, "OVERLAY", "GameFontNormal")
namePopup.iconLabel:SetPoint("TOPLEFT", namePopup, "TOPLEFT", 14, -100)
namePopup.iconLabel:SetText(T["Choose an Icon:"] or "Choose an Icon:")
-- Icon picker: 10×8 grid of buttons + scroll
local ICON_GRID_COLS = 10
local ICON_GRID_ROWS = 8
local ICON_BTN_SIZE = 36
local ICON_BTN_PAD = 6
local iconScroll = CreateFrame("ScrollFrame", "pfEqMgrIconScroll", namePopup, "FauxScrollFrameTemplate")
iconScroll:SetPoint("TOPLEFT", namePopup, "TOPLEFT", 14, -130)
iconScroll:SetWidth(ICON_GRID_COLS * (ICON_BTN_SIZE + ICON_BTN_PAD) - ICON_BTN_PAD)
iconScroll:SetHeight(ICON_GRID_ROWS * (ICON_BTN_SIZE + ICON_BTN_PAD) - ICON_BTN_PAD)
-- IconDataProviderMixin owns the icon DB, dedup, and lazy load.
-- Init on first picker open; release on hide so the cache GCs.
local provider = nil
-- Selection is tracked by PATH (not index) so it survives filter
-- changes: a spell icon you picked still saves correctly even after
-- you switch the filter to "Items" and it's no longer in the visible list.
local QUESTION_MARK = "INTERFACE\\ICONS\\INV_MISC_QUESTIONMARK"
local selectedIconPath = QUESTION_MARK
local function EnsureProvider()
if not provider then
provider = CreateAndInitFromMixin(IconDataProviderMixin,
IconDataProviderExtraType.Equipment)
end
end
-- Anchor scrollbar to iconScroll's right edge so its position tracks
-- the icon grid rather than the popup. -16/+16 vertical insets are the
-- standard up/down arrow spacing for UIPanelScrollBarTemplate.
local scrollbar = _G["pfEqMgrIconScrollScrollBar"]
if scrollbar then
scrollbar:ClearAllPoints()
scrollbar:SetPoint("TOPLEFT", iconScroll, "TOPRIGHT", 8, -16)
scrollbar:SetPoint("BOTTOMLEFT", iconScroll, "BOTTOMRIGHT", 8, 16)
SkinScrollbar(scrollbar)
end
-- Filter dropdown: "All Icons" / "Spells" / "Items" (top right of icon area).
local filterDropdown = CreateFrame("Frame", "pfEqMgrIconFilter", namePopup, "UIDropDownMenuTemplate")
filterDropdown:SetPoint("TOPRIGHT", namePopup, "TOPRIGHT", 0, -94)
local currentFilter = "all"
local function ApplyFilter(value)
currentFilter = value
UIDropDownMenu_SetSelectedValue(filterDropdown, value)
if provider then
if value == "spells" then provider:SetIconTypes({ IconDataProviderIconType.Spell })
elseif value == "items" then provider:SetIconTypes({ IconDataProviderIconType.Item })
else provider:SetIconTypes(nil) end
-- Don't reset selection — selectedIconPath persists. If the
-- selected icon isn't in the new filter, no grid entry will be
-- highlighted but Save will still write the chosen icon.
pfUI.equipmentmanager.RefreshIconGrid()
end
end
UIDropDownMenu_Initialize(filterDropdown, function()
local info
info = {}; info.text = T["All Icons"] or "All Icons"; info.value = "all"
info.func = function() ApplyFilter("all") end
info.checked = currentFilter == "all"
UIDropDownMenu_AddButton(info)
info = {}; info.text = T["Spells"] or "Spells"; info.value = "spells"
info.func = function() ApplyFilter("spells") end
info.checked = currentFilter == "spells"
UIDropDownMenu_AddButton(info)
info = {}; info.text = T["Items"] or "Items"; info.value = "items"
info.func = function() ApplyFilter("items") end
info.checked = currentFilter == "items"
UIDropDownMenu_AddButton(info)
end)
UIDropDownMenu_SetWidth(120, filterDropdown)
UIDropDownMenu_SetSelectedValue(filterDropdown, "all")
SkinDropDown(filterDropdown)
local iconButtons = {}
for r = 1, ICON_GRID_ROWS do
for c = 1, ICON_GRID_COLS do
local i = (r - 1) * ICON_GRID_COLS + c
local btn = CreateFrame("Button", nil, namePopup)
btn:SetWidth(ICON_BTN_SIZE)
btn:SetHeight(ICON_BTN_SIZE)
btn:SetPoint("TOPLEFT", iconScroll, "TOPLEFT", (c-1) * (ICON_BTN_SIZE + ICON_BTN_PAD), -(r-1) * (ICON_BTN_SIZE + ICON_BTN_PAD))
CreateBackdrop(btn)
btn.texture = btn:CreateTexture(nil, "ARTWORK")
btn.texture:SetAllPoints(btn)
btn.texture:SetTexCoord(.08, .92, .08, .92)
btn.gridIndex = i
btn:SetScript("OnClick", function()
if this.iconIndex and provider then
local path = provider:GetIconByIndex(this.iconIndex)
if path then selectedIconPath = path end
pfUI.equipmentmanager.RefreshIconGrid()
end
end)
btn:SetScript("OnEnter", function()
if not this.iconIndex or not provider then return end
local path = provider:GetIconByIndex(this.iconIndex)
if type(path) == "string" then
local name = string.gsub(path, "^.-INTERFACE\\\\ICONS\\\\", "")
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetText(name)
GameTooltip:Show()
end
end)
btn:SetScript("OnLeave", GameTooltip_Hide)
iconButtons[i] = btn
end
end
function pfUI.equipmentmanager.RefreshIconGrid()
EnsureProvider()
local numIcons = provider:GetNumIcons()
local numRows = math.ceil(numIcons / ICON_GRID_COLS)
FauxScrollFrame_Update(iconScroll, numRows, ICON_GRID_ROWS, ICON_BTN_SIZE + ICON_BTN_PAD)
local offset = FauxScrollFrame_GetOffset(iconScroll)
for i = 1, ICON_GRID_ROWS * ICON_GRID_COLS do
local listIdx = i + offset * ICON_GRID_COLS
local btn = iconButtons[i]
if listIdx <= numIcons then
btn:Show()
btn.iconIndex = listIdx
local path = provider:GetIconByIndex(listIdx)
btn.texture:SetTexture(path)
if path == selectedIconPath then
btn.backdrop:SetBackdropBorderColor(1, 0.82, 0, 1)
else
btn.backdrop:SetBackdropBorderColor(pfUI.cache.er, pfUI.cache.eg, pfUI.cache.eb, pfUI.cache.ea)
end
else
btn:Hide()
btn.iconIndex = nil
end
end
-- Sync the "Currently Selected" preview from the path directly so it
-- still shows the chosen icon when filtered out of the grid.
namePopup.selectedPreview.tex:SetTexture(selectedIconPath)
end
iconScroll:SetScript("OnVerticalScroll", function()
FauxScrollFrame_OnVerticalScroll(ICON_BTN_SIZE + ICON_BTN_PAD, function() pfUI.equipmentmanager.RefreshIconGrid() end)
end)
namePopup:SetScript("OnHide", function()
if provider then provider:Release(); provider = nil end
end)
local btnPopupOK = MakeButton("pfEqMgrPopupOK", T["OK"] or "OK", namePopup, namePopup, 14, -340, 80)
btnPopupOK:ClearAllPoints()
btnPopupOK:SetPoint("BOTTOMLEFT", namePopup, "BOTTOMLEFT", 14, 12)
local btnPopupCancel = MakeButton("pfEqMgrPopupCancel", T["Cancel"] or "Cancel", namePopup, namePopup, 0, 0, 80)
local btnPopupCancel = MakeButton("pfEqMgrPopupCancel", CANCEL, namePopup, namePopup, 0, 0, 80)
btnPopupCancel:ClearAllPoints()
btnPopupCancel:SetPoint("BOTTOMRIGHT", namePopup, "BOTTOMRIGHT", -14, 12)
btnPopupCancel:SetScript("OnClick", function() namePopup:Hide() end)
-- Okay | Cancel, both anchored to the bottom-right corner.
btnPopupOK:ClearAllPoints()
btnPopupOK:SetPoint("BOTTOMRIGHT", btnPopupCancel, "BOTTOMLEFT", -6, 0)
btnPopupOK:SetScript("OnClick", function()
local name = namePopup.editbox:GetText()
if not name or name == "" then return end
-- Strip the prefix to match ClassicAPI's persisted short-form basenames.
local iconForSave = string.gsub(selectedIconPath, "INTERFACE\\ICONS\\", "")
local iconForSave = string.gsub(iconPicker.GetIcon(), "INTERFACE\\ICONS\\", "")
if pendingAction == "new" then
C_EquipmentSet.CreateEquipmentSet(name, iconForSave)
C_EquipmentSet.ClearIgnoredSlotsForSave()
@@ -610,31 +431,20 @@ pfUI:RegisterModule("equipmentmanager", function()
function OpenNamePopup(action, prefillName, prefillIcon)
pendingAction = action
namePopup.editbox:SetText(prefillName or "")
EnsureProvider()
if prefillIcon then
local short = string.gsub(prefillIcon, "INTERFACE\\ICONS\\", "")
selectedIconPath = "INTERFACE\\ICONS\\" .. strupper(short)
-- Stored icons come back either as a full path or a bare basename
-- (see the row rendering in Refresh). Only prepend the prefix when
-- there's no path separator, so a full path isn't double-prefixed.
iconPicker.SetIcon(string.find(prefillIcon, "\\") and prefillIcon
or ("INTERFACE\\ICONS\\" .. prefillIcon))
else
selectedIconPath = QUESTION_MARK
end
if action == "rename" then
namePopup.title:SetText(T["Rename Set"] or "Rename Set")
iconScroll:Hide()
for _, b in ipairs(iconButtons) do b:Hide() end
namePopup.iconLabel:Hide()
namePopup.selectedLabel:Hide()
namePopup.selectedPreview:Hide()
filterDropdown:Hide()
else
namePopup.title:SetText(action == "new" and (T["Name Set"] or "Name Set") or (T["Save Set"] or "Save Set"))
iconScroll:Show()
namePopup.iconLabel:Show()
namePopup.selectedLabel:Show()
namePopup.selectedPreview:Show()
filterDropdown:Show()
iconPicker.SetIcon(nil)
end
namePopup.search:SetText("")
-- Rename only changes the name, so hide the whole icon-picking area.
iconPicker.SetIconAreaShown(action ~= "rename")
namePopup:Show()
if action ~= "rename" then pfUI.equipmentmanager.RefreshIconGrid() end
if action ~= "rename" then iconPicker.Refresh() end
namePopup.editbox:SetFocus()
end
@@ -643,9 +453,13 @@ pfUI:RegisterModule("equipmentmanager", function()
-- ============================================================
local flyout = CreateFrame("Frame", "pfEqMgrFlyout", UIParent)
-- The name/icon popup is sidecar-only, so it closes with the sidecar.
-- Popout arrows and the flyout are reconciled by UpdatePopouts: by
-- default they follow the sidecar, but with the "Always Show Equipment
-- Slot Flyouts" option they stay while the paperdoll is open.
frame:SetScript("OnHide", function()
for _, b in ipairs(popoutButtons) do b:Hide() end
flyout:Hide()
namePopup:Hide()
UpdatePopouts()
end)
flyout:SetFrameStrata("DIALOG")
flyout:Hide()
@@ -676,7 +490,7 @@ pfUI:RegisterModule("equipmentmanager", function()
end
end
ClearCursor()
UIErrorsFrame:AddMessage(EQUIPMENT_MANAGER_BAGS_FULL or "Your bags are full.", 1, .1, .1, 1)
UIErrorsFrame:AddMessage(ERR_EQUIPMENT_MANAGER_BAGS_FULL, 1, .1, .1, 1)
end
local function MakeFlyoutButton(i)
@@ -692,11 +506,11 @@ pfUI:RegisterModule("equipmentmanager", function()
b:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
if this.specialAction == "placeInBags" then
GameTooltip:SetText(EQUIPMENT_MANAGER_PLACE_IN_BAGS or "Place in Bags", 1, 1, 1)
GameTooltip:SetText(EQUIPMENT_MANAGER_PLACE_IN_BAGS, 1, 1, 1)
elseif this.specialAction == "ignore" then
GameTooltip:SetText(EQUIPMENT_MANAGER_IGNORE_SLOT or "Ignore this slot", 1, 1, 1)
GameTooltip:SetText(EQUIPMENT_MANAGER_IGNORE_SLOT, 1, 1, 1)
elseif this.specialAction == "unignore" then
GameTooltip:SetText(EQUIPMENT_MANAGER_UNIGNORE_SLOT or "Stop ignoring this slot", 1, 1, 1)
GameTooltip:SetText(EQUIPMENT_MANAGER_UNIGNORE_SLOT, 1, 1, 1)
elseif this.bag then
GameTooltip:SetBagItem(this.bag, this.slot)
elseif this.invSlot then
@@ -704,7 +518,7 @@ pfUI:RegisterModule("equipmentmanager", function()
end
GameTooltip:Show()
end)
b:SetScript("OnLeave", function() GameTooltip:Hide() end)
b:SetScript("OnLeave", GameTooltip_Hide)
b:SetScript("OnClick", function()
if this.specialAction == "placeInBags" then
UnequipToBags(flyout.targetInvSlot)
@@ -809,7 +623,6 @@ pfUI:RegisterModule("equipmentmanager", function()
if num == 0 then
flyout:Hide()
UIErrorsFrame:AddMessage(T["No matching items in bags"] or "No matching items in bags", 1, 1, 0, 1)
return
end
@@ -938,15 +751,26 @@ pfUI:RegisterModule("equipmentmanager", function()
end
end
-- Tie popout visibility to the EM sidecar. They appear when the
-- sidecar opens, hide when it closes, and the flyout closes too.
frame:HookScript("OnShow", function()
for _, b in ipairs(popoutButtons) do b:Show() end
end)
frame:HookScript("OnHide", function()
for _, b in ipairs(popoutButtons) do b:Hide() end
if flyout then flyout:Hide() end
end)
-- Popout arrows follow the EM sidecar by default. With the
-- "Always Show Equipment Slot Flyouts" option they follow the paperdoll
-- instead, so gear can be swapped without opening the equipment manager.
local function PopoutsActive()
if C.character.inventory.equipflyout == "1" then
return PaperDollFrame:IsShown()
end
return frame:IsShown()
end
function UpdatePopouts()
local show = PopoutsActive()
for _, b in ipairs(popoutButtons) do
if show then b:Show() else b:Hide() end
end
if not show then flyout:Hide() end
end
PaperDollFrame:HookScript("OnShow", UpdatePopouts)
PaperDollFrame:HookScript("OnHide", UpdatePopouts)
-- ============================================================
-- Refresh
+1 -1
View File
@@ -136,7 +136,7 @@ pfUI:RegisterModule("farmmode", function ()
pfUI.farmmap.button.txt = pfUI.farmmap.button:CreateFontString("pfFarmMapText", "LOW", "GameFontWhite")
pfUI.farmmap.button.txt:SetFont(pfUI.font_default, C.global.font_size, "OUTLINE")
pfUI.farmmap.button.txt:SetPoint("CENTER", 0, 0)
pfUI.farmmap.button.txt:SetText("|cff33ffcc FARM MODE")
pfUI.farmmap.button.txt:SetText("|cff33ffcc " .. T["FARM MODE"])
end
CreateBackdrop(pfUI.farmmap)
+9 -14
View File
@@ -127,7 +127,7 @@ pfUI:RegisterModule("firstrun", function ()
-- welcome dialog
pfUI.firstrun:AddStep("init", function()
local f = CreateFirstRunPage()
f.text:SetText(string.format(T["Welcome to |cff33ffccpf|cffffffffUI|r!\n\nI'm the first run wizard that will guide you through some basic configuration. If you're lazy, feel free to hit the \"Defaults\" button. If you wish to run this dialog again, go to the settings and hit the \"Reset Firstrun\" button.\n\nVisit |cff33ffcc%s|r to check for the latest version."], GetAddOnMetadata(pfUI.name, "X-Website")))
f.text:SetFormattedText(T["Welcome to |cff33ffccpf|cffffffffUI|r!\n\nI'm the first run wizard that will guide you through some basic configuration. If you're lazy, feel free to hit the \"Defaults\" button. If you wish to run this dialog again, go to the settings and hit the \"Reset Firstrun\" button.\n\nVisit |cff33ffcc%s|r to check for the latest version."], GetAddOnMetadata(pfUI.name, "X-Website"))
return f
end)
@@ -137,8 +137,7 @@ pfUI:RegisterModule("firstrun", function ()
f.text:SetText(T["A new installation of |cff33ffccpf|rUI ships with 4 prebuilt design profiles. Click below if you wish to load one of these profiles."])
f.Modern = CreateFrame("Button", nil, f, "UIPanelButtonTemplate")
f.Modern:SetWidth(120)
f.Modern:SetHeight(20)
f.Modern:SetSize(120, 20)
f.Modern:SetPoint("BOTTOM", -65, 100)
f.Modern:SetTextColor(1,1,1)
f.Modern:SetText("Modern")
@@ -151,8 +150,7 @@ pfUI:RegisterModule("firstrun", function ()
SkinButton(f.Modern)
f.Nostalgia = CreateFrame("Button", nil, f, "UIPanelButtonTemplate")
f.Nostalgia:SetWidth(120)
f.Nostalgia:SetHeight(20)
f.Nostalgia:SetSize(120, 20)
f.Nostalgia:SetPoint("BOTTOM", 65, 100)
f.Nostalgia:SetTextColor(1,1,1)
f.Nostalgia:SetText("Nostalgia")
@@ -165,8 +163,7 @@ pfUI:RegisterModule("firstrun", function ()
SkinButton(f.Nostalgia)
f.Legacy = CreateFrame("Button", nil, f, "UIPanelButtonTemplate")
f.Legacy:SetWidth(120)
f.Legacy:SetHeight(20)
f.Legacy:SetSize(120, 20)
f.Legacy:SetPoint("BOTTOM", 65, 75)
f.Legacy:SetTextColor(1,1,1)
f.Legacy:SetText("Legacy")
@@ -179,8 +176,7 @@ pfUI:RegisterModule("firstrun", function ()
SkinButton(f.Legacy)
f.Slim = CreateFrame("Button", nil, f, "UIPanelButtonTemplate")
f.Slim:SetWidth(120)
f.Slim:SetHeight(20)
f.Slim:SetSize(120, 20)
f.Slim:SetPoint("BOTTOM", -65, 75)
f.Slim:SetTextColor(1,1,1)
f.Slim:SetText("Slim")
@@ -197,8 +193,7 @@ pfUI:RegisterModule("firstrun", function ()
f.Slider.text:SetPoint("TOP", f.Slider, "BOTTOM", 0, 2)
f.Slider.text:SetText(T["Scale"])
f.Slider:SetWidth(240)
f.Slider:SetHeight(20)
f.Slider:SetSize(240, 20)
f.Slider:SetPoint("BOTTOM", 0, 50)
f.Slider:SetOrientation('HORIZONTAL')
f.Slider:SetMinMaxValues(0.5, 2.0)
@@ -263,7 +258,7 @@ pfUI:RegisterModule("firstrun", function ()
SkinCheckbox(f.checkbox, 18)
f.NextScript = function()
if not pfUI.chat then message("Couldn't apply settings. Chat module is disabled.") end
if not pfUI.chat then message("Couldn't apply settings. Chat module is disabled.") return end
pfUI.chat.SetupRightChat(f.checkbox:GetChecked())
end
@@ -283,7 +278,7 @@ pfUI:RegisterModule("firstrun", function ()
SkinCheckbox(f.checkbox, 18)
f.NextScript = function()
if not pfUI.chat then message("Couldn't apply settings. Chat module is disabled.") end
if not pfUI.chat then message("Couldn't apply settings. Chat module is disabled.") return end
if f.checkbox:GetChecked() then
pfUI.chat.SetupPositions()
end
@@ -305,7 +300,7 @@ pfUI:RegisterModule("firstrun", function ()
SkinCheckbox(f.checkbox, 18)
f.NextScript = function()
if not pfUI.chat then message("Couldn't apply settings. Chat module is disabled.") end
if not pfUI.chat then message("Couldn't apply settings. Chat module is disabled.") return end
if f.checkbox:GetChecked() then
pfUI.chat.SetupChannels()
end
+93
View File
@@ -0,0 +1,93 @@
-- Friend notes
-- Client-side notes for friends via ClassicAPI's C_FriendList note API
-- (SetFriendNotes / GetFriendInfo(.notes)). Adds an "Edit Note" entry to the
-- friend right-click menu (the "FRIEND" UnitPopup) and shows the note in a
-- tooltip when you hover a friend in the list.
pfUI:RegisterModule("friendnotes", function ()
local EDIT_TOKEN = "PFUI_FRIEND_NOTE"
-- Note editor. In 1.12 StaticPopup, OnAccept runs with `this` = the OK
-- button (the dialog is this:GetParent()); the friend name arrives as the
-- data argument passed to StaticPopup_Show.
StaticPopupDialogs["PFUI_FRIEND_NOTE_EDIT"] = {
text = SET_FRIENDNOTE_LABEL,
button1 = SAVE,
button2 = CANCEL,
hasEditBox = 1,
maxLetters = 128,
OnAccept = function(name)
C_FriendList.SetFriendNotes(name, _G[this:GetParent():GetName().."EditBox"]:GetText())
end,
EditBoxOnEnterPressed = function(name)
C_FriendList.SetFriendNotes(name, this:GetText())
this:GetParent():Hide()
end,
EditBoxOnEscapePressed = function() this:GetParent():Hide() end,
timeout = 0, whileDead = 1, hideOnEscape = 1,
}
local function OpenNoteEditor(name)
if not name or name == "" then return end
local info = C_FriendList.GetFriendInfo(name)
local dialog = StaticPopup_Show("PFUI_FRIEND_NOTE_EDIT", name, nil, name)
if not dialog then return end
local editbox = _G[dialog:GetName().."EditBox"]
editbox:SetText((info and info.notes) or "")
editbox:HighlightText()
editbox:SetFocus()
end
-- Add "Edit Note" to the friend right-click menu, just before Cancel.
UnitPopupButtons[EDIT_TOKEN] = { text = SET_NOTE, dist = 0 }
local friendMenu = UnitPopupMenus["FRIEND"]
table.insert(friendMenu, table.getn(friendMenu), EDIT_TOKEN)
UnitPopupMenus["PFUI_FRIENDNOTE"] = { EDIT_TOKEN, "CANCEL" }
local function OfflineNoteDropDown_Initialize()
UnitPopup_ShowMenu(_G[UIDROPDOWNMENU_OPEN_MENU], "PFUI_FRIENDNOTE", nil, FriendsDropDown.name)
end
local FriendsFrame_ShowDropdown_orig = FriendsFrame_ShowDropdown
_G.FriendsFrame_ShowDropdown = function(name, connected)
if connected then return FriendsFrame_ShowDropdown_orig(name, connected) end
HideDropDownMenu(1)
FriendsDropDown.initialize = OfflineNoteDropDown_Initialize
FriendsDropDown.displayMode = "MENU"
FriendsDropDown.name = name
ToggleDropDownMenu(1, nil, FriendsDropDown, "cursor")
end
-- Route our entry to the editor and close the menu; delegate the rest. A
-- bare assignment lands on pfUI.env, so set the global explicitly.
local UnitPopup_OnClick_orig = UnitPopup_OnClick
_G.UnitPopup_OnClick = function()
if this and this.value == EDIT_TOKEN then
local dropdown = _G[UIDROPDOWNMENU_INIT_MENU]
local name = dropdown and dropdown.name
CloseDropDownMenus()
OpenNoteEditor(name)
return
end
return UnitPopup_OnClick_orig()
end
-- Show the note in a tooltip while hovering a friend in the list. Friend
-- buttons carry their friend index via SetID (see FriendsFrame_Update).
local function FriendButton_OnEnter()
local index = this:GetID()
if not index or index < 1 then return end
local info = C_FriendList.GetFriendInfoByIndex(index)
if not info or not info.notes or info.notes == "" then return end
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetText(info.name or "")
GameTooltip:AddLine(info.notes, 1, 1, 1, 1)
GameTooltip:Show()
end
for i = 1, FRIENDS_TO_DISPLAY do
local button = _G["FriendsFrameFriendButton"..i]
if button then
button:HookScript("OnEnter", FriendButton_OnEnter)
button:HookScript("OnLeave", GameTooltip_Hide)
end
end
end)
+17 -5
View File
@@ -2240,7 +2240,6 @@ pfUI:RegisterModule("gui", function ()
-- build config entries
CreateConfig(U[c], T["Display Frame"] .. ": " .. t, C.unitframes[c], "visible", "checkbox")
CreateConfig(U[c], T["Enable Mouseover Tooltip"], C.unitframes[c], "showtooltip", "checkbox")
CreateConfig(U[c], T["Default Transparency"], C.unitframes[c], "alpha_visible", "dropdown", pfUI.gui.dropdowns.percent_small)
CreateConfig(U[c], T["Out Of Range Transparency"], C.unitframes[c], "alpha_outrange", "dropdown", pfUI.gui.dropdowns.percent_small)
CreateConfig(U[c], T["Offline Transparency"], C.unitframes[c], "alpha_offline", "dropdown", pfUI.gui.dropdowns.percent_small)
@@ -2412,6 +2411,7 @@ pfUI:RegisterModule("gui", function ()
CreateGUIEntry(T["Character"], T["Inventory"], function()
CreateConfig(nil, T["Show Durability"], C.character.inventory, "durability", "checkbox")
CreateConfig(nil, T["Always Show Equipment Slot Flyouts"], C.character.inventory, "equipflyout", "checkbox")
end)
CreateGUIEntry(T["Character"], T["Reputation"], function()
@@ -2763,21 +2763,32 @@ pfUI:RegisterModule("gui", function ()
end)
CreateGUIEntry(T["Tooltip"], nil, function()
CreateConfig(nil, T["General"], nil, nil, "header")
CreateConfig(nil, T["Tooltip Position"], C.tooltip, "position", "dropdown", pfUI.gui.dropdowns.tooltip_position)
CreateConfig(nil, T["Tooltip Text Font"], C.tooltip, "font_tooltip", "dropdown", pfUI.gui.dropdowns.fonts)
CreateConfig(nil, T["Tooltip Text Font Size"], C.tooltip, "font_tooltip_size")
CreateConfig(nil, T["Cursor Tooltip Align"], C.tooltip, "cursoralign", "dropdown", pfUI.gui.dropdowns.tooltip_align)
CreateConfig(nil, T["Cursor Tooltip Offset"], C.tooltip, "cursoroffset")
CreateConfig(nil, T["Enable Extended Guild Information"], C.tooltip, "extguild", "checkbox")
CreateConfig(nil, T["Always Show Health In Percent"], C.tooltip, "alwaysperc", "checkbox")
CreateConfig(nil, T["Show Item IDs"], C.tooltip, "itemid", "checkbox")
CreateConfig(nil, T["Show Movement Speed"], C.tooltip, "movespeed", "checkbox")
CreateConfig(nil, T["Custom Transparency"], C.tooltip, "alpha")
CreateConfig(nil, T["Status Bar Texture"], C.tooltip.statusbar, "texture", "dropdown", pfUI.gui.dropdowns.uf_bartexture)
CreateConfig(nil, T["Items"], nil, nil, "header")
CreateConfig(nil, T["Always Show Item Comparison"], C.tooltip.compare, "showalways", "checkbox")
CreateConfig(nil, T["Always Show Extended Vendor Values"], C.tooltip.vendor, "showalways", "checkbox")
CreateConfig(U["questitem"], T["Show Related Quest On Questitems"], C.tooltip.questitem, "showquest", "checkbox")
CreateConfig(U["questitem"], T["Show Required Questitem Count"], C.tooltip.questitem, "showcount", "checkbox")
CreateConfig(nil, T["Show Item IDs"], C.tooltip, "itemid", "checkbox")
CreateConfig(nil, T["Spells"], nil, nil, "header")
CreateConfig(nil, T["Show Aura Caster"], C.tooltip, "aurasource", "checkbox")
CreateConfig(nil, T["Show Spell IDs"], C.tooltip, "spellid", "checkbox")
CreateConfig(nil, T["Units"], nil, nil, "header")
CreateConfig(nil, T["Enable Extended Guild Information"], C.tooltip, "extguild", "checkbox")
CreateConfig(nil, T["Always Show Health In Percent"], C.tooltip, "alwaysperc", "checkbox")
CreateConfig(nil, T["Show Movement Speed"], C.tooltip, "movespeed", "checkbox")
CreateConfig(nil, T["Show Unit Buffs"], C.tooltip, "showbuffs", "checkbox")
CreateConfig(nil, T["Show Unit IDs"], C.tooltip, "unitid", "checkbox")
end)
CreateGUIEntry(T["Castbar"], T["General"], function()
@@ -2988,6 +2999,7 @@ pfUI:RegisterModule("gui", function ()
CreateConfig(nil, T["Use Chat Colors for Meters"], C.thirdparty, "chatbg", "checkbox")
CreateConfig(nil, "ShaguDPS (" .. T["Skin"] .. ")", C.thirdparty.shagudps, "skin", "checkbox")
CreateConfig(nil, "ShaguDPS (" .. T["Dock"] .. ")", C.thirdparty.shagudps, "dock", "checkbox")
CreateConfig(nil, "GreedMeter (" .. T["Dock"] .. ")", C.thirdparty.greedmeter, "dock", "checkbox")
CreateConfig(nil, "DPSMate (" .. T["Skin"] .. ")", C.thirdparty.dpsmate, "skin", "checkbox")
CreateConfig(nil, "DPSMate (" .. T["Dock"] .. ")", C.thirdparty.dpsmate, "dock", "checkbox")
CreateConfig(nil, "SWStats (" .. T["Skin"] .. ")", C.thirdparty.swstats, "skin", "checkbox")
+34 -9
View File
@@ -1,10 +1,8 @@
pfUI:RegisterModule("hunterbar", function ()
local class = UnitClassBase("player")
if class ~= "HUNTER" or C.bars.hunterbar == "0" then return end
if UnitClassBase("player") ~= "HUNTER" or C.bars.hunterbar == "0" then return end
-- Wing Clip (any rank) and Arcane Shot (any rank) spell IDs.
-- IsSpellInRange(spellId) works with any spell ID via Nampower,
-- no actionbar slot needed.
-- C_Spell.IsSpellInRange works with any spell ID, no actionbar slot needed.
local WINGCLIP_ID = 2974 -- melee range indicator (~5 yd)
local ARCANESHOT_ID = 3044 -- ranged range indicator (~35 yd)
@@ -12,22 +10,37 @@ pfUI:RegisterModule("hunterbar", function ()
-- Only swap BACK to melee bar when Wing Clip is actually in range.
-- This prevents rapid bar-flipping in the transition zone.
local THROTTLE = 0.02 -- seconds between range checks (was every frame)
pfUI.hunterbar = CreateFrame("Frame", "pfHunterBar", UIParent)
-- track which page we last forced so we don't spam ChangeActionBarPage()
pfUI.hunterbar.lastPage = nil
pfUI.hunterbar.elapsed = 0
-- Idle until there's a live, attackable target. A hidden frame runs no
-- OnUpdate, so the range polling only ticks while it can matter.
pfUI.hunterbar:Hide()
pfUI.hunterbar:SetScript("OnUpdate", function()
-- only act when there is a live, attackable target
this.elapsed = this.elapsed + arg1
if this.elapsed < THROTTLE then return end
this.elapsed = 0
-- Target died or turned unattackable without a target-change event
-- (it dropped to a corpse); stop ticking until the next target.
if not UnitExists("target") or not UnitCanAttack("player", "target") then
this:Hide()
return
end
local wingclipInRange = IsSpellInRange(WINGCLIP_ID, "target")
local arcaneshotInRange = IsSpellInRange(ARCANESHOT_ID, "target")
-- C_Spell.IsSpellInRange returns true / false / nil (rangeless). The
-- explicit == true / == false tests leave the bar unchanged on nil.
local wingclipInRange = C_Spell.IsSpellInRange(WINGCLIP_ID, "target")
local arcaneshotInRange = C_Spell.IsSpellInRange(ARCANESHOT_ID, "target")
-- swap to ranged bar: out of melee range AND arcane shot (8yd) in range
if wingclipInRange == 0 and arcaneshotInRange == 1 then
if wingclipInRange == false and arcaneshotInRange == true then
if this.lastPage ~= 2 then
this.lastPage = 2
_G.CURRENT_ACTIONBAR_PAGE = 2
@@ -35,7 +48,7 @@ pfUI:RegisterModule("hunterbar", function ()
end
-- swap to melee bar: in melee range AND arcane shot (8yd) out of range
elseif wingclipInRange == 1 and arcaneshotInRange == 0 then
elseif wingclipInRange == true and arcaneshotInRange == false then
if this.lastPage ~= 1 then
this.lastPage = 1
_G.CURRENT_ACTIONBAR_PAGE = 1
@@ -43,4 +56,16 @@ pfUI:RegisterModule("hunterbar", function ()
end
end
end)
-- Start ticking only when we gain an attackable target.
pfUI.hunterbar:RegisterEvent("PLAYER_TARGET_CHANGED")
pfUI.hunterbar:RegisterEvent("PLAYER_ENTERING_WORLD")
pfUI.hunterbar:SetScript("OnEvent", function()
if UnitExists("target") and UnitCanAttack("player", "target") then
this.elapsed = THROTTLE -- run the first check on the next frame
this:Show()
else
this:Hide()
end
end)
end)
+7 -7
View File
@@ -75,14 +75,14 @@ pfUI:RegisterModule("innervatecall", function ()
SendChatMessage(">> Innervate casted on " .. targetName .. " <<", channel)
-- Schedule "ready" announcement when cooldown expires
-- Use GetSpellIdCooldown for precise remaining time, fallback to 360s
-- Schedule "ready" announcement when the cooldown expires. Read the
-- precise remaining time from C_Spell.GetSpellCooldown (startTime and
-- duration are seconds from the GetTime epoch); fall back to 360s.
local cdRemaining = 360
if GetSpellIdCooldown then
local cd = GetSpellIdCooldown(INNERVATE_SPELLID)
if cd and cd.cooldownRemainingMs and cd.cooldownRemainingMs > 0 then
cdRemaining = cd.cooldownRemainingMs / 1000
end
local cd = C_Spell.GetSpellCooldown(INNERVATE_SPELLID)
if cd and cd.duration and cd.duration > 0 then
local remaining = cd.startTime + cd.duration - GetTime()
if remaining > 0 then cdRemaining = remaining end
end
C_Timer.After(cdRemaining, function()
+8 -4
View File
@@ -26,10 +26,14 @@ pfUI:RegisterModule("itemcount", function ()
if bank < 0 then bank = 0 end
frame:AddLine(" ")
if bags > 0 then frame:AddDoubleLine("Bags:", bags, 1, 1, 1, 1, 1, 1) end
if bank > 0 then frame:AddDoubleLine("Bank:", bank, 1, 1, 1, 1, 1, 1) end
if equipped > 0 then frame:AddDoubleLine("Equipped:", equipped, 1, 1, 1, 1, 1, 1) end
frame:AddDoubleLine("Total:", total, 1, 1, 1, 1, 1, 1)
if bags > 0 then frame:AddDoubleLine(T["Bags"] .. ":", bags, 1, 1, 1, 1, 1, 1) end
if bank > 0 then frame:AddDoubleLine(T["Bank"] .. ":", bank, 1, 1, 1, 1, 1, 1) end
if equipped > 0 then frame:AddDoubleLine(T["Equipped"] .. ":", equipped, 1, 1, 1, 1, 1, 1) end
local sources = (bags > 0 and 1 or 0) + (bank > 0 and 1 or 0) + (equipped > 0 and 1 or 0)
if sources > 1 then
frame:AddDoubleLine(T["Total"] .. ":", total, 1, 1, 1, 1, 1, 1)
end
frame:Show()
end
+5 -15
View File
@@ -77,9 +77,7 @@ pfUI:RegisterModule("loot", function ()
if pfUI.loot.my_index or pfUI.loot.disenchanter_index or pfUI.loot.banker_index then
info = wipe(info)
info.text = T["Special Recipient"]
info.textR = NORMAL_FONT_COLOR.r
info.textG = NORMAL_FONT_COLOR.g
info.textB = NORMAL_FONT_COLOR.b
info.textR, info.textG, info.textB = NORMAL_FONT_COLOR:GetRGB()
info.textHeight = 12
info.hasArrow = 1
info.notCheckable = 1
@@ -158,9 +156,7 @@ pfUI:RegisterModule("loot", function ()
if level == 1 then
info = wipe(info)
info.text = T["Random"]
info.textR = NORMAL_FONT_COLOR.r
info.textG = NORMAL_FONT_COLOR.g
info.textB = NORMAL_FONT_COLOR.b
info.textR, info.textG, info.textB = NORMAL_FONT_COLOR:GetRGB()
info.value = "PFRANDOM"
info.textHeight = 12
info.notCheckable = 1
@@ -170,9 +166,7 @@ pfUI:RegisterModule("loot", function ()
info = wipe(info)
info.text = T["Request Rolls"]
info.textR = NORMAL_FONT_COLOR.r
info.textG = NORMAL_FONT_COLOR.g
info.textB = NORMAL_FONT_COLOR.b
info.textR, info.textG, info.textB = NORMAL_FONT_COLOR:GetRGB()
info.value = "PFROLLS"
info.textHeight = 12
info.hasArrow = 1
@@ -183,9 +177,7 @@ pfUI:RegisterModule("loot", function ()
if UIDROPDOWNMENU_MENU_VALUE == "PFROLLS" then
info = wipe(info)
info.text = T["Clear Rolls"]
info.textR = NORMAL_FONT_COLOR.r
info.textG = NORMAL_FONT_COLOR.g
info.textB = NORMAL_FONT_COLOR.b
info.textR, info.textG, info.textB = NORMAL_FONT_COLOR:GetRGB()
info.value = "PFCLEARROLLS"
info.notCheckable = 1
info.func = pfUI.loot.ClearRolls
@@ -193,9 +185,7 @@ pfUI:RegisterModule("loot", function ()
info = wipe(info)
info.text = T["Reroll Ties"]
info.textR = NORMAL_FONT_COLOR.r
info.textG = NORMAL_FONT_COLOR.g
info.textB = NORMAL_FONT_COLOR.b
info.textR, info.textG, info.textB = NORMAL_FONT_COLOR:GetRGB()
info.value = "PFTIEROLL"
info.notCheckable = 1
info.arg1 = pfUI.loot.rollers_sorted
+2 -8
View File
@@ -16,12 +16,6 @@ pfUI:RegisterModule("loothistory", function ()
local WINMARK = "Interface\\Buttons\\UI-CheckBox-Check"
local QUESTIONMARK = "Interface\\Icons\\INV_Misc_QuestionMark"
local function ClassColor(class)
local c = class and RAID_CLASS_COLORS[class]
if c then return c.r, c.g, c.b end
return 1, 1, 1
end
-- Paint an item row's icon/name/quality from a loaded Item mixin.
local function RenderItemVisual(f, item)
local r, g, b = 1, 1, 1
@@ -277,7 +271,7 @@ pfUI:RegisterModule("loothistory", function ()
if wroll and wroll > 0 then f.winroll:SetText(wroll) else f.winroll:SetText("") end
f.winroll:Show()
f.winname:SetText(wname or UNKNOWN)
f.winname:SetTextColor(ClassColor(wclass))
f.winname:SetTextColor(PFUI_CLASS_COLORS[wclass]:GetRGB())
f.winname:Show()
else
-- nobody won: everyone passed
@@ -298,7 +292,7 @@ pfUI:RegisterModule("loothistory", function ()
local function RenderPlayerFrame(pf, name, class, rollType, roll, isWinner)
pf.name:SetText(name or UNKNOWN)
pf.name:SetTextColor(ClassColor(class))
pf.name:SetTextColor(PFUI_CLASS_COLORS[class]:GetRGB())
pf.rollicon:SetTexture(ROLL_TEX[rollType] or ROLL_TEX[ROLL_PASS])
if roll and roll > 0 then pf.rolltext:SetText(roll) else pf.rolltext:SetText("") end
+128
View File
@@ -0,0 +1,128 @@
-- Macro icon picker
-- Replaces Blizzard's MacroPopupFrame (the "name + icon" dialog opened by the
-- New / Change-Icon buttons) with a pfUI-native picker driven by
-- IconDataProviderMixin, so the full spell + item + loose icon set is
-- available instead of the stock spell-only list. Saving goes 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
-- on both the modern (Turtle/Octo) and stock-vanilla macro UIs. The surrounding
-- MacroFrame (list + body editor) is Blizzard's, already skinned elsewhere.
pfUI:RegisterModule("macroicons", function ()
HookAddonOrVariable("Blizzard_MacroUI", function()
-- ============================================================
-- Popup frame
-- ============================================================
local picker = CreateFrame("Frame", "pfMacroIconPicker", UIParent)
picker:SetFrameStrata("DIALOG")
picker:SetSize(472, 484)
-- Starts anchored to the right of the macro panel (re-anchored at open
-- once the skin's backdrop exists); dragging pins it in place after.
picker:SetPoint("BOTTOMLEFT", MacroFrame, "BOTTOMRIGHT", 8, 0)
picker:Hide()
CreateBackdrop(picker, nil, nil, .9)
CreateBackdropShadow(picker)
picker:EnableMouse(true)
picker:SetMovable(true)
picker:RegisterForDrag("LeftButton")
picker:SetScript("OnDragStart", function() this:StartMoving() end)
picker:SetScript("OnDragStop", function()
this:StopMovingOrSizing()
this.userMoved = true
end)
-- Spellbook seed leads the grid with class-relevant spell/talent icons.
local iconPicker = CreateIconPicker("pfMacroIcon", picker,
IconDataProviderExtraType.Spellbook, MACRO_POPUP_TEXT)
-- ============================================================
-- Open / save
-- ============================================================
-- Blizzard's popup disables these while it is open; mirror that so the
-- underlying frame can't be double-driven, then let MacroFrame_Update
-- restore the correct states on close.
local function SetMacroButtons(enabled)
local m = enabled and "Enable" or "Disable"
MacroNewButton[m](MacroNewButton)
MacroEditButton[m](MacroEditButton)
MacroDeleteButton[m](MacroDeleteButton)
end
local function OpenMacroPopup(mode)
if mode == "edit" and MacroFrame.selectedMacro then
picker.mode = "edit"
local name, texture = GetMacroInfo(MacroFrame.selectedMacro)
picker.editbox:SetText(name or "")
iconPicker.SetIcon(texture)
else
picker.mode = "new"
picker.editbox:SetText("")
iconPicker.SetIcon(nil)
end
picker.search:SetText("")
if not picker.userMoved then
picker:ClearAllPoints()
picker:SetPoint("BOTTOMLEFT", MacroFrame.backdrop or MacroFrame, "BOTTOMRIGHT", 8, 0)
end
SetMacroButtons(false)
picker:Show()
picker.editbox:SetFocus()
iconPicker.Refresh()
end
local function SaveMacroPopup()
local text = picker.editbox:GetText()
if text == "" then return end
local icon = iconPicker.GetIcon()
if picker.mode == "edit" and MacroFrame.selectedMacro then
C_Macro.EditMacro(MacroFrame.selectedMacro, text, icon, nil)
MacroFrame_SelectMacro(MacroFrame.selectedMacro)
else
local idx = C_Macro.CreateMacro(text, icon, nil, (MacroFrame.macroBase or 0) > 0)
if idx then MacroFrame_SelectMacro(idx) end
end
picker:Hide()
end
picker:SetScript("OnHide", function() MacroFrame_Update() end)
picker.editbox:SetScript("OnEnterPressed", SaveMacroPopup)
picker.editbox:SetScript("OnEscapePressed", function() picker:Hide() end)
-- OK / Cancel
local okay = CreateFrame("Button", "pfMacroIconPickerOkay", picker, "UIPanelButtonTemplate")
okay:SetSize(80, 22)
okay:SetText(OKAY)
okay:SetScript("OnClick", SaveMacroPopup)
SkinButton(okay)
local cancel = CreateFrame("Button", "pfMacroIconPickerCancel", picker, "UIPanelButtonTemplate")
cancel:SetSize(80, 22)
cancel:SetText(CANCEL)
cancel:SetPoint("BOTTOMRIGHT", picker, "BOTTOMRIGHT", -14, 12)
cancel:SetScript("OnClick", function() picker:Hide() end)
SkinButton(cancel)
-- Okay | Cancel, both anchored to the bottom-right corner.
okay:SetPoint("BOTTOMRIGHT", cancel, "BOTTOMLEFT", -6, 0)
-- ============================================================
-- Take over the New / Change-Icon buttons; retire Blizzard's popup
-- ============================================================
MacroNewButton:SetScript("OnClick", function()
MacroFrame_SaveMacro()
OpenMacroPopup("new")
end)
MacroEditButton:SetScript("OnClick", function()
MacroFrame_SaveMacro()
OpenMacroPopup("edit")
end)
-- Closing the macro window takes the picker with it.
MacroFrame:HookScript("OnHide", function() picker:Hide() end)
end)
end)
+2 -1
View File
@@ -13,7 +13,8 @@ pfUI:RegisterModule("macrotweak", function ()
end
-- do not write macro calls into chat input history
if ChatFrameEditBox._AddHistoryLine then
-- (install once: _AddHistoryLine is our backup slot and is nil until we set it)
if not ChatFrameEditBox._AddHistoryLine then
local userinput
ChatFrameEditBox._AddHistoryLine = ChatFrameEditBox.AddHistoryLine
ChatFrameEditBox.AddHistoryLine = function(self, text)
+8 -2
View File
@@ -77,7 +77,13 @@ pfUI:RegisterModule("map", function ()
if point == "TOPLEFT" and relpoint == "TOPLEFT" then
offx = offx*oldscale/scale
offy = offy*oldscale/scale
WorldMapFrame:SetPoint(point, rel, relpoint, offx, offy)
-- Anchor to the parent (3-arg SetPoint) rather than re-using the frame
-- GetPoint handed back. Re-anchoring to `rel` throws
-- "<unnamed> is dependent on this" as soon as anything else has anchored
-- itself to WorldMapFrame, which aborts the whole zoom handler. The
-- parent-relative form is also what LoadMovable/SaveMovable already
-- assume: SaveMovable stores only xpos/ypos, with no relative frame.
WorldMapFrame:SetPoint(point, offx, offy)
end
WorldMapFrame:SetScale(scale)
@@ -175,7 +181,7 @@ pfUI:RegisterModule("map", function ()
end
if mx and my and MouseIsOver(WorldMapButton) then
WorldMapButton.coords.text:SetText(string.format('%.1f / %.1f', mx, my))
WorldMapButton.coords.text:SetFormattedText('%.1f / %.1f', mx, my)
else
WorldMapButton.coords.text:SetText("")
end
+5 -4
View File
@@ -52,19 +52,19 @@ pfUI:RegisterModule("mapreveal", function ()
WorldMapTooltip:AddLine(this.name, 1, 1, 1)
WorldMapTooltip:Show()
if not explorecaches[this.name] then return end
if not explorecaches[this.area] then return end
if C.appearance.worldmap.mapreveal == "0" then return end
for texture in pairs(explorecaches[this.name]) do
for texture in pairs(explorecaches[this.area]) do
texture:SetVertexColor(1,1,1,1)
end
end
local exploreLeave = function()
WorldMapTooltip:Hide()
if not explorecaches[this.name] then return end
if not explorecaches[this.area] then return end
if C.appearance.worldmap.mapreveal == "0" then return end
local r,g,b,a = GetStringColor(C.appearance.worldmap.mapreveal_color)
for texture in pairs(explorecaches[this.name]) do
for texture in pairs(explorecaches[this.area]) do
texture:SetVertexColor(r,g,b,a)
end
end
@@ -120,6 +120,7 @@ pfUI:RegisterModule("mapreveal", function ()
explore:EnableMouse(true)
explore:SetFrameLevel(255)
explore.name = mapFileName .. " (" .. name .. ")"
explore.area = name -- cache key: explorecaches is keyed by the plain area name
explore.tex = explore.tex or explore:CreateTexture("", "OVERLAY")
explore.tex:SetBlendMode("ADD")
explore.tex:SetTexCoord(.08, .92, .08, .92)
+54 -59
View File
@@ -1,57 +1,32 @@
pfUI:RegisterModule("marktracking", function ()
-- Requires mark1-mark8 unit tokens (Turtle WoW / Nampower)
if not UnitExists("mark1") and not UnitExists("mark8") then
if not pcall(function() UnitExists("mark1") end) then return end
end
local rawborder, border = GetBorderSize()
-- Parse color strings "r,g,b,a" into components
local function ParseColor(str, dr, dg, db, da)
if not str or str == "" then return dr, dg, db, da end
local _, _, r, g, b, a = string.find(str, "([%d%.]+),([%d%.]+),([%d%.]+),([%d%.]+)")
if r then
return tonumber(r) or dr, tonumber(g) or dg, tonumber(b) or db, tonumber(a) or da
end
return dr, dg, db, da
end
local markerOrder = { 8, 7, 6, 5, 4, 3, 2, 1 } -- skull, cross, square, moon, triangle, diamond, circle, star
local markerTokens = {}
for i = 1, 8 do markerTokens[i] = "mark" .. i end
-- Default colors per marker
local defaultColors = {
[1] = { 1.0, 0.9, 0.0, 1 }, -- star: yellow
[2] = { 1.0, 0.5, 0.0, 1 }, -- circle: orange
[3] = { 0.8, 0.0, 0.8, 1 }, -- diamond: purple
[4] = { 0.0, 0.8, 0.0, 1 }, -- triangle: green
[5] = { 0.7, 0.7, 0.7, 1 }, -- moon: silver
[6] = { 0.0, 0.4, 0.9, 1 }, -- square: blue
[7] = { 0.9, 0.0, 0.0, 1 }, -- cross: red
[8] = { 1.0, 1.0, 1.0, 1 }, -- skull: white
}
local markerConfigKeys = {
[1] = "raidmarkercolor_star",
[2] = "raidmarkercolor_circle",
[3] = "raidmarkercolor_diamond",
[4] = "raidmarkercolor_triangle",
[5] = "raidmarkercolor_moon",
[6] = "raidmarkercolor_square",
[7] = "raidmarkercolor_cross",
[8] = "raidmarkercolor_skull",
"raidmarkercolor_star",
"raidmarkercolor_circle",
"raidmarkercolor_diamond",
"raidmarkercolor_triangle",
"raidmarkercolor_moon",
"raidmarkercolor_square",
"raidmarkercolor_cross",
"raidmarkercolor_skull",
}
local markerColors = {}
for i = 1, 8 do
local d = defaultColors[i]
local r, g, b, a = ParseColor(C.unitframes[markerConfigKeys[i]], d[1], d[2], d[3], d[4])
markerColors[i] = { r, g, b, a }
for i, markKey in ipairs(markerConfigKeys) do
markerTokens[i] = "mark" .. i
local r, g, b, a = GetStringColor(C.unitframes[markKey])
markerColors[i] = { tonumber(r), tonumber(g), tonumber(b), tonumber(a) }
end
local FALLBACK_INTERVAL = 1.0 -- safety net for units that come into range after marker was set
local elapsed = 0
local isUnlocked = false
local ROW_HEIGHT = tonumber(C.unitframes.raidmarkerheight) or 14
local BAR_WIDTH = tonumber(C.unitframes.raidmarkerwidth) or 80
@@ -125,19 +100,16 @@ pfUI.marktracking = CreateFrame("Frame", "pfMarkTracking", UIParent)
local i = markerOrder[idx]
local row = CreateFrame("Button", nil, pfUI.marktracking)
row:SetWidth(TOTAL_ROW_WIDTH)
row:SetHeight(ROW_HEIGHT)
row:SetSize(TOTAL_ROW_WIDTH, ROW_HEIGHT)
row:Hide()
row:RegisterForClicks("LeftButtonUp")
row:SetScript("OnClick", function()
TargetUnit(markerTokens[this.markerIndex])
end)
row:RegisterForClicks("LeftButtonUp", "RightButtonUp")
row:SetAttribute("type1", "target")
row:SetAttribute("type2", "menu")
-- raid icon
row.icon = row:CreateTexture(nil, "ARTWORK")
row.icon:SetWidth(ROW_HEIGHT)
row.icon:SetHeight(ROW_HEIGHT)
row.icon:SetSize(ROW_HEIGHT, ROW_HEIGHT)
row.icon:SetPoint("LEFT", row, "LEFT", 1, 0)
local markTex = C.unitframes.blizzard_raidicons == "1" and "Interface\\TargetingFrame\\UI-RaidTargetingIcons" or pfUI.media["img:raidicons"]
row.icon:SetTexture(markTex)
@@ -145,8 +117,7 @@ pfUI.marktracking = CreateFrame("Frame", "pfMarkTracking", UIParent)
-- portrait (right side)
row.portrait = row:CreateTexture(nil, "ARTWORK")
row.portrait:SetWidth(PORTRAIT_SIZE)
row.portrait:SetHeight(PORTRAIT_SIZE)
row.portrait:SetSize(PORTRAIT_SIZE, PORTRAIT_SIZE)
row.portrait:SetPoint("RIGHT", row, "RIGHT", -1, 0)
row.portrait:SetTexCoord(.1, .9, .1, .9)
if not rm_showportrait then row.portrait:Hide() end
@@ -204,8 +175,8 @@ pfUI.marktracking = CreateFrame("Frame", "pfMarkTracking", UIParent)
local token = markerTokens[i]
if UnitExists(token) and not UnitIsDead(token) then
local hp = UnitHealth(token)
local maxhp = UnitHealthMax(token)
local hp, maxhp = UnitHealth(token), UnitHealthMax(token)
row:SetAttribute('unit', token)
if hp and maxhp and maxhp > 0 and hp > 0 then
local pct = hp / maxhp
@@ -267,6 +238,27 @@ pfUI.marktracking = CreateFrame("Frame", "pfMarkTracking", UIParent)
end
end
-- Fast per-row refresh for a single mark's UNIT_HEALTH/UNIT_MAXHEALTH: moves
-- just the bar + hp text. If the mark's visibility flips (comes into range,
-- dies, hp crosses 0) the visible rows re-pack, so hand off to UpdateDisplay.
local function UpdateRow(i)
if isUnlocked then return end
local row = pfUI.marktracking.rows[i]
if not row then return end
local token = markerTokens[i]
local hp, maxhp = UnitHealth(token), UnitHealthMax(token)
local shouldShow = UnitExists(token) and not UnitIsDead(token)
and hp and maxhp and maxhp > 0 and hp > 0 and true or false
if shouldShow ~= (row:IsShown() and true or false) then
UpdateDisplay()
return
end
if not shouldShow then return end
local pct = hp / maxhp
row.health:SetValue(pct)
if rm_showpct then row.hptext:SetText(math.ceil(pct * 100) .. "%") end
end
-- Unlock mode: show fixed 1-row placeholder so positioning works correctly
if pfUI.unlock then
local origShow = pfUI.unlock:GetScript("OnShow")
@@ -292,24 +284,27 @@ pfUI.marktracking = CreateFrame("Frame", "pfMarkTracking", UIParent)
-- Event-driven scanner frame
local scanner = CreateFrame("Frame")
-- RAID_TARGET_UPDATE: fires when a raid marker is set/cleared
-- PLAYER_ENTERING_WORLD: fires on login, reload, and zone transitions
-- UNIT_HEALTH/UNIT_MAXHEALTH: fires on HP changes for real-time bar updates
-- RAID_TARGET_UPDATE: a raid marker was set/cleared -> full refresh
-- PLAYER_ENTERING_WORLD: login/reload/zone -> full refresh
-- UNIT_HEALTH/UNIT_MAXHEALTH: ClassicAPI fires these per token; with the mark
-- tokens observed they arrive as arg1 == "markN", so we refresh just that
-- one row (UpdateRow) instead of rescanning all eight.
scanner:RegisterEvent("RAID_TARGET_UPDATE")
scanner:RegisterEvent("PLAYER_ENTERING_WORLD")
scanner:RegisterEvent("UNIT_HEALTH")
scanner:RegisterEvent("UNIT_MAXHEALTH")
scanner:SetScript("OnEvent", function()
if event == "UNIT_HEALTH" or event == "UNIT_MAXHEALTH" then
-- arg1 is the token; "markN" -> N, nil for any non-mark token.
local i = arg1 and tonumber(string.match(arg1, "^mark(%d)"))
if i then UpdateRow(i) end
return
end
UpdateDisplay()
end)
-- Fallback poll at 1s: catches units that come into range AFTER a marker was set
-- Fallback poll: catches units that come into range AFTER a marker was set
-- (no event fires for that case, so we need this safety net)
scanner:SetScript("OnUpdate", function()
elapsed = elapsed + arg1
if elapsed < FALLBACK_INTERVAL then return end
elapsed = 0
UpdateDisplay()
end)
C_Timer.NewTicker(FALLBACK_INTERVAL, UpdateDisplay)
end)
+7 -9
View File
@@ -131,18 +131,16 @@ pfUI:RegisterModule("minimap", function ()
-- Create coordinates text frame with location configurable
pfUI.minimapCoordinates = CreateFrame("Frame", "pfMinimapCoord", pfUI.minimap)
pfUI.minimapCoordinates:SetScript("OnUpdate", function()
-- Throttle to update coords every 0.1 seconds
if ( this.tick or 0) > GetTime() then return end
this.tick = GetTime() + .1
-- Update coords every 0.1 seconds
C_Timer.NewTicker(0.1, function()
if C.appearance.minimap.coordstext == "off" then return end
this.posX, this.posY = GetPlayerMapPosition("player")
if this.posX ~= 0 and this.posY ~= 0 then
this.text:SetText(string.format("%.1f, %.1f", round(this.posX * 100, 1), round(this.posY * 100, 1)))
local coord = pfUI.minimapCoordinates
coord.posX, coord.posY = GetPlayerMapPosition("player")
if coord.posX ~= 0 and coord.posY ~= 0 then
coord.text:SetFormattedText("%.1f, %.1f", round(coord.posX * 100, 1), round(coord.posY * 100, 1))
else
this.text:SetText("|cffffaaaaN/A")
coord.text:SetText("|cffffaaaaN/A")
end
end)
+220 -127
View File
@@ -55,6 +55,7 @@ pfUI:RegisterModule("nameplates", function ()
local savedHostileState = nil
local savedFriendlyState = nil
local inFriendlyZone = false
local myGuild = nil
local platecount = 0
local registry = {}
-- Subset of registry that currently has a unit assigned (between
@@ -64,18 +65,19 @@ pfUI:RegisterModule("nameplates", function ()
local raidGuidCache = {} -- guid -> name (rebuilt on RAID_ROSTER_UPDATE/PARTY_MEMBERS_CHANGED)
-- Per-GUID cast state, populated by nampower's SPELL_START_OTHER events and
-- cleared on SPELL_FAILED_OTHER / plate removal / expiry. This replaces the
-- old per-tick C_Spell poll on every visible plate: cast detection is now
-- event driven, and GetCastInfo just reads this cache.
-- Per-GUID cast state, populated from ClassicAPI's UNIT_SPELLCAST_* events
-- (which now fire for nameplate tokens) and cleared on STOP / plate removal /
-- expiry. This replaces the old per-tick C_Spell poll on every visible plate:
-- cast detection is event driven, and GetCastInfo just reads this cache.
local castState = {}
-- guid -> nameplate, maintained on NAME_PLATE_UNIT_ADDED/_REMOVED so a cast
-- event can find its plate in O(1) and only cache casts we actually show.
local plateByGuid = {}
-- One-shot C_Spell poll. Only used to seed a plate that spawns while its
-- unit is already mid-cast (its SPELL_START_OTHER fired before the plate
-- existed). Never called per frame.
-- One-shot C_Spell poll. Builds the cast struct for a UNIT_SPELLCAST_* event
-- (the payload carries no timing) and seeds a plate that spawns while its unit
-- is already mid-cast (its START fired before the plate existed). Picks cast
-- vs channel itself. Never called per frame.
local function PollCastInfo(unit)
if not unit then return nil end
local name, _, texture, startMs, endMs, _, _, _, spellID = C_Spell.UnitCastingInfo(unit)
@@ -281,15 +283,35 @@ pfUI:RegisterModule("nameplates", function ()
end
end
local function TotemPlate(name)
if C.nameplates.totemicons == "1" then
for totem, icon in pairs(L["totems"]) do
if string.find(name, totem) then return icon end
end
-- Numeric CreatureType.dbc id for the plate's unit (8 = Critter, 11 = Totem).
-- It's fixed per unit, so cache it and reset on plate reuse. nil while the GUID
-- can't resolve yet (incl. the no-SuperWoW case, where cachedGuid is empty).
-- Locale-proof and covers custom units for free -- no name tables needed.
local function CreatureType(plate)
local guid = plate.cachedGuid
if not guid or guid == "" then return nil end
if not plate.creatureType then
plate.creatureType = UnitCreatureTypeID(guid) -- nil retries next call
end
return plate.creatureType
end
local function HidePlate(unittype, name, fullhp, target)
-- Totem icon, read straight from the game:
-- * Passive totems self-cast their provided buff, so they carry exactly one
-- aura whose icon IS the totem's icon (Totem::Summon TOTEM_PASSIVE).
-- * Active totems (Searing/Magma/Fire Nova) cast at enemies and hold no
-- self-aura, so their icon arrives via UNIT_SPELLCAST_SUCCEEDED (cached
-- into plate.totemIcon by the event handler); nil here until then.
local function TotemPlate(plate)
if C.nameplates.totemicons ~= "1" then return nil end
if CreatureType(plate) ~= 11 then return nil end
if plate.totemIcon then return plate.totemIcon end
local aura = C_UnitAuras.GetBuffDataByIndex(plate.cachedGuid, 1)
if aura then plate.totemIcon = aura.icon end
return plate.totemIcon
end
local function HidePlate(unittype, fullhp, target, plate)
-- keep some plates always visible according to config
if C.nameplates.fullhealth == "1" and not fullhp then return nil end
if C.nameplates.target == "1" and target then return nil end
@@ -305,14 +327,10 @@ pfUI:RegisterModule("nameplates", function ()
return true
elseif C.nameplates.friendlyplayer == "1" and unittype == "FRIENDLY_PLAYER" then
return true
elseif C.nameplates.critters == "1" and unittype == "NEUTRAL_NPC" then
for i, critter in pairs(L["critters"]) do
if string.lower(name) == string.lower(critter) then return true end
end
elseif C.nameplates.totems == "1" then
for totem in pairs(L["totems"]) do
if string.find(name, totem) then return true end
end
elseif C.nameplates.critters == "1" and CreatureType(plate) == 8 then
return true
elseif C.nameplates.totems == "1" and CreatureType(plate) == 11 then
return true
end
-- nothing to hide
@@ -477,8 +495,12 @@ nameplates:RegisterEvent("NAME_PLATE_UNIT_REMOVED")
nameplates:RegisterEvent("UNIT_AURA")
nameplates:RegisterEvent("UNIT_FLAGS")
nameplates:RegisterEvent("UPDATE_MOUSEOVER_UNIT")
nameplates:RegisterEvent("SPELL_START_OTHER")
nameplates:RegisterEvent("SPELL_FAILED_OTHER")
nameplates:RegisterEvent("UNIT_SPELLCAST_START")
nameplates:RegisterEvent("UNIT_SPELLCAST_CHANNEL_START")
nameplates:RegisterEvent("UNIT_SPELLCAST_STOP")
nameplates:RegisterEvent("UNIT_SPELLCAST_CHANNEL_STOP")
nameplates:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED")
nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
nameplates:SetScript("OnEvent", function()
-- Stop event handling during logout to prevent crash 132
@@ -490,13 +512,17 @@ nameplates:RegisterEvent("SPELL_FAILED_OTHER")
nameplates.mouselook:SetScript("OnUpdate", nil)
end
return
elseif event == "PLAYER_GUILD_UPDATE" and arg1 == 'player' then
myGuild = GetGuildInfo("player")
elseif event == "PLAYER_ENTERING_WORLD" or event == "ZONE_CHANGED_NEW_AREA" then
if event == "PLAYER_ENTERING_WORLD" then
CacheConfig()
this:SetGameVariables()
RebuildRaidGuidCache()
frameState.targetGuid = UnitGUID("target")
myGuild = GetGuildInfo("player")
end
-- Handle friendly zone nameplate disable feature
@@ -560,11 +586,13 @@ nameplates:RegisterEvent("SPELL_FAILED_OTHER")
local guid = UnitGUID(arg1)
plate.nameplate.cachedGuid = guid
plate.nameplate.unit = arg1
plate.nameplate.creatureType = nil -- recompute for the new unit
plate.nameplate.totemIcon = nil
if guid then
plateByGuid[guid] = plate.nameplate
-- Seed: the unit may already be mid-cast (its SPELL_START_OTHER fired
-- before this plate existed). One poll here catches that; ongoing
-- casts arrive via the event.
-- Seed: the unit may already be mid-cast (its UNIT_SPELLCAST_START
-- fired before this plate existed). One poll here catches that;
-- ongoing casts arrive via the event.
castState[guid] = PollCastInfo(arg1)
end
nameplates.OnShow(plate)
@@ -609,39 +637,48 @@ nameplates:RegisterEvent("SPELL_FAILED_OTHER")
if pn then pn.eventcache = true end
end
elseif event == "SPELL_START_OTHER" then
-- nampower: arg2=spellId, arg3=casterGuid, arg6=castTime(ms),
-- arg7=channel duration(ms, 0 if not a channel), arg8=spellType
-- (1 = channel). Cache the cast only for a unit we have a plate for, so
elseif event == "UNIT_SPELLCAST_START" or event == "UNIT_SPELLCAST_CHANNEL_START" then
-- ClassicAPI fires UNIT_SPELLCAST_* per unit token, including the caster's
-- "nameplateN". The payload has no timing, so poll it (PollCastInfo picks
-- cast vs channel) and cache -- only for a unit we have a plate for, so
-- the table stays bounded to on-screen casters.
local casterGuid = arg3
local plate = casterGuid and plateByGuid[casterGuid]
if plate then
local isChannel = arg8 == 1
local durationMs = isChannel and arg7 or arg6
if durationMs and durationMs > 0 then
local spellId = arg2
local now = GetTime()
castState[casterGuid] = {
spellName = C_Spell.GetSpellName(spellId),
spellID = spellId,
icon = C_Spell.GetSpellTexture(spellId),
startTime = now,
endTime = now + durationMs / 1000,
duration = durationMs / 1000,
isChannel = isChannel,
}
plate.castUpdate = true -- bypass the throttle so the bar shows now
if arg1 and strfind(arg1, "^nameplate") then
local guid = UnitGUID(arg1)
local plate = guid and plateByGuid[guid]
if plate then
castState[guid] = PollCastInfo(arg1)
if castState[guid] then
plate.castUpdate = true -- bypass the throttle so the bar shows now
end
end
end
elseif event == "SPELL_FAILED_OTHER" then
-- nampower: arg1=casterGuid, arg2=spellId. Clear on interrupt/failure.
local casterGuid = arg1
if casterGuid and castState[casterGuid] then
castState[casterGuid] = nil
local plate = plateByGuid[casterGuid]
if plate then plate.castUpdate = true end
elseif event == "UNIT_SPELLCAST_STOP" or event == "UNIT_SPELLCAST_CHANNEL_STOP" then
-- Cast/channel ended (natural, interrupted, or cancelled -- the poll fires
-- STOP for all three). Clear the cached cast and refresh its plate.
if arg1 and strfind(arg1, "^nameplate") then
local guid = UnitGUID(arg1)
if guid and castState[guid] then
castState[guid] = nil
local plate = plateByGuid[guid]
if plate then plate.castUpdate = true end
end
end
elseif event == "UNIT_SPELLCAST_SUCCEEDED" then
-- Active totems (Searing/Magma/Fire Nova) carry no self-aura, so their
-- attack cast is the only icon source. Capture it once per totem, gated on
-- creature type so a normal caster's spell never styles it as a totem.
if arg1 and strfind(arg1, "^nameplate") then
local guid = UnitGUID(arg1)
local plate = guid and plateByGuid[guid]
if plate and not plate.totemIcon and arg3 and CreatureType(plate) == 11 then
local tex = C_Spell.GetSpellTexture(arg3)
if tex then
plate.totemIcon = tex
plate.castUpdate = true -- re-render now so the icon shows
end
end
end
elseif event == "UNIT_AURA" then
@@ -952,7 +989,7 @@ nameplates:RegisterEvent("SPELL_FAILED_OTHER")
-- Use the same castbar texture and color as the unit frame castbar (castbar.lua)
local cbtexture = pfUI.media[C.appearance.castbar.texture]
nameplate.castbar:SetStatusBarTexture(cbtexture or hptexture)
local cbr, cbg, cbb, cba = strsplit(",", C.appearance.castbar.castbarcolor)
local cbr, cbg, cbb, cba = GetStringColor(C.appearance.castbar.castbarcolor)
nameplate.castbar:SetStatusBarColor(cbr, cbg, cbb, cba)
-- reset endTime cache so color/texture refresh takes effect on next cast
nameplate.castbar.lastEndTime = nil
@@ -1002,13 +1039,29 @@ nameplates:RegisterEvent("SPELL_FAILED_OTHER")
local mouseover = plate.cachedGuid and plate.cachedGuid == frameState.mouseoverGuid or nil
local unitstr = target and "target" or mouseover and "mouseover" or plate.cachedGuid or nil
-- target event sometimes fires too quickly, where nameplate identifiers are not
-- yet updated. So while being inside this event, we cannot trust the unitstr.
if event == "PLAYER_TARGET_CHANGED" then unitstr = nil end
-- remove unitstr when it doesn't resolve to this plate's unit (stale istarget,
-- stale mouseover guid or a despawned unit). Must run before the cache fills
-- below -- an unrelated unitstr would poison cache.player/minion with the
-- *other* unit's answer, and the nil-gate would then never recompute it.
if unitstr and UnitName(unitstr) ~= name then unitstr = nil end
-- resolve player vs npc from plate's own unit so libunitscan can't return
-- a player record for an NPC sharing the same name (e.g. Chromie). Stored
-- as true/false/nil so it doubles as the GetUnitInfo hint.
if plate.cache.player == nil and unitstr then
plate.cache.player = UnitIsPlayer(unitstr) and true or false
end
local class, ulevel, elite, player, guild = GetUnitInfo(name, true, plate.cache.player)
if plate.cache.minion == nil and unitstr then
plate.cache.minion = UnitIsMinion(unitstr) and true or false
end
local class, ulevel, elite, player, guild
if not plate.cache.minion then
class, ulevel, elite, player, guild = GetUnitInfo(name, true, plate.cache.player)
end
if plate.cache.player ~= nil then player = plate.cache.player or nil end
-- Use database level ONLY if current level is ?? (fixes ?? after reload, but doesn't override visible levels)
@@ -1028,18 +1081,11 @@ nameplates:RegisterEvent("SPELL_FAILED_OTHER")
if player and unittype == "ENEMY_NPC" then unittype = "ENEMY_PLAYER" end
if player and unittype == "FRIENDLY_NPC" then unittype = "FRIENDLY_PLAYER" end
elite = plate.original.levelicon:IsShown() and not player and "boss" or elite
if not class then plate.wait_for_scan = true end
if not class and not plate.cache.minion then plate.wait_for_scan = true end
-- skip data updates on invisible frames
if not visible then return end
-- target event sometimes fires too quickly, where nameplate identifiers are not
-- yet updated. So while being inside this event, we cannot trust the unitstr.
if event == "PLAYER_TARGET_CHANGED" then unitstr = nil end
-- remove unitstr on unit name mismatch
if unitstr and UnitName(unitstr) ~= name then unitstr = nil end
-- always make sure to keep plate visible
plate:Show()
@@ -1070,11 +1116,11 @@ nameplates:RegisterEvent("SPELL_FAILED_OTHER")
end
-- hide frames according to the configuration
local TotemIcon = TotemPlate(name)
local TotemIcon = TotemPlate(plate)
if TotemIcon then
-- create totem icon
plate.totem.icon:SetTexture("Interface\\Icons\\" .. TotemIcon)
-- icon resolved from the totem's aura / attack cast (already a full path)
plate.totem.icon:SetTexture(TotemIcon)
plate.glow:Hide()
plate.level:Hide()
@@ -1082,7 +1128,7 @@ nameplates:RegisterEvent("SPELL_FAILED_OTHER")
plate.health:Hide()
plate.guild:Hide()
plate.totem:Show()
elseif HidePlate(unittype, name, (hpmax-hp == hpmin), target) then
elseif HidePlate(unittype, (hpmax-hp == hpmin), target, plate) then
plate.level:SetPoint("RIGHT", plate.name, "LEFT", -3, 0)
plate.name:SetParent(plate)
plate.guild:SetPoint("BOTTOM", plate.name, "BOTTOM", -2, -(font_size + 2))
@@ -1114,18 +1160,27 @@ nameplates:RegisterEvent("SPELL_FAILED_OTHER")
if plate.cache.level ~= level or plate.cache.elite ~= elite then
plate.cache.level = level
plate.cache.elite = elite
plate.level:SetText(string.format("%s%s", level, (elitestrings[elite] or "")))
plate.level:SetFormattedText("%s%s", level, (elitestrings[elite] or ""))
end
-- Set level color from GetDifficultyColor when using DB level
-- Set level color from GetDifficultyColor when using DB level.
-- No brightening here: adding a flat offset to all three channels
-- desaturates every tier toward white and collapses the boundaries
-- (orange reads as yellow, red reads as orange). Use Blizzard's values.
-- Clearing cache.levelcolor forces the sync block in OnUpdate to
-- re-colour once the ?? resolves and it takes ownership again.
if levelFromDB and type(level) == "number" then
local color = GetDifficultyColor(level)
plate.level:SetTextColor(color.r + 0.3, color.g + 0.3, color.b + 0.3, 1)
plate.level:SetTextColor(color.r, color.g, color.b, 1)
plate.cache.levelcolor = nil
end
-- remember who owns the level colour so the two writers cannot fight
plate.cache.levelfromdb = levelFromDB or nil
if guild and C.nameplates.showguildname == "1" then
plate.guild:SetText(guild)
if UnitIsInMyGuild(plate.unit) then
if guild == myGuild then
plate.guild:SetTextColor(0, 0.9, 0, 1)
else
plate.guild:SetTextColor(0.8, 0.8, 0.8, 1)
@@ -1166,21 +1221,21 @@ nameplates:RegisterEvent("SPELL_FAILED_OTHER")
local hasdata = ( rhp and rhpmax ) or estimated or hpmax > 100 or (round(hpmax/100*hp) ~= hp)
if setting == "curperc" and hasdata and rhp then
plate.health.text:SetText(string.format("%s | %s%%", Abbreviate(rhp), ceil(hp/hpmax*100)))
plate.health.text:SetFormattedText("%s | %s%%", Abbreviate(rhp), ceil(hp/hpmax*100))
elseif setting == "cur" and hasdata and rhp then
plate.health.text:SetText(string.format("%s", Abbreviate(rhp)))
plate.health.text:SetFormattedText("%s", Abbreviate(rhp))
elseif setting == "curmax" and hasdata and rhp then
plate.health.text:SetText(string.format("%s - %s", Abbreviate(rhp), Abbreviate(rhpmax)))
plate.health.text:SetFormattedText("%s - %s", Abbreviate(rhp), Abbreviate(rhpmax))
elseif setting == "curmaxs" and hasdata and rhp then
plate.health.text:SetText(string.format("%s / %s", Abbreviate(rhp), Abbreviate(rhpmax)))
plate.health.text:SetFormattedText("%s / %s", Abbreviate(rhp), Abbreviate(rhpmax))
elseif setting == "curmaxperc" and hasdata and rhp then
plate.health.text:SetText(string.format("%s - %s | %s%%", Abbreviate(rhp), Abbreviate(rhpmax), ceil(hp/hpmax*100)))
plate.health.text:SetFormattedText("%s - %s | %s%%", Abbreviate(rhp), Abbreviate(rhpmax), ceil(hp/hpmax*100))
elseif setting == "curmaxpercs" and hasdata and rhp then
plate.health.text:SetText(string.format("%s / %s | %s%%", Abbreviate(rhp), Abbreviate(rhpmax), ceil(hp/hpmax*100)))
plate.health.text:SetFormattedText("%s / %s | %s%%", Abbreviate(rhp), Abbreviate(rhpmax), ceil(hp/hpmax*100))
elseif setting == "deficit" and rhp then
plate.health.text:SetText(string.format("-%s" .. (hasdata and "" or "%%"), Abbreviate(rhpmax - rhp)))
plate.health.text:SetFormattedText("-%s" .. (hasdata and "" or "%%"), Abbreviate(rhpmax - rhp))
else -- "percent" as fallback
plate.health.text:SetText(string.format("%s%%", ceil(hp/hpmax*100)))
plate.health.text:SetFormattedText("%s%%", ceil(hp/hpmax*100))
end
else
plate.health.text:SetText()
@@ -1189,12 +1244,11 @@ nameplates:RegisterEvent("SPELL_FAILED_OTHER")
local r, g, b, a = unpack(unitcolors[unittype])
if class then
if unittype == "ENEMY_PLAYER" and C.nameplates["enemyclassc"] == "1" then
r, g, b, a = PFUI_CLASS_COLORS[class]:GetRGBA()
elseif unittype == "FRIENDLY_PLAYER" and C.nameplates["friendclassc"] == "1" then
r, g, b, a = PFUI_CLASS_COLORS[class]:GetRGBA()
end
if class and (
(unittype == "ENEMY_PLAYER" and C.nameplates["enemyclassc"] == "1") or
(unittype == "FRIENDLY_PLAYER" and C.nameplates["friendclassc"] == "1")
) then
r, g, b, a = PFUI_CLASS_COLORS[class]:GetRGBA()
end
if unitstr and UnitIsTapped(unitstr) and not UnitIsTappedByPlayer(unitstr) then
@@ -1215,15 +1269,43 @@ nameplates:RegisterEvent("SPELL_FAILED_OTHER")
plate.cache.r, plate.cache.g, plate.cache.b = r, g, b
end
if r + g + b ~= plate.cache.namecolor and unittype == "FRIENDLY_PLAYER" and C.nameplates["friendclassnamec"] == "1" and class and PFUI_CLASS_COLORS[class] then
plate.name:SetTextColor(r, g, b, a)
plate.cache.namecolor = r + g + b
-- Friendly player names take this colour when friendclassnamec is on.
-- Ownership is recorded so the OnUpdate sync below stands down rather than
-- racing us: both writers used to share cache.namecolor despite storing
-- unrelated quantities (this colour vs Blizzard's name FontString), so
-- either could suppress the other -- and since nameplate.cache survives
-- pool reuse, a recycled plate could keep the previous unit's name colour.
local ownname = unittype == "FRIENDLY_PLAYER" and C.nameplates["friendclassnamec"] == "1"
and class and true or nil
if plate.cache.ownname ~= ownname then
plate.cache.ownname = ownname
-- ownership flipped: whichever writer is now in charge must re-assert
plate.cache.namecolor = nil
plate.cache.ownnamecolor = nil
end
-- read the class colour directly rather than reusing the bar's r,g,b: the
-- bar only carries a class colour when friendclassc happens to be on, and
-- it also picks up the tapped-grey and barcombatstate overrides, neither of
-- which belongs on the name.
if ownname then
local cr, cg, cb, ca = PFUI_CLASS_COLORS[class]:GetRGBA()
if cr + cg + cb ~= plate.cache.ownnamecolor then
plate.cache.ownnamecolor = cr + cg + cb
plate.name:SetTextColor(cr, cg, cb, ca)
end
end
-- update combopoints
for i=1, 5 do plate.combopoints[i]:Hide() end
if target and C.nameplates.cpdisplay == "1" then
for i=1, GetComboPoints("target") do plate.combopoints[i]:Show() end
local cp = GetComboPoints("target")
if plate.cpShown ~= cp then
for i=1, 5 do plate.combopoints[i]:SetShown(i <= cp) end
plate.cpShown = cp
end
elseif plate.cpShown then
for i=1, 5 do plate.combopoints[i]:Hide() end
plate.cpShown = nil
end
-- update debuffs
@@ -1240,19 +1322,22 @@ nameplates:RegisterEvent("SPELL_FAILED_OTHER")
for i = 1, 16 do debuffDisplayBuf[i].effect = nil end
if unitstr then
local filter = cfg.owndebuffs and "HARMFUL|PLAYER" or "HARMFUL"
local auras = C_UnitAuras.GetUnitAuras(unitstr, filter)
local now = GetTime()
for _, aura in ipairs(auras) do
if debuffCount >= 16 then break end
-- positional UnitAura writes straight into the reusable buffer, so the
-- per-plate scan allocates nothing (no per-aura table, no result array)
local i = 1
while debuffCount < 16 do
local aname, icon, count, dispelType, duration, expirationTime = C_UnitAuras.UnitAura(unitstr, i, filter)
if not aname then break end
debuffCount = debuffCount + 1
local timeleft = (aura.expirationTime and aura.expirationTime > 0) and (aura.expirationTime - now) or nil
local b = debuffDisplayBuf[debuffCount]
b.effect = aura.name
b.texture = aura.icon
b.stacks = aura.applications
b.dtype = aura.dispelName
b.duration = aura.duration
b.timeleft = timeleft
b.effect = aname
b.texture = icon
b.stacks = count
b.dtype = dispelType
b.duration = duration
b.timeleft = (expirationTime and expirationTime > 0) and (expirationTime - now) or nil
i = i + 1
end
end
for i = 1, 16 do
@@ -1475,38 +1560,46 @@ nameplates:RegisterEvent("SPELL_FAILED_OTHER")
-- otherwise an NPC sharing a name with a known player flips wait_for_scan
-- off here, then OnDataChanged re-sets it, every frame until the mob scan
-- lands.
if nameplate.wait_for_scan and GetUnitInfo(name, true, nameplate.cache.player) then
if nameplate.wait_for_scan and not nameplate.cache.minion and GetUnitInfo(name, true, nameplate.cache.player) then
nameplate.wait_for_scan = nil
update = true
end
-- trigger update when name color changed (includes combat state check)
local r, g, b = original.name:GetTextColor()
local inCombatWithPlayer = cfg.namefightcolor and UnitAffectingCombat(nameplate.unit) and UnitAffectingCombat("player")
if r + g + b ~= nameplate.cache.namecolor or (cfg.namefightcolor and nameplate.cache.inCombat ~= inCombatWithPlayer) then
nameplate.cache.namecolor = r + g + b
nameplate.cache.inCombat = inCombatWithPlayer
-- trigger update when name color changed (includes combat state check).
-- Skipped once OnDataChanged owns the name colour (class-coloured friendly
-- players), so the two writers cannot overwrite each other.
if not nameplate.cache.ownname then
local r, g, b = original.name:GetTextColor()
local inCombatWithPlayer = cfg.namefightcolor and UnitAffectingCombat(nameplate.unit) and UnitAffectingCombat("player")
if cfg.namefightcolor then
if (r > .9 and g < .2 and b < .2) or inCombatWithPlayer then
nameplate.name:SetTextColor(1,0.4,0.2,1)
if r + g + b ~= nameplate.cache.namecolor or (cfg.namefightcolor and nameplate.cache.inCombat ~= inCombatWithPlayer) then
nameplate.cache.namecolor = r + g + b
nameplate.cache.inCombat = inCombatWithPlayer
if cfg.namefightcolor then
if (r > .9 and g < .2 and b < .2) or inCombatWithPlayer then
nameplate.name:SetTextColor(1,0.4,0.2,1)
else
nameplate.name:SetTextColor(r,g,b,1)
end
else
nameplate.name:SetTextColor(r,g,b,1)
nameplate.name:SetTextColor(1,1,1,1)
end
else
nameplate.name:SetTextColor(1,1,1,1)
update = true
end
update = true
end
-- trigger update when level color changed
local r, g, b = original.level:GetTextColor()
r, g, b = r + .3, g + .3, b + .3
if r + g + b ~= nameplate.cache.levelcolor then
nameplate.cache.levelcolor = r + g + b
nameplate.level:SetTextColor(r,g,b,1)
update = true
-- trigger update when level color changed. Skipped while the level came
-- from the database (?? plates), where OnDataChanged owns the colour --
-- otherwise both writers race and cache.levelcolor goes stale, which
-- leaves a recycled plate wearing the previous unit's colour.
if not nameplate.cache.levelfromdb then
local r, g, b = original.level:GetTextColor()
if r + g + b ~= nameplate.cache.levelcolor then
nameplate.cache.levelcolor = r + g + b
nameplate.level:SetTextColor(r,g,b,1)
update = true
end
end
-- use timer based updates
@@ -1616,7 +1709,7 @@ nameplates:RegisterEvent("SPELL_FAILED_OTHER")
rounded = floor(remaining * 100)
if castbar.lastTextTick ~= rounded then
castbar.lastTextTick = rounded
castbar.text:SetText(string.format("%.2f", remaining))
castbar.text:SetFormattedText("%.2f", remaining)
end
end
end
+31 -54
View File
@@ -37,18 +37,12 @@ pfUI:RegisterModule("panel", function()
return
end
if arg1 == "LeftButton" then
if widget.timerFrame:IsShown() then
widget.timerFrame:Hide()
else
widget.timerFrame:Show()
end
widget.timerFrame:SetShown(not widget.timerFrame:IsShown())
elseif arg1 == "RightButton" then
widget.timerFrame.Snapshot = GetTime()
end
end
widget:SetScript("OnUpdate",function()
if ( this.tick or 1) > GetTime() then return else this.tick = GetTime() + 1 end
C_Timer.NewTicker(1, function()
local secondsenabled = C.panel.seconds == "1"
local fmt
if C.global.twentyfour == "0" then
@@ -62,8 +56,7 @@ pfUI:RegisterModule("panel", function()
widget.timerFrame = CreateFrame("Frame", "pfUITimer", UIParent)
widget.timerFrame:Hide()
widget.timerFrame:SetWidth(120)
widget.timerFrame:SetHeight(35)
widget.timerFrame:SetSize(120, 35)
widget.timerFrame:SetPoint("TOP", 0, -100)
UpdateMovable(widget.timerFrame)
@@ -103,11 +96,9 @@ pfUI:RegisterModule("panel", function()
pfUI.panel:OutputPanel("combat", T["Combat"] .. ": " .. NOT_APPLICABLE)
end
end)
widget.combat:SetScript("OnUpdate", function()
if not this.tick then this.tick = GetTime() end
if GetTime() <= this.tick + 1 then return else this.tick = GetTime() end
if this.combat then
pfUI.panel:OutputPanel("combat", "|cffffaaaa" .. SecondsToTime(ceil(GetTime() - this.combat)))
C_Timer.NewTicker(1, function()
if widget.combat.combat then
pfUI.panel:OutputPanel("combat", "|cffffaaaa" .. SecondsToTime(ceil(GetTime() - widget.combat.combat)))
end
end)
end
@@ -153,9 +144,7 @@ pfUI:RegisterModule("panel", function()
pfUI.addons:Show()
end
end
widget:SetScript("OnUpdate",function()
if ( this.tick or 1) > GetTime() then return else this.tick = GetTime() + 1 end
C_Timer.NewTicker(1, function()
fps = floor(GetFramerate())
_, _, lag = GetNetStats()
@@ -307,18 +296,18 @@ pfUI:RegisterModule("panel", function()
local playerzone = GetRealZoneText()
for friendIndex=1, all do
local friend_name, friend_level, friend_class, friend_area, friend_connected = GetFriendInfo(friendIndex)
if friend_connected and friend_class and friend_level then
local info = C_FriendList.GetFriendInfoByIndex(friendIndex)
if info and info.connected and info.classFilename and info.level then
if not init then
GameTooltip_SetDefaultAnchor(GameTooltip, this)
GameTooltip:ClearLines()
GameTooltip:AddLine("|cff555555" .. T["Friends Online"])
init = true
end
local ccolor = PFUI_CLASS_COLORS[L["class"][friend_class]] or { 1, 1, 1 }
local lcolor = GetDifficultyColor(tonumber(friend_level)) or { 1, 1, 1 }
local zcolor = friend_area == playerzone and "|cff33ffcc" or "|cffcccccc"
GameTooltip:AddDoubleLine(rgbhex(ccolor) .. friend_name .. rgbhex(lcolor) .. " [" .. friend_level .. "]", zcolor .. friend_area)
local ccolor = PFUI_CLASS_COLORS[info.classFilename]
local lcolor = GetDifficultyColor(tonumber(info.level)) or { 1, 1, 1 }
local zcolor = info.area == playerzone and "|cff33ffcc" or "|cffcccccc"
GameTooltip:AddDoubleLine(ccolor:WrapTextInColorCode(info.name) .. rgbhex(lcolor) .. " [" .. info.level .. "]", zcolor .. info.area)
end
end
@@ -326,15 +315,7 @@ pfUI:RegisterModule("panel", function()
end
widget.Click = function() ToggleFriendsFrame(1) end
widget:SetScript("OnEvent", function()
local online = 0
local all = GetNumFriends()
for friendIndex=1, all do
local friend_name, friend_level, friend_class, friend_area, friend_connected = GetFriendInfo(friendIndex)
if ( friend_connected ) then
online = online + 1
end
end
local online = C_FriendList.GetNumOnlineFriends()
pfUI.panel:OutputPanel("friends", FRIENDS .. ": " .. online, widget.Tooltip, widget.Click)
end)
end
@@ -346,7 +327,7 @@ pfUI:RegisterModule("panel", function()
widget:RegisterEvent("PLAYER_GUILD_UPDATE")
widget.Tooltip = function()
-- skip without guild
if not GetGuildInfo("player") then return end
if not IsInGuild() then return end
local raidparty = {}
for i=1,4 do -- detect people in group
@@ -398,7 +379,7 @@ pfUI:RegisterModule("panel", function()
end
widget.Click = function() ToggleFriendsFrame(3) end
widget:SetScript("OnEvent", function()
if GetGuildInfo("player") then
if IsInGuild() then
local count = 0
for i = 1, GetNumGuildMembers() do
local _, _, _, _, _, _, _, _, online = GetGuildRosterInfo(i)
@@ -411,9 +392,8 @@ pfUI:RegisterModule("panel", function()
end
end)
widget:SetScript("OnUpdate",function()
if ( this.tick or 60) > GetTime() then return else this.tick = GetTime() + 60 end
if GetGuildInfo("player") then GuildRoster() end
C_Timer.NewTicker(60, function()
if IsInGuild() then GuildRoster() end
end)
end
@@ -438,8 +418,13 @@ pfUI:RegisterModule("panel", function()
local repPercent = floor(cur / max * 100)
if repPercent < 100 then
local _, _, _, hex = GetColorGradient(repPercent/100)
local link = GetInventoryItemLink("player", id)
local texture = GetInventoryItemTexture("player", id)
if texture then
link = CreateSimpleTextureMarkup(texture, 24) .. " " .. link
end
itemLines[table.getn(itemLines)+1] = {
GetInventoryItemLink("player", id),
link,
string.format("%s%s%%|r", hex, repPercent)
}
end
@@ -448,8 +433,7 @@ pfUI:RegisterModule("panel", function()
if totalRep > 0 then
GameTooltip:ClearLines()
GameTooltip_SetDefaultAnchor(GameTooltip, this)
GameTooltip:SetText("|cff555555"..(string.gsub(REPAIR_COST,":","")).."|r")
SetTooltipMoney(GameTooltip, totalRep)
GameTooltip:AddLine(REPAIR_COST.." " .. CreateGoldString(totalRep), 0.3333, 0.3333, 0.3333)
for _,line in ipairs(itemLines) do
GameTooltip:AddDoubleLine(line[1],line[2])
end
@@ -492,11 +476,7 @@ pfUI:RegisterModule("panel", function()
GameTooltip:Show()
end
widget.Click = function()
if WorldMapFrame:IsShown() then
WorldMapFrame:Hide()
else
WorldMapFrame:Show()
end
WorldMapFrame:SetShown(not WorldMapFrame:IsShown())
end
widget:SetScript("OnEvent", function()
pfUI.panel:OutputPanel("zone", GetMinimapZoneText(), widget.Tooltip, widget.Click)
@@ -645,8 +625,7 @@ pfUI:RegisterModule("panel", function()
local frame = CreateFrame("Button", nil, parent)
frame:SetFrameLevel(0)
frame:ClearAllPoints()
frame:SetWidth(width)
frame:SetHeight(parent:GetHeight())
frame:SetSize(width, parent:GetHeight())
frame:SetPoint(location, 0, 0)
frame.text = frame:CreateFontString("Status", "LOW", "GameFontNormal")
frame.text:ClearAllPoints()
@@ -687,8 +666,7 @@ pfUI:RegisterModule("panel", function()
local imgstring = "img:" .. leftright
parent.texture:SetTexture(pfUI.media[imgstring])
parent.texture:SetPoint("CENTER", 0, 0)
parent.texture:SetWidth(8)
parent.texture:SetHeight(8)
parent.texture:SetSize(8, 8)
parent.texture:SetVertexColor(.25,.25,.25,1)
return parent.texture
end
@@ -710,7 +688,7 @@ pfUI:RegisterModule("panel", function()
UpdateMovable(pfUI.panel.left)
pfUI.panel.left.hide:SetScript("OnClick", function()
if pfUI.chat.left:IsShown() then pfUI.chat.left:Hide() else pfUI.chat.left:Show() end
pfUI.chat.left:SetShown(not pfUI.chat.left:IsShown())
end)
if not pfUI.chat then pfUI.panel.left.hide:Hide() end
@@ -747,7 +725,7 @@ pfUI:RegisterModule("panel", function()
UpdateMovable(pfUI.panel.right)
pfUI.panel.right.hide:SetScript("OnClick", function()
if pfUI.chat.right:IsShown() then pfUI.chat.right:Hide() else pfUI.chat.right:Show() end
pfUI.chat.right:SetShown(not pfUI.chat.right:IsShown())
end)
if not pfUI.chat then pfUI.panel.right.hide:Hide() end
@@ -796,8 +774,7 @@ pfUI:RegisterModule("panel", function()
pfUI.panel.microbutton = CreateFrame("Frame", "pfPanelMicroButton", UIParent)
pfUI.panel.microbutton:SetPoint("TOP", pfUI.panel.minimap, "BOTTOM", 0, -2*default_border)
UpdateMovable(pfUI.panel.microbutton)
pfUI.panel.microbutton:SetHeight(23)
pfUI.panel.microbutton:SetWidth(145)
pfUI.panel.microbutton:SetSize(145, 23)
pfUI.panel.microbutton:SetFrameStrata("MEDIUM")
for i=1,table.getn(MICRO_BUTTONS) do
+28 -30
View File
@@ -39,17 +39,20 @@ pfUI:RegisterModule("player", function ()
playerFrame.isSpellCaster = myclass ~= "WARRIOR" and myclass ~= "ROGUE" and myclass ~= "HUNTER"
-- Convert "r,g,b,a" config color string to a 6-char hex string, or nil if unset
local function cfgColorToHex(colorStr)
if not colorStr or colorStr == "" then return nil end
local r, g, b = strsplit(",", colorStr)
r, g, b = tonumber(r), tonumber(g), tonumber(b)
if not r or not g or not b then return nil end
return string.format("%02X%02X%02X", r * 255, g * 255, b * 255)
end
local cfgColorToHexTable = setmetatable({}, {
__index = function (t, colorStr)
if not colorStr or colorStr == "" then return nil end
local r, g, b = GetStringColor(colorStr)
if not r or not g or not b then return nil end
local hex = C_ColorUtil.GenerateTextColorCode({ r=r, g = g, b=b})
rawset(t, colorStr, hex)
return hex
end
})
-- SP school colors indexed by GetSpellBonusDamage's 1-based school order
-- (1=phys, 2=holy, 3=fire, 4=nature, 5=frost, 6=shadow, 7=arcane)
local spColors = { "FFFFFF", "FFFF80", "FF8000", "4DFF4D", "80FFFF", "9482C9", "FFFFFF" }
local spColors = { "FFFFFFFF", "FFFFFF80", "FFFF8000", "FF4DFF4D", "FF80FFFF", "FF9482C9", "FFFFFFFF" }
-- Default SP school per class used as tiebreaker when multiple schools are equal
local spDefaultSchool = {
@@ -68,42 +71,37 @@ pfUI:RegisterModule("player", function ()
-- from UNIT_MOD_CAST_SPEED). Talent/spell-specific cast-time reductions
-- show up in the actual cast bar via C_Spell.UnitCastingInfo; folding them
-- in here too was mixing two different concepts into one number.
local showHaste = cfg.display_haste == "1"
local showSP = cfg.display_spellpower == "1"
local isSpellCaster = playerFrame.isSpellCaster
if (not showHaste or not isSpellCaster) and not showSP then
local showHaste = isSpellCaster and cfg.display_haste == "1"
local showSP = isSpellCaster and cfg.display_spellpower == "1"
if not showHaste and not showSP then
playerFrame.infoTopCenterText:SetText("")
return
end
local haste = UnitSpellHaste("player")
local text = ""
local parts = {}
if showHaste and isSpellCaster and haste then
local hasteHex = cfgColorToHex(cfg.display_haste_color) or "FFFFFF"
text = string.format("|cff%s%.1f%%|r", hasteHex, haste)
if showHaste then
local hex = cfgColorToHexTable[cfg.display_haste_color] or "FFFFFFFF"
table.insert(parts, string.format("|c%s%.1f%%|r", hex, UnitSpellHaste("player")))
end
if showSP and isSpellCaster then
local defSchool = spDefaultSchool[myclass] or 2
local maxSP = GetSpellBonusDamage(defSchool) or 0
local maxColor = spColors[defSchool]
for i = 2, 7 do -- skip physical (1); default school seeds the tiebreak
if showSP then
local school = spDefaultSchool[myclass] or 2
local maxSP = GetSpellBonusDamage(school) or 0
for i = 2, 7 do -- skip physical (1)
local v = GetSpellBonusDamage(i) or 0
if v > maxSP then
maxSP = v
maxColor = spColors[i]
end
if v > maxSP then maxSP, school = v, i end
end
if maxSP > 0 then
local spHex = (cfg.display_sp_color_override == "1" and cfgColorToHex(cfg.display_sp_color)) or maxColor
if text ~= "" then text = text .. " " end
text = text .. string.format("|cff%s+%d SP|r", spHex, maxSP)
local hex = (cfg.display_sp_color_override == "1" and cfgColorToHexTable[cfg.display_sp_color]) or spColors[school]
table.insert(parts, string.format("|c%s+%d SP|r", hex, maxSP))
end
end
playerFrame.infoTopCenterText:SetText(text)
playerFrame.infoTopCenterText:SetText(table.concat(parts, " "))
end
-- Keep a reference to the generic UF UpdateConfig so we can chain it
+27 -3
View File
@@ -4,7 +4,9 @@ pfUI:RegisterModule("raid", function ()
-- tell RaidFrame.lua pfUI replaces party frames
HookAddonOrVariable("Blizzard_RaidUI", function()
GROUP_REPLACE_PARTY = "1"
-- must reach _G: RaidFrame.lua reads this global, and pfUI.env has
-- __index but no __newindex, so a bare assignment stays in the sandbox
_G.GROUP_REPLACE_PARTY = "1"
end)
pfUI.uf.raid = CreateFrame("Frame", "pfRaidUpdater", UIParent)
@@ -123,6 +125,7 @@ pfUI:RegisterModule("raid", function ()
local grid = self.petgrid
local function place(pet, cell, id)
pet.label = "raidpet"
pet.id = id
local r, g = SlotToCoord(cell, grid.fill, grid.x, grid.y)
pet:ClearAllPoints()
@@ -138,6 +141,22 @@ pfUI:RegisterModule("raid", function ()
return
end
-- Party shown as a raid grid: mirror slots 1..5 (player + party members)
-- into fixed cells. UpdateVisibility maps each slot to pet / partypet<N>.
if not IsInRaid() and IsInGroup() and C.unitframes.raidforgroup == "1" then
for id = 1, maxraid do
if self.pets[id] then
if id <= 5 then
place(self.pets[id], id, id)
else
self.pets[id].id = 0
self.pets[id]:Hide()
end
end
end
return
end
if C.unitframes.raidpet.collapse == "1" then
-- Pack the pets that exist into the leading cells, no gaps.
local k = 0
@@ -211,8 +230,13 @@ pfUI:RegisterModule("raid", function ()
this.tick = GetTime() + 1.0
this.pendingUpdate = nil
-- don't proceed without raid
if not IsInRaid() then return end
-- Without a raid there is nothing to sort, but a party shown as a raid
-- grid still needs its pet frames placed and mapped to the party pets.
if not IsInRaid() then
this:LayoutPets()
this:Hide()
return
end
-- clear all existing frames
for i=1, maxraid do SetRaidIndex(pfUI.uf.raid[i], 0) end
+8 -3
View File
@@ -7,8 +7,12 @@ pfUI:RegisterModule("roll", function ()
local LOOT_ROLL_NEED = string.gsub(LOOT_ROLL_NEED, "%%s|Hitem:%%d:%%d:%%d:%%d|h%[%%s%]|h%%s", "%%s")
local LOOT_ROLL_PASSED = string.gsub(LOOT_ROLL_PASSED, "%%s|Hitem:%%d:%%d:%%d:%%d|h%[%%s%]|h%%s", "%%s")
-- try to detect the everyone string
local _, _, everyone, _ = strfind(LOOT_ROLL_ALL_PASSED, LOOT_ROLL_PASSED)
-- detect the "everyone passed" subject exactly as the loot scanner will capture
-- it: feed a dummy item into LOOT_ROLL_ALL_PASSED and run the same LOOT_ROLL_PASSED
-- match. (The old strfind had no captures, so `everyone` was always nil and
-- "Everyone has passed on: X" got counted as a fake roller.)
local everyoneSample = string.gsub(LOOT_ROLL_ALL_PASSED, "%%s", "x")
local everyone = cmatch(everyoneSample, LOOT_ROLL_PASSED)
pfUI.roll.blacklist = { YOU, everyone }
pfUI.roll.cache = {}
@@ -43,6 +47,7 @@ pfUI:RegisterModule("roll", function ()
local _, _, itemLink = string.find(hyperlink, "(item:%d+:%d+:%d+:%d+)")
local itemName = C_Item.GetItemInfo(itemLink)
if not itemName then return end -- uncached item: avoid cache[nil] "table index is nil"
-- delete obsolete tables
if pfUI.roll.cache[itemName] and pfUI.roll.cache[itemName]["TIMESTAMP"] < GetTime() - 60 then
@@ -234,7 +239,7 @@ pfUI:RegisterModule("roll", function ()
f.time.bar:SetAllPoints(f.time)
f.time.bar:SetStatusBarTexture(pfUI.media["img:bar"])
f.time.bar:SetMinMaxValues(0, 100)
local r, g, b, a = strsplit(",", C.appearance.border.color)
local r, g, b, a = GetStringColor(C.appearance.border.color)
f.time.bar:SetStatusBarColor(r, g, b)
f.time.bar:SetValue(20)
f.time.bar:SetScript("OnUpdate", function()
+4 -4
View File
@@ -119,7 +119,7 @@ pfUI:RegisterModule("screenshot", function ()
end
function pfUI.screenshot:CHAT_MSG_SYSTEM()
local _,_, standing, rep = string.find(arg1, FACTION_STANDING_CHANGEDregex)
local standing, rep = string.match(arg1, FACTION_STANDING_CHANGEDregex)
if standing and rep then
local dt = date("%a, %b %d, %Y %X")
local loc = string.format("%s - %s",GetRealZoneText(),GetSubZoneText())
@@ -150,13 +150,13 @@ pfUI:RegisterModule("screenshot", function ()
end
function pfUI.screenshot:CHAT_MSG_LOOT()
local _,_, item, amount = string.find(arg1, LOOT_ITEM_SELF_MULTIPLEregex)
local item, amount = string.match(arg1, LOOT_ITEM_SELF_MULTIPLEregex)
if amount then -- ignore stacks
return
else
_,_, item = string.find(arg1, LOOT_ITEM_SELFregex)
item = string.match(arg1, LOOT_ITEM_SELFregex)
if item then
local _, _, itemColor, itemString, itemName = string.find(item, "^(|c%x+)|H(.+)|h(%[.+%])")
local itemColor, itemString, itemName = string.match(item, "^(|c%x+)|H(.+)|h(%[.+%])")
local quality = color2quality[itemColor]
if quality and quality >= tonumber(C.screenshot.loot) then
local dt = date("%a, %b %d, %Y %X")
+70 -72
View File
@@ -4,7 +4,7 @@ pfUI:RegisterModule("socialmod", function ()
pfUI.socialmod:RegisterEvent("CHAT_MSG_SYSTEM")
pfUI.socialmod:SetScript("OnEvent", function()
local name = cmatch(arg1, _G.ERR_FRIEND_ONLINE_SS)
name = cmatch(arg1, _G.ERR_FRIEND_OFFLINE_S)
name = name or cmatch(arg1, _G.ERR_FRIEND_OFFLINE_S)
if name and playerdb[name] and playerdb[name].cname then
playerdb[name].lastseen = date("%a %d-%b-%Y")
end
@@ -77,20 +77,19 @@ pfUI:RegisterModule("socialmod", function ()
local off = FauxScrollFrame_GetOffset(FriendsFrameFriendsScrollFrame)
for i=1, FRIENDS_TO_DISPLAY do
local name, level, class, zone, connected, status = GetFriendInfo(off + i)
if not name or name == _G.UNKNOWN then break end
local info = C_FriendList.GetFriendInfoByIndex(off + i)
if not info or not info.name or info.name == _G.UNKNOWN then break end
local name = info.name
local friendName = _G["FriendsFrameFriendButton"..i.."ButtonTextName"]
local friendLoc = _G["FriendsFrameFriendButton"..i..FRIENDS_NAME_LOCATION]
local friendInfo = _G["FriendsFrameFriendButton"..i.."ButtonTextInfo"]
local caption = friendName or friendLoc
if connected then
if not class or class == _G.UNKNOWN then break end
local ccolor = PFUI_CLASS_COLORS[L["class"][class]] or { 1, 1, 1 }
local lcolor = GetDifficultyColor(tonumber(level)) or { 1, 1, 1 }
zone = ( zone == playerzone and "|cffffffff" or "|cffcccccc" ) .. zone .. "|r"
local cname = rgbhex(ccolor) .. name .. "|r"
if info.connected then
local ccolor = PFUI_CLASS_COLORS[info.classFilename]
local status = info.afk and CHAT_FLAG_AFK or info.dnd and CHAT_FLAG_DND or ""
local zone = ( info.area == playerzone and "|cffffffff" or "|cffcccccc" ) .. info.area .. "|r"
local cname = ccolor:WrapTextInColorCode(name)
if playerdb[name] then
playerdb[name].lastseen = date("%a %d-%b-%Y")
playerdb[name].cname = cname
@@ -98,20 +97,20 @@ pfUI:RegisterModule("socialmod", function ()
if friendName then
friendName:SetText(cname)
friendLoc:SetText(format(TEXT(FRIENDS_LIST_TEMPLATE), zone, status))
friendLoc:SetFormattedText(TEXT(FRIENDS_LIST_TEMPLATE), zone, status)
else
friendLoc:SetText(format(TEXT(FRIENDS_LIST_TEMPLATE), cname, zone, status))
friendLoc:SetFormattedText(TEXT(FRIENDS_LIST_TEMPLATE), cname, zone, status)
end
friendInfo:SetText(format(TEXT(FRIENDS_LEVEL_TEMPLATE), level, class))
friendInfo:SetFormattedText(TEXT(FRIENDS_LEVEL_TEMPLATE), info.level, info.className)
caption:SetVertexColor(1,1,1,.9)
friendInfo:SetVertexColor(1,1,1,.9)
else
if playerdb[name] and playerdb[name].cname and playerdb[name].level and playerdb[name].lastseen then
caption:SetText(format(TEXT(FRIENDS_LIST_OFFLINE_TEMPLATE), playerdb[name].cname))
friendInfo:SetText(format(TEXT(FRIENDS_LEVEL_TEMPLATE), playerdb[name].level, playerdb[name].lastseen))
caption:SetFormattedText(TEXT(FRIENDS_LIST_OFFLINE_TEMPLATE), playerdb[name].cname)
friendInfo:SetFormattedText(TEXT(FRIENDS_LEVEL_TEMPLATE), playerdb[name].level, playerdb[name].lastseen)
else
caption:SetText(format(TEXT(FRIENDS_LIST_OFFLINE_TEMPLATE), name.."|r"))
caption:SetFormattedText(TEXT(FRIENDS_LIST_OFFLINE_TEMPLATE), name.."|r")
friendInfo:SetText(TEXT(UNKNOWN))
end
@@ -124,74 +123,73 @@ pfUI:RegisterModule("socialmod", function ()
do -- add colors to who list
hooksecurefunc("WhoList_Update", function()
local num, max = GetNumWhoResults()
local num, max = C_FriendList.GetNumWhoResults()
local off = FauxScrollFrame_GetOffset(WhoListScrollFrame)
local playerzone = GetRealZoneText()
local playerrace = UnitRace("player")
local playerguild = GetGuildInfo("player")
if num + 1 >= MAX_WHOS_FROM_SERVER then
WhoFrameTotals:SetFormattedText("|cffffffff" .. GetText("WHO_FRAME_TOTAL_TEMPLATE", nil, num) .. " |cffaaaaaa" .. WHO_FRAME_SHOWN_TEMPLATE, max, MAX_WHOS_FROM_SERVER)
else
WhoFrameTotals:SetFormattedText("|cffffffff" .. GetText("WHO_FRAME_TOTAL_TEMPLATE", nil, num) .. " |cffaaaaaa" .. WHO_FRAME_SHOWN_TEMPLATE, num, num)
end
for i=1, WHOS_TO_DISPLAY do
local name, guild, level, race, class, zone = GetWhoInfo(off + i)
local displayedText = ""
local info = C_FriendList.GetWhoInfo(off + i)
if info then
-- filename is the class token, so no L["class"] reversal is needed
local class = info.filename
if num + 1 >= MAX_WHOS_FROM_SERVER then
displayedText = format(WHO_FRAME_SHOWN_TEMPLATE, MAX_WHOS_FROM_SERVER)
WhoFrameTotals:SetText("|cffffffff" .. format(GetText("WHO_FRAME_TOTAL_TEMPLATE", nil, num), max).." |cffaaaaaa"..displayedText)
else
displayedText = format(WHO_FRAME_SHOWN_TEMPLATE, num)
WhoFrameTotals:SetText("|cffffffff" .. format(GetText("WHO_FRAME_TOTAL_TEMPLATE", nil, num), num).." |cffaaaaaa"..displayedText)
end
_G["WhoFrameButton"..i.."Name"]:SetTextColor(NORMAL_FONT_COLOR:GetRGB())
class = L["class"][class]
_G["WhoFrameButton"..i.."Name"]:SetTextColor(NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b)
if (UIDropDownMenu_GetSelectedID(WhoFrameDropDown) == 1) then
if (zone == playerzone) then
_G["WhoFrameButton"..i.."Variable"]:SetTextColor(.5, 1, 1)
else
_G["WhoFrameButton"..i.."Variable"]:SetTextColor(1, 1, 1)
end
elseif (UIDropDownMenu_GetSelectedID(WhoFrameDropDown) == 2) then
if (guild == playerguild) then
_G["WhoFrameButton"..i.."Variable"]:SetTextColor(.5, 1, 1)
else
_G["WhoFrameButton"..i.."Variable"]:SetTextColor(1, 1, 1)
end
elseif (UIDropDownMenu_GetSelectedID(WhoFrameDropDown) == 3) then
if (race == playerrace) then
_G["WhoFrameButton"..i.."Variable"]:SetTextColor(.5, 1, 1)
else
_G["WhoFrameButton"..i.."Variable"]:SetTextColor(1, 1, 1)
end
end
if class then
local classicon = _G["WhoFrameButton"..i].classicon
local coords = CLASS_ICON_TCOORDS[class]
local color = PFUI_CLASS_COLORS[class]
-- do we have classicons? (skin enabled?)
if classicon then
_G["WhoFrameButton"..i.."Class"]:SetTextColor(0,0,0,0)
_G["WhoFrameButton"..i.."Name"]:SetTextColor(color.r,color.g,color.b,1)
if coords then
classicon:Show()
classicon:SetTexCoord(unpack(coords))
if (UIDropDownMenu_GetSelectedID(WhoFrameDropDown) == 1) then
if (info.area == playerzone) then
_G["WhoFrameButton"..i.."Variable"]:SetTextColor(.5, 1, 1)
else
classicon:Hide()
_G["WhoFrameButton"..i.."Variable"]:SetTextColor(1, 1, 1)
end
else
_G["WhoFrameButton"..i.."Class"]:SetTextColor(color.r,color.g,color.b,1)
end
end
local color = GetDifficultyColor(level)
_G["WhoFrameButton"..i.."Level"]:SetTextColor(color.r, color.g, color.b)
elseif (UIDropDownMenu_GetSelectedID(WhoFrameDropDown) == 2) then
if (info.fullGuildName == playerguild) then
_G["WhoFrameButton"..i.."Variable"]:SetTextColor(.5, 1, 1)
else
_G["WhoFrameButton"..i.."Variable"]:SetTextColor(1, 1, 1)
end
elseif (UIDropDownMenu_GetSelectedID(WhoFrameDropDown) == 3) then
if (info.raceStr == playerrace) then
_G["WhoFrameButton"..i.."Variable"]:SetTextColor(.5, 1, 1)
else
_G["WhoFrameButton"..i.."Variable"]:SetTextColor(1, 1, 1)
end
end
if class then
local classicon = _G["WhoFrameButton"..i].classicon
local coords = CLASS_ICON_TCOORDS[class]
local color = PFUI_CLASS_COLORS[class]
-- do we have classicons? (skin enabled?)
if classicon then
_G["WhoFrameButton"..i.."Class"]:SetTextColor(0,0,0,0)
_G["WhoFrameButton"..i.."Name"]:SetTextColor(color.r,color.g,color.b,1)
if coords then
classicon:Show()
classicon:SetTexCoord(unpack(coords))
else
classicon:Hide()
end
else
_G["WhoFrameButton"..i.."Class"]:SetTextColor(color.r,color.g,color.b,1)
end
end
local color = GetDifficultyColor(info.level)
_G["WhoFrameButton"..i.."Level"]:SetTextColor(color.r, color.g, color.b)
end
end
end)
end
+4 -1
View File
@@ -75,7 +75,10 @@ pfUI:RegisterModule("superwow", function ()
DEFAULT_CHAT_FRAME:AddMessage("-> https://github.com/balakethelock/SuperWoW/releases/")
end
if SUPERWOW_VERSION == "1.5" then
-- compare numerically: an exact string match silently drops this on any
-- SuperWoW release past 1.5
local swVersion = tonumber(SUPERWOW_VERSION)
if swVersion and swVersion >= 1.5 then
QueueFunction(function()
local pfCombatText_AddMessage = _G.CombatText_AddMessage
_G.CombatText_AddMessage = function(message, a, b, c, d, e, f)
+25 -34
View File
@@ -9,11 +9,6 @@ pfUI:RegisterModule("swingtimer", function ()
local ON_SWING_QUEUED = 0
local ON_SWING_QUEUE_POPPED = 1
-- Spell.dbc bits used to mirror server-side swing-reset rules.
local FLAG_AUTOATTACK = tonumber("0x08", 16) -- SPELL_INTERRUPT_FLAG_AUTOATTACK
local ATTR_KEEP_SWINGS = tonumber("0x20000", 16) -- SPELL_ATTR_EX2_NOT_RESET_AUTO_ACTIONS
local ATTR_ON_NEXT_SWING = tonumber("0x04", 16) -- SPELL_ATTR_ON_NEXT_SWING
-- Consolidate state into a table to avoid Lua 5.0 upvalue limit (32 max)
local S = {
mhTimer = 0, mhTimerMax = 1,
@@ -43,12 +38,11 @@ pfUI:RegisterModule("swingtimer", function ()
local WAND_SHOOT_SPELLID = 5019
local THROW_SPELLID = 2764 -- one-shot ranged, not auto-repeat
-- ATTR_ON_NEXT_SWING: spell replaces next auto-attack swing.
-- On-next-swing spells replace the next auto-attack swing.
-- Covers Raptor Strike, Maul, Mongoose Bite, Holy Strike, etc. automatically.
local function IsOnSwingSpell(spellId)
if S.onSwingCache[spellId] ~= nil then return S.onSwingCache[spellId] end
local attr = GetSpellRecField(spellId, "attributes") or 0
local result = bit.band(attr, ATTR_ON_NEXT_SWING) ~= 0
local result = C_Spell.IsNextMeleeSpell(spellId)
S.onSwingCache[spellId] = result
return result
end
@@ -290,7 +284,9 @@ pfUI:RegisterModule("swingtimer", function ()
UpdateMovable(pfUI.swingtimer.ranged)
-- OH weapon detection
local OH_WEAPON_TYPES = { [13]=true, [21]=true }
-- OH weapon detection: slot 17 (off hand) accepts one-hand (13) and off-hand
-- weapons (22); main-hand-only (21) can never sit there.
local OH_WEAPON_TYPES = { [13]=true, [22]=true }
local function HasOffhandWeapon()
local l = GetInventoryItemLink("player", 17)
if not l then return false end
@@ -596,10 +592,10 @@ pfUI:RegisterModule("swingtimer", function ()
end
pfUI.swingtimer.mainhand:SetStatusBarColor(curR, curG, curB, mhA)
if sw_showtext then
pfUI.swingtimer.mainhand.text:SetText(string.format("%.1f", math.floor(S.mhTimer * 10) / 10))
pfUI.swingtimer.mainhand.text:SetFormattedText("%.1f", math.floor(S.mhTimer * 10) / 10)
end
if sw_showspeed and S.mhSpeed > 0 then
pfUI.swingtimer.mainhand.speed:SetText(string.format("%.2f", S.mhSpeed))
pfUI.swingtimer.mainhand.speed:SetFormattedText("%.2f", S.mhSpeed)
end
anyActive = true
end
@@ -630,10 +626,10 @@ pfUI:RegisterModule("swingtimer", function ()
end
end
if sw_showtext then
pfUI.swingtimer.offhand.text:SetText(string.format("%.1f", math.floor(S.ohTimer * 10) / 10))
pfUI.swingtimer.offhand.text:SetFormattedText("%.1f", math.floor(S.ohTimer * 10) / 10)
end
if sw_showspeed and S.ohSpeed > 0 then
pfUI.swingtimer.offhand.speed:SetText(string.format("%.2f", S.ohSpeed))
pfUI.swingtimer.offhand.speed:SetFormattedText("%.2f", S.ohSpeed)
end
anyActive = true
elseif not sw_showoh then
@@ -672,9 +668,9 @@ pfUI:RegisterModule("swingtimer", function ()
end
if sw_showtext then
if remaining <= 0.5 then
pfUI.swingtimer.ranged.text:SetText(string.format("%.1f", math.floor(remaining * 10) / 10))
pfUI.swingtimer.ranged.text:SetFormattedText("%.1f", math.floor(remaining * 10) / 10)
else
pfUI.swingtimer.ranged.text:SetText(string.format("%.1f", math.floor((remaining - 0.5) * 10) / 10))
pfUI.swingtimer.ranged.text:SetFormattedText("%.1f", math.floor((remaining - 0.5) * 10) / 10)
end
end
else
@@ -687,11 +683,11 @@ pfUI:RegisterModule("swingtimer", function ()
pfUI.swingtimer.ranged.right:Hide()
pfUI.swingtimer.ranged.warn:Hide()
if sw_showtext then
pfUI.swingtimer.ranged.text:SetText(string.format("%.1f", math.floor(remaining * 10) / 10))
pfUI.swingtimer.ranged.text:SetFormattedText("%.1f", math.floor(remaining * 10) / 10)
end
end
if sw_showspeed and S.raSpeed > 0 then
pfUI.swingtimer.ranged.speed:SetText(string.format("%.2f", S.raSpeed))
pfUI.swingtimer.ranged.speed:SetFormattedText("%.2f", S.raSpeed)
end
local raProgress = 1 - (S.raTimer / S.raTimerMax)
local raMarkerX = raProgress * sw_width
@@ -734,13 +730,12 @@ pfUI:RegisterModule("swingtimer", function ()
S.pendingCastSpellId = arg1
-- Freeze the swing timer for cast-time spells that DON'T reset auto-
-- attack on completion (Slam, Hammer of Wrath on Turtle, etc.) — those
-- let the swing resume from where it paused. Detect dynamically via the
-- absent AUTOATTACK interrupt flag (8); spells with that bit reset on
-- SPELL_GO_SELF so freezing isn't necessary. Subsumes the old hardcoded
-- swingDelaySpells list (no list maintenance for new Slam-style spells).
-- let the swing resume from where it paused. C_Spell.ResetsMeleeSwing
-- mirrors the server rule; spells that reset don't need freezing (they
-- reset on SPELL_GO_SELF). Subsumes the old hardcoded swingDelaySpells
-- list (no list maintenance for new Slam-style spells).
if S.mhActive then
local iflags = GetSpellRecField(arg1, "interruptFlags") or 0
if bit.band(iflags, FLAG_AUTOATTACK) == 0 then
if not C_Spell.ResetsMeleeSwing(arg1) then
S.mhFrozenAt = GetTime()
end
end
@@ -777,17 +772,13 @@ pfUI:RegisterModule("swingtimer", function ()
S.hsQueued = false; S.cleaveQueued = false; S.maulQueued = false
ResetMH()
else
-- Mirror the server rule for "does this spell reset the auto-attack
-- swing" (Spell::IsMeleeAttackResetSpell in Turtle's core):
-- InterruptFlags has SPELL_INTERRUPT_FLAG_AUTOATTACK (0x08)
-- AND AttributesEx2 lacks NOT_RESET_AUTO_ACTIONS (0x20000).
-- If neither path resets and we're holding a frozen-swing-during-cast
-- (mhFrozenAt set by SPELL_START_SELF for non-AUTOATTACK spells), this
-- is a Slam-style cast — push the timer forward by the cast duration
-- so the bar resumes from where it paused.
local iflags = GetSpellRecField(spellId, "interruptFlags") or 0
if bit.band(iflags, FLAG_AUTOATTACK) ~= 0
and bit.band(GetSpellRecField(spellId, "attributesEx2") or 0, ATTR_KEEP_SWINGS) == 0 then
-- C_Spell.ResetsMeleeSwing mirrors the server rule (Turtle's
-- Spell::IsMeleeAttackResetSpell): InterruptFlags has AUTOATTACK and
-- AttributesEx2 lacks NOT_RESET_AUTO_ACTIONS. When the spell resets the
-- swing, snap the timers to full. Otherwise (elseif) a frozen-swing-
-- during-cast is a Slam-style cast — push the timer forward by the cast
-- duration so the bar resumes from where it paused.
if C_Spell.ResetsMeleeSwing(spellId) then
if S.mhActive and S.mhSpeed > 0 then
UpdateWeaponSpeeds()
S.mhTimerMax = S.mhSpeed
+6 -6
View File
@@ -100,10 +100,10 @@ pfUI:RegisterModule("thirdparty-vanilla", function()
CreateBackdropShadow(KLHTM_Frame)
if C.thirdparty.chatbg == "1" and C.chat.global.custombg == "1" then
local r, g, b, a = strsplit(",", C.chat.global.background)
local r, g, b, a = GetStringColor(C.chat.global.background)
KLHTM_Frame.backdrop:SetBackdropColor(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
local r, g, b, a = strsplit(",", C.chat.global.border)
local r, g, b, a = GetStringColor(C.chat.global.border)
KLHTM_Frame.backdrop:SetBackdropBorderColor(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
end
@@ -199,10 +199,10 @@ pfUI:RegisterModule("thirdparty-vanilla", function()
CreateBackdropShadow(TWTMain)
if C.thirdparty.chatbg == "1" and C.chat.global.custombg == "1" then
local r, g, b, a = strsplit(",", C.chat.global.background)
local r, g, b, a = GetStringColor(C.chat.global.background)
TWTMain.backdrop:SetBackdropColor(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
local r, g, b, a = strsplit(",", C.chat.global.border)
local r, g, b, a = GetStringColor(C.chat.global.border)
TWTMain.backdrop:SetBackdropBorderColor(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
end
@@ -328,10 +328,10 @@ pfUI:RegisterModule("thirdparty-vanilla", function()
CreateBackdropShadow(SW_BarFrame1)
if C.thirdparty.chatbg == "1" and C.chat.global.custombg == "1" then
local r, g, b, a = strsplit(",", C.chat.global.background)
local r, g, b, a = GetStringColor(C.chat.global.background)
SW_BarFrame1.backdrop:SetBackdropColor(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
local r, g, b, a = strsplit(",", C.chat.global.border)
local r, g, b, a = GetStringColor(C.chat.global.border)
SW_BarFrame1.backdrop:SetBackdropBorderColor(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
end
+101 -4
View File
@@ -244,10 +244,10 @@ pfUI:RegisterModule("thirdparty", function()
end
if C.thirdparty.chatbg == "1" and C.chat.global.custombg == "1" then
local r, g, b, a = strsplit(",", C.chat.global.background)
local r, g, b, a = GetStringColor(C.chat.global.background)
window.backdrop:SetBackdropColor(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
local r, g, b, a = strsplit(",", C.chat.global.border)
local r, g, b, a = GetStringColor(C.chat.global.border)
window.backdrop:SetBackdropBorderColor(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
end
@@ -277,6 +277,103 @@ pfUI:RegisterModule("thirdparty", function()
end
end)
-- GreedMeter Damage Meter
-- Vanilla: https://github.com/iGreed/GreedMeter
-- GreedMeter builds its windows lazily and supports several of them, so the
-- frames are resolved at call time. The primary window takes the "damage"
-- dock slot; a second window (GreedMeter.UI.frames[2]) takes the "threat"
-- slot so the two tile the panel side-by-side. Docking triggers a
-- LayoutBars/RefreshFrame so the bars refit the new width.
HookAddonOrVariable("GreedMeter", function()
local UI = GreedMeter.UI
local function frameAt(i)
return UI and UI.frames and UI.frames[i]
end
local function primary()
return (UI and UI.mainFrame) or getglobal("GreedMeterFrame1")
end
local function relayout(frame)
if not UI then return end
if UI.LayoutBars then UI:LayoutBars(frame) end
if UI.RefreshFrame then UI:RefreshFrame(frame) end
end
-- Builds a docktable for a window resolved through getframe(). left picks
-- the left half in split mode; the primary window uses the right half.
local function BuildDock(name, getframe, left)
return { "greedmeter", "GreedMeter", name,
function() -- single: fill the panel
local frame = getframe()
if not frame then return end
frame:ClearAllPoints()
frame:SetAllPoints(pfUI.chat.right)
frame:SetWidth(pfUI.chat.right:GetWidth())
relayout(frame)
end,
function() -- dual: take one half
local frame = getframe()
if not frame then return end
frame:ClearAllPoints()
if left then
frame:SetPoint("TOPLEFT", pfUI.chat.right, "TOPLEFT", 0, 0)
frame:SetPoint("BOTTOMRIGHT", pfUI.chat.right, "BOTTOM", 0, 0)
else
frame:SetPoint("TOPLEFT", pfUI.chat.right, "TOP", 0, 0)
frame:SetPoint("BOTTOMRIGHT", pfUI.chat.right, "BOTTOMRIGHT", 0, 0)
end
frame:SetWidth(pfUI.chat.right:GetWidth() / 2)
relayout(frame)
end,
function() -- show
local frame = getframe()
if frame then frame:Show() relayout(frame) end
end,
function() -- hide
local frame = getframe()
if frame then frame:Hide() end
end
}
end
local threatdock = BuildDock("GreedMeterFrame2", function() return frameAt(2) end, true)
pfUI.thirdparty.meters:RegisterMeter("damage", BuildDock("GreedMeterFrame1", primary, false))
-- pfUI's dock decides fill-vs-split by whether the threat slot is filled,
-- not by counting windows, so reconcile that slot whenever GreedMeter adds
-- or removes one. Only the first two windows fit; any beyond that float.
local function SyncSecond()
if not pfUI.thirdparty.meters.damage then return end -- dock disabled
local has2 = frameAt(2) ~= nil
if has2 and not pfUI.thirdparty.meters.threat then
pfUI.thirdparty.meters.threat = threatdock
elseif not has2 and pfUI.thirdparty.meters.threat == threatdock then
pfUI.thirdparty.meters.threat = nil
end
pfUI.thirdparty.meters:Resize()
-- match the second window's visibility to the panel (the primary tracks it)
local f1, f2 = primary(), frameAt(2)
if f2 then
if f1 and f1:IsShown() then f2:Show() relayout(f2) else f2:Hide() end
end
end
-- AddFrame and the login-time layout restore both go through
-- CreateMeterFrame; RemoveFrame handles closing a window.
local origCreate = UI.CreateMeterFrame
UI.CreateMeterFrame = function(self, isPrimary, copyFrom)
local frame = origCreate(self, isPrimary, copyFrom)
SyncSecond()
return frame
end
local origRemove = UI.RemoveFrame
UI.RemoveFrame = function(self, frame)
origRemove(self, frame)
SyncSecond()
end
end)
-- DPSMate Damage Meter
-- Vanilla: https://github.com/Geigerkind/DPSMate
-- TBC: https://github.com/Geigerkind/DPSMateTBC
@@ -350,10 +447,10 @@ pfUI:RegisterModule("thirdparty", function()
CreateBackdropShadow(frame)
if C.thirdparty.chatbg == "1" and C.chat.global.custombg == "1" then
local r, g, b, a = strsplit(",", C.chat.global.background)
local r, g, b, a = GetStringColor(C.chat.global.background)
frame.backdrop:SetBackdropColor(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
local r, g, b, a = strsplit(",", C.chat.global.border)
local r, g, b, a = GetStringColor(C.chat.global.border)
frame.backdrop:SetBackdropBorderColor(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
end
end
+127 -3
View File
@@ -166,9 +166,9 @@ pfUI:RegisterModule("tooltip", function ()
end
if C.tooltip.alwaysperc == "0" and ( estimated or hpmax > 100 or round(hpmax/100*hp) ~= hp ) then
pfUI.tooltipStatusBar.HP:SetText(string.format("%s / %s", Abbreviate(rhp), Abbreviate(rhpmax)))
pfUI.tooltipStatusBar.HP:SetFormattedText("%s / %s", Abbreviate(rhp), Abbreviate(rhpmax))
elseif hpmax > 0 then
pfUI.tooltipStatusBar.HP:SetText(string.format("%s%%", ceil(hp/hpmax*100)))
pfUI.tooltipStatusBar.HP:SetFormattedText("%s%%", ceil(hp/hpmax*100))
else
pfUI.tooltipStatusBar.HP:SetText("")
end
@@ -185,14 +185,102 @@ pfUI:RegisterModule("tooltip", function ()
GameTooltipStatusBar.SetStatusBarColor_orig = GameTooltipStatusBar.SetStatusBarColor
GameTooltipStatusBar.SetStatusBarColor = function() return end
-- A row of buff icons anchored above the tooltip for mouseover units.
-- Icons come from a ClassicAPI object pool: ReleaseAll hides the whole
-- set each refresh, then we Acquire and re-anchor left-to-right.
local BUFF_SIZE, BUFF_SPACING, BUFF_MAX = 20, 2, 32
pfUI.tooltip.buffs = CreateFrame("Frame", "pfTooltipBuffs", GameTooltip)
pfUI.tooltip.buffs:SetPoint("BOTTOMLEFT", GameTooltipStatusBar, "TOPLEFT", 0, default_border + 2)
pfUI.tooltip.buffs:SetHeight(BUFF_SIZE)
local function CreateBuffIcon()
local icon = CreateFrame("Frame", nil, pfUI.tooltip.buffs)
icon:SetSize(BUFF_SIZE, BUFF_SIZE)
icon.texture = icon:CreateTexture(nil, "BACKGROUND")
icon.texture:SetTexCoord(.07, .93, .07, .93)
icon.texture:SetAllPoints(icon)
CreateBackdrop(icon)
icon.stacks = icon:CreateFontString(nil, "OVERLAY", "GameFontNormal")
icon.stacks:SetPoint("BOTTOMRIGHT", icon, "BOTTOMRIGHT", 1, 0)
icon.stacks:SetFont(C.tooltip.font_tooltip, C.tooltip.font_tooltip_size, "OUTLINE")
icon.stacks:SetTextColor(1, 1, 1, 1)
icon.timer = icon:CreateFontString(nil, "OVERLAY", "GameFontNormal")
icon.timer:SetPoint("CENTER", icon, "CENTER", 0, 0)
icon.timer:SetFont(C.tooltip.font_tooltip, C.tooltip.font_tooltip_size, "OUTLINE")
icon.timer:SetTextColor(1, 1, 1, 1)
return icon
end
pfUI.tooltip.buffpool = CreateObjectPool(CreateBuffIcon, function(_, icon)
icon:Hide()
icon:ClearAllPoints()
end)
-- Tick the remaining-time text on the visible icons. OnUpdate only fires
-- while the row is shown, so it stops as soon as the tooltip hides.
pfUI.tooltip.buffs:SetScript("OnUpdate", function()
local now = GetTime()
if (this.tick or 0) > now then return end
this.tick = now + 0.1
for icon in pfUI.tooltip.buffpool:EnumerateActive() do
local timeleft = icon.expirationTime and icon.expirationTime > 0 and (icon.expirationTime - now) or 0
icon.timer:SetText(timeleft > 0 and GetColoredTimeString(timeleft) or "")
end
end)
function pfUI.tooltip:UpdateBuffs(unit)
pfUI.tooltip.buffpool:ReleaseAll()
if C.tooltip.showbuffs ~= "1" or not unit or unit == "none" then
pfUI.tooltip.buffs:Hide()
return
end
local prev, count = nil, 0
for i = 1, BUFF_MAX do
local aura = C_UnitAuras.GetBuffDataByIndex(unit, i)
if not aura then break end
count = count + 1
local icon = pfUI.tooltip.buffpool:Acquire()
icon.texture:SetTexture(aura.icon)
icon.stacks:SetText(aura.applications and aura.applications > 1 and aura.applications or "")
icon.expirationTime = aura.expirationTime
local timeleft = aura.expirationTime and aura.expirationTime > 0 and (aura.expirationTime - GetTime()) or 0
icon.timer:SetText(timeleft > 0 and GetColoredTimeString(timeleft) or "")
if prev then
icon:SetPoint("LEFT", prev, "RIGHT", BUFF_SPACING, 0)
else
icon:SetPoint("BOTTOMLEFT", pfUI.tooltip.buffs, "BOTTOMLEFT", 0, 0)
end
icon:Show()
prev = icon
end
if count > 0 then
pfUI.tooltip.buffs:SetWidth(count * BUFF_SIZE + (count - 1) * BUFF_SPACING)
pfUI.tooltip.buffs:Show()
else
pfUI.tooltip.buffs:Hide()
end
end
function pfUI.tooltip:Update()
local unit = pfUI.tooltip:GetUnit()
pfUI.tooltip:UpdateBuffs(unit)
if unit == "none" then
-- process item tooltips
if C.tooltip.itemid == "1" and GameTooltip:HasItem() then
local _, _, itemID = GameTooltip:GetItem()
GameTooltip:AddLine(T["ItemID"] .. ": " .. itemID, .25,.5,1)
GameTooltip:Show()
elseif C.tooltip.spellid == "1" and GameTooltip:HasSpell() then
local _, _, spellID = GameTooltip:GetSpell()
GameTooltip:AddLine(T["SpellID"] .. ": " .. spellID, .25,.5,1)
GameTooltip:Show()
end
return
@@ -259,6 +347,13 @@ pfUI:RegisterModule("tooltip", function ()
end
end
if C.tooltip.unitid == "1" and not UnitIsPlayer(unit) then
local npcID = C_CreatureInfo.GetCreatureID(UnitGUID(unit))
if npcID then
GameTooltip:AddLine(T["UnitID"] .. ": " .. npcID, .25,.5,1)
end
end
if hp and hpm then
if hp >= 1000 then hp = round(hp / 1000, 1) .. "k" end
if hpm >= 1000 then hpm = round(hpm / 1000, 1) .. "k" end
@@ -266,4 +361,33 @@ pfUI:RegisterModule("tooltip", function ()
end
GameTooltip:Show()
end
if C.tooltip.aurasource == "1" or C.tooltip.spellid == "1" then
hooksecurefunc(GameTooltip, "SetUnitAura", function(self, ...)
local aura = C_UnitAuras.GetAuraDataByIndex(unpack(arg))
if not aura then return end
if C.tooltip.aurasource == "1" then
local caster = aura.sourceUnit and UnitName(aura.sourceUnit)
if not caster and aura.sourceGUID then
caster = UnitNameFromGUID(aura.sourceGUID)
end
if caster and caster ~= "" then
local classToken
if aura.sourceUnit and UnitIsPlayer(aura.sourceUnit) then
classToken = UnitClassBase(aura.sourceUnit)
elseif aura.sourceGUID then
classToken = select(2, GetPlayerInfoByGUID(aura.sourceGUID))
end
if classToken then
caster = PFUI_CLASS_COLORS[classToken]:WrapTextInColorCode(caster)
end
self:AddLine(T["Cast by"] .. ": " .. caster, .25,.5,1)
end
end
if C.tooltip.spellid == "1" then
self:AddLine(T["SpellID"] .. ": " .. aura.spellId, .25,.5,1)
end
self:Show()
end)
end
end)
+23 -90
View File
@@ -3,56 +3,15 @@ pfUI:RegisterModule("tracking", function ()
MINIMAP_TRACKING_FRAME:UnregisterAllEvents()
MINIMAP_TRACKING_FRAME:Hide()
local function HasEntries(tbl)
for _ in pairs(tbl) do
return true
end
return nil
end
local rawborder, border = GetBorderSize()
local size = tonumber(C.appearance.minimap.tracking_size)
local pulse = C.appearance.minimap.tracking_pulse == "1"
-- Tracking spells identified by SpellID + expected icon path.
-- SpellID is the primary identifier (stable, locale-independent).
-- Icon path is used as secondary confirmation when scanning the spellbook.
local knownTrackingSpells = {
any = {
{ id = 2481, icon = "Racial_Dwarf_FindTreasure" }, -- Find Treasure
{ id = 2580, icon = "Spell_Nature_Earthquake" }, -- Find Minerals
{ id = 2383, icon = "INV_Misc_Flower_02" }, -- Find Herbs (Rank 1)
{ id = 8387, icon = "INV_Misc_Flower_02" }, -- Find Herbs (Rank 2)
{ id = 52917, icon = "INV_TradeSkillItem_03" }, -- Find Trees (TurtleWow)
},
HUNTER = {
{ id = 1494, icon = "Ability_Tracking" }, -- Track Beasts
{ id = 19883, icon = "Spell_Holy_PrayerOfHealing" }, -- Track Humanoids
{ id = 19884, icon = "Spell_Shadow_DarkSummoning" }, -- Track Undead
{ id = 19885, icon = "Ability_Stealth" }, -- Track Hidden
{ id = 19880, icon = "Spell_Frost_SummonWaterElemental" }, -- Track Elementals
{ id = 19878, icon = "Spell_Shadow_SummonFelHunter" }, -- Track Demons
{ id = 19882, icon = "Ability_Racial_Avatar" }, -- Track Giants
{ id = 19879, icon = "INV_Misc_Head_Dragon_01" }, -- Track Dragonkin
},
PALADIN = {
{ id = 5502, icon = "Spell_Holy_SenseUndead" }, -- Sense Undead
},
WARLOCK = {
{ id = 5500, icon = "Spell_Shadow_Metamorphosis" }, -- Sense Demons
},
DRUID = {
{ id = 5225, icon = "Ability_Tracking" }, -- Track Humanoids (Cat Form only)
},
}
-- Build a flat lookup: spellId -> entry, for fast spellbook matching
local spellIdLookup = {}
for _, entries in pairs(knownTrackingSpells) do
for _, entry in ipairs(entries) do
spellIdLookup[entry.id] = entry
end
end
-- Tracking spells come from ClassicAPI's native GetNumTrackingTypes /
-- GetTrackingInfo (see the DLL's docs/API.md "Tracking"). The DLL
-- enumerates them straight from the spellbook by tracking-aura effect,
-- so there is no spell table to maintain here and server-custom trackers
-- (e.g. Turtle's Find Trees) are picked up automatically.
local state = {
texture = nil,
@@ -128,50 +87,24 @@ pfUI:RegisterModule("tracking", function ()
end)
function pfUI.tracking:RefreshSpells()
local playerClass = UnitClassBase("player")
local isCatForm = pfUI.tracking:PlayerIsDruidInCatForm(playerClass)
state.spells = {}
if not GetNumTrackingTypes then return end
-- Build set of valid SpellIDs for this class
local validIds = {}
for _, entry in ipairs(knownTrackingSpells.any) do
validIds[entry.id] = true
end
if knownTrackingSpells[playerClass] then
for _, entry in ipairs(knownTrackingSpells[playerClass]) do
validIds[entry.id] = true
end
end
-- Druid Track Humanoids (5225) only casts in Cat Form. Hide it out of
-- form so the menu never offers a tracker that would fail to cast.
local isCatForm = pfUI.tracking:PlayerIsDruidInCatForm(UnitClassBase("player"))
-- Druids only get Track Humanoids in Cat Form
if playerClass == "DRUID" and not isCatForm then
validIds[5225] = nil
end
-- Scan spellbook: match by icon path, confirm SpellID is valid for this class
for tabIndex = 1, GetNumSpellTabs() do
local _, _, offset, numSpells = GetSpellTabInfo(tabIndex)
for spellIndex = offset + 1, offset + numSpells do
local spellTexture = GetSpellTexture(spellIndex, BOOKTYPE_SPELL)
local spellName = GetSpellName(spellIndex, BOOKTYPE_SPELL)
if pfUI.tracking.invalidSpells[spellName] then
spellTexture = nil
end
if spellTexture then
local lowerTexture = string.lower(spellTexture)
for spellId, entry in pairs(spellIdLookup) do
if validIds[spellId] and not state.spells[spellId]
and strfind(lowerTexture, string.lower(entry.icon)) then
state.spells[spellId] = {
index = spellIndex,
name = spellName,
texture = spellTexture,
spellId = spellId,
}
end
end
end
for i = 1, GetNumTrackingTypes() do
local name, texture, _, _, spellId = GetTrackingInfo(i)
local castable = not (spellId == 5225 and not isCatForm)
if name and texture and castable
and not pfUI.tracking.invalidSpells[name] then
state.spells[i] = {
index = i, -- tracking index, passed to SetTracking()
name = name,
texture = texture,
spellId = spellId,
}
end
end
end
@@ -187,7 +120,7 @@ pfUI:RegisterModule("tracking", function ()
elseif not texture then
state.texture = nil
if pulse and HasEntries(state.spells) then
if pulse and next(state.spells) ~= nil then
pfUI.tracking.pulse = true
pfUI.tracking.icon:SetTexture("Interface\\Icons\\INV_Misc_QuestionMark")
pfUI.tracking.icon:SetVertexColor(1,1,1,1)
@@ -217,7 +150,7 @@ pfUI:RegisterModule("tracking", function ()
checked = spell.texture == state.texture,
arg1 = spell,
func = function (arg1)
CastSpell(arg1.index, BOOKTYPE_SPELL)
SetTracking(arg1.index)
CloseDropDownMenus()
end
})
+19
View File
@@ -355,6 +355,25 @@ pfUI:RegisterModule("turtle-wow", function ()
end
end)
if C.disabled["macroicons"] == "1" then
HookAddonOrVariable("Blizzard_MacroUI", function()
if type(UpdateMacroIconFilenames) ~= "function" then return end
function _G.UpdateMacroIconFilenames()
wipe(MACRO_ICON_FILENAMES)
local provider = CreateAndInitFromMixin(IconDataProviderMixin, IconDataProviderExtraType.Spellbook)
local seen = {}
for i = 1, provider:GetNumIcons() do
-- uppercase, prefix-stripped basename, matching Blizzard's original
local icon = string.gsub(string.upper(provider:GetIconByIndex(i)), "INTERFACE\\ICONS\\", "")
if not seen[icon] then
seen[icon] = true
table.insert(MACRO_ICON_FILENAMES, icon)
end
end
provider:Release()
end
end)
end
-- add turtle-wow sell values
pfSellData = {
+14 -5
View File
@@ -28,7 +28,7 @@ pfUI:RegisterModule("unitxp", function ()
behindFrame.text:SetFont(pfUI.font_default, fontSize, "OUTLINE")
behindFrame.text:SetPoint("RIGHT", behindFrame, "RIGHT", -1, 0)
behindFrame.text:SetTextColor(0.3, 1, 0.3, 1)
behindFrame.text:SetText("BEHIND")
behindFrame.text:SetText(T["BEHIND"])
behindFrame.text:Hide()
local lastCheck = 0
@@ -62,7 +62,7 @@ pfUI:RegisterModule("unitxp", function ()
losFrame.text:SetFont(pfUI.font_default, fontSize, "OUTLINE")
losFrame.text:SetPoint("RIGHT", losFrame, "RIGHT", -1, -slot)
losFrame.text:SetTextColor(1, 0.3, 0.3, 1)
losFrame.text:SetText("NO LOS")
losFrame.text:SetText(T["NO LOS"])
losFrame.text:Hide()
local lastCheck = 0
@@ -134,7 +134,7 @@ pfUI:RegisterModule("unitxp", function ()
end
this.text:SetTextColor(r, g, b, 1)
this.text:SetText(string.format("%.1f%s", distance, suffix))
this.text:SetFormattedText("%.1f%s", distance, suffix)
this.text:Show()
end)
@@ -169,6 +169,9 @@ pfUI:RegisterModule("unitxp", function ()
local throttle = 0
local scanner = CreateFrame("Frame")
-- expose the poller frame so PLAYER_LOGOUT can stop its UnitXP OnUpdate
-- (it lives on this separate frame, not on distanceIndicator) -> crash 132
pfUI.uf.target.distanceScanner = scanner
scanner:SetScript("OnUpdate", function()
throttle = throttle + arg1
if throttle < 0.05 then return end
@@ -203,7 +206,7 @@ pfUI:RegisterModule("unitxp", function ()
end
f.text:SetTextColor(r, g, b, 1)
f.text:SetText(string.format("%.1f%s", distance, suffix))
f.text:SetFormattedText("%.1f%s", distance, suffix)
end)
end
pfUI.uf.target.distanceIndicator = pfRangeDisplay
@@ -236,6 +239,10 @@ pfUI:RegisterModule("unitxp", function ()
if pfUI.uf.target.distanceIndicator then
pfUI.uf.target.distanceIndicator:SetScript("OnUpdate", nil)
end
-- free-frame mode polls from its own scanner frame, not the indicator
if pfUI.uf.target.distanceScanner then
pfUI.uf.target.distanceScanner:SetScript("OnUpdate", nil)
end
end
return
end
@@ -268,7 +275,9 @@ pfUI:RegisterModule("unitxp", function ()
-- Also notify on BG queue pop
local origBattlefieldPortShow = BattlefieldFrame_Show
if origBattlefieldPortShow then
BattlefieldFrame_Show = function()
-- pfUI.env has __index but no __newindex, so a bare global assignment
-- inside a RegisterModule closure never leaves the sandbox
_G.BattlefieldFrame_Show = function()
pcall(UnitXP, "notify", "taskbarIcon")
pcall(UnitXP, "notify", "systemSound")
return origBattlefieldPortShow()
+3 -3
View File
@@ -236,7 +236,7 @@ end
local xpperc = round(xp / xpmax * 100)
local experc = ex and round(ex / xpmax * 100) or 0
if ex then text = "%s: %s%% (%s%% %s)" end
self.bar.text:SetText(string.format(text, T["Experience"], xpperc, experc, T["Rested"]))
self.bar.text:SetFormattedText(text, T["Experience"], xpperc, experc, T["Rested"])
self.tick = GetTime() + self.timeout
if event == "UPDATE_EXHAUSTION" and GameTooltip:IsOwned(self) then
@@ -254,7 +254,7 @@ end
local text = "%s: %s%%"
local xpperc = nextXP and nextXP ~= 0 and round(currXP / nextXP * 100) or 0
self.bar.text:SetText(string.format(text, T["Pet Experience"], xpperc))
self.bar.text:SetFormattedText(text, T["Pet Experience"], xpperc)
self.tick = GetTime() + self.timeout
return
@@ -282,7 +282,7 @@ end
local text = "%s: %s%% (%s)"
local perc = round(barValue / barMax * 100)
local standing = GetText("FACTION_STANDING_LABEL"..standingID, gender)
self.bar.text:SetText(string.format(text, name, perc, standing))
self.bar.text:SetFormattedText(text, name, perc, standing)
self.tick = GetTime() + self.timeout
return
+33 -11
View File
@@ -23,7 +23,7 @@ do
-- ClassicAPI dependency check.
-- pfUI relies pervasively on the modern C_* / SuperWoW / nameplate / focus
-- API surface that ClassicAPI polyfills, so presence is required.
local PFUI_CLASSIC_API_MIN = 10802 -- (X*10000 + Y*100 + Z)
local PFUI_CLASSIC_API_MIN = 11204 -- (X*10000 + Y*100 + Z)
local PFUI_CLASSIC_API_LATEST = PFUI_CLASSIC_API_MIN
local PFUI_CLASSIC_API_WEBSITE = "https://github.com/brues-code/ClassicAPI"
local PFUI_CLASSIC_API_LATEST_URL = PFUI_CLASSIC_API_WEBSITE .. "/releases/latest"
@@ -86,14 +86,13 @@ do
end
end
-- initialize saved variables
pfUI_playerDB = {}
pfUI_config = {}
pfUI_init = {}
pfUI_profiles = {}
pfUI_addon_profiles = {}
pfUI_cache = {}
pfUI_throttle = {}
pfUI_playerDB = pfUI_playerDB or {}
pfUI_config = pfUI_config or {}
pfUI_init = pfUI_init or {}
pfUI_profiles = pfUI_profiles or {}
pfUI_addon_profiles = pfUI_addon_profiles or {}
pfUI_cache = pfUI_cache or {}
pfUI_throttle = pfUI_throttle or {}
-- localization
pfUI_locale = {}
@@ -110,6 +109,14 @@ pfUI.movables = {}
pfUI.version = {}
pfUI.env = {}
-- Capability flag: this fork's pfActionBar buttons correctly handle the modern
-- HookScript widget method, so ClassicAPI's AddOnCompat shim must NOT shadow
-- HookScript during actionbar load (older forks branch on `if button.HookScript`
-- and take a modern path they were never written for). This describes behavior,
-- not identity: a fork that keeps the HookScript-correct actionbar code keeps
-- the flag; one that lacks it should drop the flag and get the safe fallback.
pfUI.handlesHookScript = true
if not pfUI.disabled then
pfUI.events = Mixin({}, CallbackRegistryMixin)
pfUI.events:OnLoad()
@@ -292,6 +299,21 @@ function pfUI:GetEnvironment()
pfUI.env.C = pfUI_config
pfUI.env.pfUI_throttle = _G.pfUI_throttle
pfUI.env.L = (pfUI_locale[GetLocale()] or pfUI_locale["enUS"])
pfUI.env.L["class"] = pfUI.env.L["class"] or tInvert(LOCALIZED_CLASS_NAMES_MALE)
if not pfUI.env.L["race"] then
pfUI.env.L["race"] = {}
local i = 1
local raceInfo = C_CreatureInfo.GetRaceInfo(i)
while raceInfo ~= nil do
pfUI.env.L["race"][raceInfo.clientFileString] = {
raceName = raceInfo.raceName,
raceID = raceInfo.raceID,
faction = C_CreatureInfo.GetFactionInfo(i).groupTag,
}
i = i + 1
raceInfo = C_CreatureInfo.GetRaceInfo(i)
end
end
return pfUI.env
end
@@ -425,7 +447,7 @@ pfUI:SetScript("OnEvent", function()
pfUI.version.major, pfUI.version.minor, pfUI.version.fix = 0, 0, 0
pfUI.version.string = "dev"
else
local major, minor, fix = pfUI.api.strsplit(".", raw)
local major, minor, fix = pfUI.api.strsplit(".", string.gsub(raw, "^[vV]", ""))
pfUI.version.major = tonumber(major) or 0
pfUI.version.minor = tonumber(minor) or 0
pfUI.version.fix = tonumber(fix) or 0
@@ -554,4 +576,4 @@ function pfUI.SetupCVars()
COMBAT_TEXT_SHOW_HONOR_GAINED = "1"
end
UIParentLoadAddOn("Blizzard_CombatText")
end
end
-1
View File
@@ -6,7 +6,6 @@
## Version: @project-version@
## SavedVariables: pfUI_profiles, pfUI_addon_profiles, pfUI_cache, pfUI_throttle
## SavedVariablesPerCharacter: pfUI_config, pfUI_init, pfUI_playerDB
## Dependencies: !!!ClassicAPI
## X-Website: https://github.com/brues-code/pfUI
pfUI.lua
+2 -3
View File
@@ -295,9 +295,8 @@ pfUI:RegisterSkin("Character", function ()
if faction and faction.reaction and faction.reaction < 8 then
local repLeft = faction.nextReactionThreshold - faction.currentStanding
if repLeft > 1 then
local text = standing:GetText() .. string.format(" (%d)", repLeft)
standing:SetText(text)
standing:GetParent().standingText = text
standing:SetFormattedText("%s (%d)", standing:GetText(), repLeft)
standing:GetParent().standingText = standing:GetText()
end
end
end
+1 -1
View File
@@ -125,7 +125,7 @@ pfUI:RegisterSkin("Inspect", function ()
local guild, title = GetGuildInfo(InspectFrame.unit)
if guild then
InspectGuildText:SetPoint("TOP", InspectLevelText, "BOTTOM", 0, -1)
InspectGuildText:SetText(format(TEXT(GUILD_TITLE_TEMPLATE), title, guild))
InspectGuildText:SetFormattedText(TEXT(GUILD_TITLE_TEMPLATE), title, guild)
InspectGuildText:Show()
else
InspectGuildText:SetText("")
+26 -24
View File
@@ -60,33 +60,35 @@ pfUI:RegisterSkin("Macro", function ()
MacroFrameScrollFrame:ClearAllPoints()
MacroFrameScrollFrame:SetPoint("TOPLEFT", MacroFrameSelectedMacroBackground, "BOTTOMLEFT", 11, -13)
-- The macroicons module replaces MacroPopupFrame with its own picker.
-- Only skin the stock popup when that module is disabled.
if pfUI_config["disabled"] and pfUI_config["disabled"]["macroicons"] == "1" then
StripTextures(MacroPopupFrame)
CreateBackdrop(MacroPopupFrame, nil, nil, .75)
MacroPopupFrame:SetFrameStrata("DIALOG")
MacroPopupFrame:ClearAllPoints()
MacroPopupFrame:SetPoint("TOPLEFT", MacroFrame.backdrop, "TOPRIGHT", 2*border, 0)
StripTextures(MacroPopupScrollFrame)
SkinScrollbar(MacroPopupScrollFrameScrollBar)
StripTextures(MacroPopupFrame)
CreateBackdrop(MacroPopupFrame, nil, nil, .75)
MacroPopupFrame:SetFrameStrata("DIALOG")
MacroPopupFrame:ClearAllPoints()
MacroPopupFrame:SetPoint("TOPLEFT", MacroFrame.backdrop, "TOPRIGHT", 2*border, 0)
MacroPopupEditBox:DisableDrawLayer("BACKGROUND")
CreateBackdrop(MacroPopupEditBox, nil, true)
MacroPopupEditBox:SetScript("OnEscapePressed", function()
MacroPopupFrame:Hide()
MacroFrame_Update()
end)
StripTextures(MacroPopupScrollFrame)
SkinScrollbar(MacroPopupScrollFrameScrollBar)
MacroPopupEditBox:DisableDrawLayer("BACKGROUND")
CreateBackdrop(MacroPopupEditBox, nil, true)
MacroPopupEditBox:SetScript("OnEscapePressed", function()
MacroPopupFrame:Hide()
MacroFrame_Update()
end)
for i=1, NUM_MACRO_ICONS_SHOWN do
local button = _G["MacroPopupButton"..i]
local icon = _G["MacroPopupButton"..i..'Icon']
StripTextures(button)
SkinButton(button, nil, nil, nil, icon)
for i=1, NUM_MACRO_ICONS_SHOWN do
local button = _G["MacroPopupButton"..i]
local icon = _G["MacroPopupButton"..i..'Icon']
StripTextures(button)
SkinButton(button, nil, nil, nil, icon)
end
SkinButton(MacroPopupCancelButton)
SkinButton(MacroPopupOkayButton)
MacroPopupOkayButton:ClearAllPoints()
MacroPopupOkayButton:SetPoint("RIGHT", MacroPopupCancelButton, "LEFT", -2*bpad, 0)
end
SkinButton(MacroPopupCancelButton)
SkinButton(MacroPopupOkayButton)
MacroPopupOkayButton:ClearAllPoints()
MacroPopupOkayButton:SetPoint("RIGHT", MacroPopupCancelButton, "LEFT", -2*bpad, 0)
end)
end)