18 Commits
Author SHA1 Message Date
Brues 40dd9dd6db equipmentmanager: drag a set onto the action bars via PickupEquipmentSet 2026-09-27 00:13:27 -05:00
Brues c1eba4d295 Bump CAPI min to 1.15.10 2026-09-25 10:58:07 -05:00
Brues cbc531531c swingtimer: drive the timers from ClassicAPI's PLAYER_SWING
Replaces the Lua reconstruction of the swing clock (AUTO_ATTACK_SELF with
an extra-attack guess, parry-haste maths, Slam freeze, DBC-based reset
checks on every SPELL_GO including triggered spells). PLAYER_SWING tracks
the server's clock from packets, so triggered casts, extra attacks, parry
haste, weapon swaps and the ranged wind-up all match the server.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* CAPI min bumped to 1.13.1

* Gracefully disable pfUI when ClassicAPI is missing

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

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

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

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

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

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

* Show Faction/Race icons in chat

* Load pfUI through ClassicAPI's flavor TOCs

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

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

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

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

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

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

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

* Split the vendor price tables into their own manifests

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

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

* Drop the vanilla compat layer

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

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

NAMEPLATE_FRAMETYPE and PLAYER_BUFF_START_ID had no readers left.

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

* bump CAPI min to 11303

* auras: uncap the self-debuff tooltip lookup

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

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

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

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

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

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

The colors were also only applied at module load, so the pickers needed a
/reload to show anything. Extract that into ApplyPanelColors and expose it as
pfUI.chat:UpdateConfig, which the gui resolves as U["chat"], so the three chat
color settings take effect on the spot. CreateBackdrop is re-run first to
restore the appearance theme, which is what lets toggling custom colors back
off return the panel to the global theme without a reload.
2026-09-02 11:40:20 -05:00
57 changed files with 6636 additions and 6369 deletions
+3 -3
View File
@@ -23,12 +23,12 @@ jobs:
IFS=. read -r MAJOR MINOR PATCH <<<"$LATEST" IFS=. read -r MAJOR MINOR PATCH <<<"$LATEST"
PACKED=$((MAJOR * 10000 + MINOR * 100 + PATCH)) PACKED=$((MAJOR * 10000 + MINOR * 100 + PATCH))
echo "Pinning PFUI_CLASSIC_API_LATEST to $LATEST ($PACKED)" echo "Pinning PFUI_CLASSIC_API_LATEST to $LATEST ($PACKED)"
sed -i "s/^\([[:space:]]*\)local PFUI_CLASSIC_API_LATEST = .*/\1local PFUI_CLASSIC_API_LATEST = $PACKED/" pfUI.lua sed -i "s/^\([[:space:]]*\)local PFUI_CLASSIC_API_LATEST = .*/\1local PFUI_CLASSIC_API_LATEST = $PACKED/" API_Check.lua
grep 'local PFUI_CLASSIC_API_' pfUI.lua grep 'local PFUI_CLASSIC_API_' API_Check.lua
env: env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Package and release to GitHub - name: Package and release to GitHub
uses: BigWigsMods/packager@v2 uses: brues-code/packager@vCAPI
env: env:
GITHUB_OAUTH: ${{ secrets.GITHUB_TOKEN }} GITHUB_OAUTH: ${{ secrets.GITHUB_TOKEN }}
+78
View File
@@ -0,0 +1,78 @@
do
-- ClassicAPI dependency gate.
--
-- Which TOC the client opened already answers "is ClassicAPI here?". Whenever
-- the DLL is loaded it redirects the read of `pfUI\pfUI.toc` to
-- `pfUI_Turtle.toc` (Turtle clients) or `pfUI_ClassicAPI.toc` (everything
-- else). Reaching this file from the plain `pfUI.toc` therefore means
-- ClassicAPI is absent -- and that TOC deliberately loads nothing but this
-- file, so there is nothing to disable, only a notice to show.
--
-- The flavor TOCs still need a version gate of their own: the redirect has
-- existed since ClassicAPI v1.11.0, well below the API surface pfUI relies
-- on. There pfUI does load, and will throw wherever it reaches for something
-- the installed DLL doesn't have yet -- the popup names the cause so those
-- errors aren't a mystery.
local PFUI_CLASSIC_API_MIN = 11510 -- (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"
local function FormatVersion(packed)
local x = math.floor(packed / 10000)
local y = math.floor(math.mod(packed, 10000) / 100)
local z = math.mod(packed, 100)
return string.format("v%d.%d.%d", x, y, z)
end
local headline, detail
if not CLASSIC_API_VERSION then
headline = "|cff33ffccpf|cffffffffUI|r has been disabled."
detail = "The ClassicAPI DLL isn't loaded. Download the latest release from:"
elseif CLASSIC_API_VERSION < PFUI_CLASSIC_API_MIN then
headline = "|cff33ffccpf|cffffffffUI|r will not work correctly on ClassicAPI " .. FormatVersion(CLASSIC_API_VERSION) .. "."
detail = FormatVersion(PFUI_CLASSIC_API_MIN) .. " or newer is required -- any errors alongside this one are the APIs it is missing. Download the latest release from:"
end
if detail then
local function ShowRequiredPopup()
StaticPopupDialogs["PFUI_CLASSICAPI_REQUIRED"] = {
text = headline .. "\n\n" .. detail,
button1 = OKAY,
hasEditBox = 1,
editBoxWidth = 280,
timeout = 0,
whileDead = 1,
hideOnEscape = 1,
preferredIndex = 3,
OnShow = function()
local editBox = getglobal(this:GetName().."EditBox")
if editBox then
editBox:SetText(PFUI_CLASSIC_API_LATEST_URL)
editBox:HighlightText()
editBox:SetFocus()
end
end,
}
StaticPopup_Show("PFUI_CLASSICAPI_REQUIRED")
DEFAULT_CHAT_FRAME:AddMessage(
headline .. " " .. detail .. " " .. PFUI_CLASSIC_API_LATEST_URL,
1, 0.3, 0.3
)
end
local loginFrame = CreateFrame("Frame")
loginFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
loginFrame:SetScript("OnEvent", function()
loginFrame:UnregisterEvent("PLAYER_ENTERING_WORLD")
ShowRequiredPopup()
end)
elseif CLASSIC_API_VERSION < PFUI_CLASSIC_API_LATEST then
EventUtil.ContinueOnPlayerLogin(function()
C_Timer.After(8, function()
DEFAULT_CHAT_FRAME:AddMessage(
"|cff33ffccpf|rUI: ClassicAPI " .. FormatVersion(PFUI_CLASSIC_API_LATEST) .. " is available — " .. PFUI_CLASSIC_API_LATEST_URL,
1, 0.85, 0.3
)
end)
end)
end
end
+1 -4
View File
@@ -1,13 +1,10 @@
# pfUI - ClassicAPI Edition # pfUI - ClassicAPI Edition
[![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) [![ClassicAPI](https://img.shields.io/badge/ClassicAPI-Required-purple.svg)](https://github.com/brues-code/ClassicAPI)
[![Nampower](https://img.shields.io/badge/Nampower-Required-purple.svg)](https://github.com/brues-code/nampower) [![Nampower](https://img.shields.io/badge/Nampower-Required-purple.svg)](https://github.com/brues-code/nampower)
[![SuperWoW](https://img.shields.io/badge/SuperWoW-Optional-yellow.svg)](https://github.com/balakethelock/SuperWoW) [![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) [![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/)**
This version includes significant performance improvements and DLL-enhanced features. This version includes significant performance improvements and DLL-enhanced features.
## Installation ## Installation
@@ -18,7 +15,7 @@ This version includes significant performance improvements and DLL-enhanced feat
## 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: Since pfUI 6.0.0 includes integrations with client-side DLLs for enhanced functionality:
### [ClassicAPI](https://github.com/brues-code/ClassicAPI) ### [ClassicAPI](https://github.com/brues-code/ClassicAPI)
+85
View File
@@ -204,6 +204,26 @@ function pfUI.api.UnitHasBuff(unit, name)
return C_UnitAuras.GetAuraDataBySpellName(unit, name, "HELPFUL") ~= nil or nil return C_UnitAuras.GetAuraDataBySpellName(unit, name, "HELPFUL") ~= nil or nil
end end
-- [ ScanAuraSlots ]
-- Fills `buf` with the slot ids of the auras on `unit` that match `filter` and
-- returns how many (buf[1..n]; entries past n are cleared), so a module can
-- keep one buffer and refill it every refresh. Read each aura with
-- C_UnitAuras.UnitAuraBySlot(unit, buf[i]) (positional, no table) or
-- GetAuraDataBySlot. One enumeration walks the aura array once, where a
-- by-index loop (UnitAura(unit, i)) re-walks it from the start for every i.
-- Uses GetAuraSlots' fill-a-table form (table as the 5th argument) instead of
-- its vararg return: Lua 5.0 builds an `arg` table for every vararg call, so
-- collecting the returns in a Lua helper would allocate once per scan.
-- unit [string] unit token
-- filter [string] aura filter ("HELPFUL", "HARMFUL|PLAYER", ...)
-- buf [table] reusable buffer, filled in place
-- max [number] optional cap on the slot count (nil = all)
-- return: [number] count of slot ids written to buf
function pfUI.api.ScanAuraSlots(unit, filter, buf, max)
local _, n = C_UnitAuras.GetAuraSlots(unit, filter, max, nil, buf)
return n
end
-- [ IsPlayerGuid ] -- [ IsPlayerGuid ]
-- Returns whether a GUID or unit token refers to the local player. -- Returns whether a GUID or unit token refers to the local player.
-- guid [string] A unit GUID (or unitID) to test. -- guid [string] A unit GUID (or unitID) to test.
@@ -242,6 +262,45 @@ function pfUI.api.GetUnitColor(unitstr)
return classColor:GenerateHexColorMarkup(), classColor:GetRGB() return classColor:GenerateHexColorMarkup(), classColor:GetRGB()
end end
-- [ GetRaceIcon ]
-- Builds an inline race icon texture from the shared races atlas.
-- 'raceKey' [string] englishRace key (e.g. "NightElf")
-- 'sex' [int] unit sex (3 = female)
-- return: [string] inline texture escape, or an empty string
function pfUI.api.GetRaceIcon(raceKey, sex)
if not raceKey then return "" end
local gender = sex == 3 and "FEMALE" or "MALE"
local coords = RACE_ICON_TCOORDS[strupper(raceKey) .. "_" .. gender]
if not coords then return "" end
return string.format(
"|TInterface\\Glues\\CharacterCreate\\UI-CharacterCreate-Races:0:0:0:0:512:512:%d:%d:%d:%d|t",
coords[1] * 512, coords[2] * 512, coords[3] * 512, coords[4] * 512)
end
-- inline faction emblems (cropped from the target-frame PvP banners)
local FACTION_ICON = {
Alliance = "|TInterface\\TargetingFrame\\UI-PVP-Alliance:0:0:0:0:64:64:5:37:3:35|t",
Horde = "|TInterface\\TargetingFrame\\UI-PVP-Horde:0:0:0:0:64:64:5:37:3:35|t",
}
-- [ GetFactionIcon ]
-- Resolves an inline faction emblem from the englishRace key stored in L["race"].
-- 'raceKey' [string] englishRace key (e.g. "Orc")
-- return: [string] inline texture escape, or an empty string
function pfUI.api.GetFactionIcon(raceKey)
local info = raceKey and L["race"][raceKey]
if not info then return "" end
return FACTION_ICON[info.faction] or ""
end
-- [ GetPlayerRaceIcons ]
-- Returns the faction and race emblems of the player character.
-- return: [string] both inline textures, or an empty string
function pfUI.api.GetPlayerRaceIcons()
local _, raceKey = UnitRace("player")
return pfUI.api.GetFactionIcon(raceKey) .. pfUI.api.GetRaceIcon(raceKey, UnitSex("player"))
end
-- [ strvertical ] -- [ strvertical ]
-- Creates vertical text using linebreaks. Multibyte char friendly. -- Creates vertical text using linebreaks. Multibyte char friendly.
-- 'str' [string] String to columnize. -- 'str' [string] String to columnize.
@@ -1274,6 +1333,32 @@ function pfUI.api.CreateBackdropShadow(f)
f.backdrop_shadow:SetBackdropBorderColor(0, 0, 0, tonumber(pfUI_config.appearance.border.shadow_intensity)) f.backdrop_shadow:SetBackdropBorderColor(0, 0, 0, tonumber(pfUI_config.appearance.border.shadow_intensity))
end end
-- [ Set Castbar Shield ]
-- Colors a castbar for a cast that none of the player's own interrupts or
-- silences can stop. The bar and the icon's border both take the shield color,
-- so the state still reads on a bar configured without an icon. Not shielded
-- restores the normal cast/channel color and the configured border.
-- 'bar' [frame] the castbar statusbar
-- 'icon' [frame] the icon frame, or nil for a bar without one
-- 'shielded' [bool] whether the cast is uninterruptible
-- 'isChannel' [bool] selects the unshielded color
function pfUI.api.SetCastbarShield(bar, icon, shielded, isChannel)
if shielded then
bar:SetStatusBarColor(pfUI.api.GetStringColor(C.appearance.castbar.shieldcolor))
else
bar:SetStatusBarColor(pfUI.api.GetStringColor(C.appearance.castbar[isChannel and "channelcolor" or "castbarcolor"]))
end
local iconborder = icon and icon.backdrop
if not iconborder then return end
if shielded then
iconborder:SetBackdropBorderColor(pfUI.api.GetStringColor(C.appearance.castbar.shieldcolor))
else
iconborder:SetBackdropBorderColor(pfUI.api.GetStringColor(C.appearance.border.color))
end
end
-- [ Bar Layout Options ] -- -- [ Bar Layout Options ] --
-- 'barsize' size of bar in number of buttons -- 'barsize' size of bar in number of buttons
-- returns: array of options as strings for pfUI.gui.bar -- returns: array of options as strings for pfUI.gui.bar
+6 -1
View File
@@ -156,6 +156,8 @@ function pfUI:LoadConfig()
pfUI:UpdateConfig("appearance", "castbar", "castbarcolor", ".7,.7,.9,.8") pfUI:UpdateConfig("appearance", "castbar", "castbarcolor", ".7,.7,.9,.8")
pfUI:UpdateConfig("appearance", "castbar", "channelcolor", ".9,.9,.7,.8") pfUI:UpdateConfig("appearance", "castbar", "channelcolor", ".9,.9,.7,.8")
pfUI:UpdateConfig("appearance", "castbar", "failcolor", "1,.2,.2,.8") pfUI:UpdateConfig("appearance", "castbar", "failcolor", "1,.2,.2,.8")
pfUI:UpdateConfig("appearance", "castbar", "shieldcolor", ".8,.45,.45,.8")
pfUI:UpdateConfig("appearance", "castbar", "fsrcolor", ".9,.6,.2,.8")
pfUI:UpdateConfig("appearance", "castbar", "texture", "Interface\\AddOns\\pfUI\\img\\bar") pfUI:UpdateConfig("appearance", "castbar", "texture", "Interface\\AddOns\\pfUI\\img\\bar")
pfUI:UpdateConfig("appearance", "infight", "screen", "0") pfUI:UpdateConfig("appearance", "infight", "screen", "0")
pfUI:UpdateConfig("appearance", "infight", "aggro", "0") pfUI:UpdateConfig("appearance", "infight", "aggro", "0")
@@ -324,6 +326,9 @@ function pfUI:LoadConfig()
pfUI:UpdateConfig("unitframes", "player", "showRest", "0") pfUI:UpdateConfig("unitframes", "player", "showRest", "0")
pfUI:UpdateConfig("unitframes", "player", "energy", "1") pfUI:UpdateConfig("unitframes", "player", "energy", "1")
pfUI:UpdateConfig("unitframes", "player", "manatick", "0") pfUI:UpdateConfig("unitframes", "player", "manatick", "0")
pfUI:UpdateConfig("unitframes", "player", "showfsr", "1")
pfUI:UpdateConfig("unitframes", "player", "fsrheight", "4")
pfUI:UpdateConfig("unitframes", "player", "fsrstyle", "0")
pfUI:UpdateConfig("unitframes", "player", "display_haste", "0") pfUI:UpdateConfig("unitframes", "player", "display_haste", "0")
pfUI:UpdateConfig("unitframes", "player", "display_haste_color", "1,1,1,1") pfUI:UpdateConfig("unitframes", "player", "display_haste_color", "1,1,1,1")
pfUI:UpdateConfig("unitframes", "player", "display_spellpower", "1") pfUI:UpdateConfig("unitframes", "player", "display_spellpower", "1")
@@ -645,7 +650,6 @@ function pfUI:LoadConfig()
pfUI:UpdateConfig("bars", nil, "animation", "zoomfade") pfUI:UpdateConfig("bars", nil, "animation", "zoomfade")
pfUI:UpdateConfig("bars", nil, "animmode", "keypress") pfUI:UpdateConfig("bars", nil, "animmode", "keypress")
pfUI:UpdateConfig("bars", nil, "animalways", "0") pfUI:UpdateConfig("bars", nil, "animalways", "0")
pfUI:UpdateConfig("bars", nil, "macroscan", "1")
pfUI:UpdateConfig("bars", nil, "reagents", "1") pfUI:UpdateConfig("bars", nil, "reagents", "1")
pfUI:UpdateConfig("bars", nil, "hunterbar", "0") pfUI:UpdateConfig("bars", nil, "hunterbar", "0")
pfUI:UpdateConfig("bars", nil, "pagemasteralt", "0") pfUI:UpdateConfig("bars", nil, "pagemasteralt", "0")
@@ -817,6 +821,7 @@ function pfUI:LoadConfig()
pfUI:UpdateConfig("chat", "text", "playerlinks", "1") pfUI:UpdateConfig("chat", "text", "playerlinks", "1")
pfUI:UpdateConfig("chat", "text", "detecturl", "1") pfUI:UpdateConfig("chat", "text", "detecturl", "1")
pfUI:UpdateConfig("chat", "text", "classcolor", "1") pfUI:UpdateConfig("chat", "text", "classcolor", "1")
pfUI:UpdateConfig("chat", "text", "playericons", "0")
pfUI:UpdateConfig("chat", "text", "whosearchunknown", "0") pfUI:UpdateConfig("chat", "text", "whosearchunknown", "0")
pfUI:UpdateConfig("chat", "text", "playerlevel", "0") pfUI:UpdateConfig("chat", "text", "playerlevel", "0")
pfUI:UpdateConfig("chat", "left", "width", "380") pfUI:UpdateConfig("chat", "left", "width", "380")
+244 -198
View File
@@ -4,6 +4,17 @@ setfenv(1, pfUI:GetEnvironment())
pfUI.uf = CreateFrame("Frame", nil, UIParent) pfUI.uf = CreateFrame("Frame", nil, UIParent)
pfUI.uf.frames = {} pfUI.uf.frames = {}
-- Reusable buffer for C_UnitAuras.GetAuraSlots slot ids (filled by
-- ScanAuraSlots, api.lua). Each aura scan in RefreshUnit fills it once, then
-- reads every aura by slot id: one array walk per scan instead of one per index.
-- Scans run back to back and consume the buffer before the next refill.
local auraSlots = {}
-- Separate buffer for the tooltip handlers. They run from OnEnter, which can
-- fire while RefreshUnit is showing/hiding icons under the cursor, so they must
-- not share the refresh buffer.
local tooltipSlots = {}
-- ============================================================================ -- ============================================================================
-- GUID-based Roster Tracking for Smart Updates -- GUID-based Roster Tracking for Smart Updates
-- Only updates frames where the unit actually changed, not ALL 40 frames -- Only updates frames where the unit actually changed, not ALL 40 frames
@@ -24,14 +35,38 @@ pfUI.api.RegisterSlashCommand("PFTEST", { "/pftest", "/pfuftest" }, function()
if pfUI.uf.raid and pfUI.uf.raid.LayoutPets then pfUI.uf.raid:LayoutPets() end if pfUI.uf.raid and pfUI.uf.raid.LayoutPets then pfUI.uf.raid:LayoutPets() end
end, true) end, true)
-- HoT buff indicators that need name verification because their icons are -- Buff indicators are identified by a spell id, resolved once into the aura's
-- reused by other spells. Maps icon (lowercased) → expected aura name + -- localized name plus its icon. A match needs both to agree.
-- libpredict key for the prediction integration. --
local HOT_INDICATORS = { -- Icon alone is ambiguous: Spell.dbc reuses icons across unrelated spells, so
[strlower(C_Spell.GetSpellTexture(774))] = { name = strlower(C_Spell.GetSpellName(774)), predict = "Reju" }, -- an icon-only filter lights the indicator for the wrong buff (Blessing of
[strlower(C_Spell.GetSpellTexture(139))] = { name = strlower(C_Spell.GetSpellName(139)), predict = "Renew" }, -- Sanctuary shares its icon with Lightning Shield and Shadowguard, Blessing of
[strlower(C_Spell.GetSpellTexture(8936))] = { name = strlower(C_Spell.GetSpellName(8936)), predict = "Regr" }, -- Kings with Mage Armor and Commanding Shout, Totemic Power with the Blessed
} -- Sunfruit food buff). Name alone is ambiguous too -- creature and item auras
-- reuse player spell names ("Renew", "Rejuvenation", "Fire Resistance").
--
-- Every rank of a spell carries the same name and icon, so one id per buff
-- covers the whole rank ladder. Ids missing from this client resolve to nil and
-- drop out of the list. 'predict' names the libpredict key of a HoT.
--
-- Both fields are stored raw, and RefreshUnit compares them raw. The aura's
-- name and icon and these come out of the same DBC records byte for byte --
-- Spell.dbc's localized name, SpellIcon.dbc's path -- so case folding either
-- side would only burn a string per aura per scan. Feed this ids, never
-- hand-written names or icon paths, or that equality quietly stops holding.
local indicator_cache = {}
local function AddIndicator(indicators, spellId, predict)
local record = indicator_cache[spellId]
if record == nil then
local name = C_Spell.GetSpellName(spellId)
local icon = name and C_Spell.GetSpellTexture(spellId)
-- cache misses as false, so an absent spell is only looked up once
record = icon and { name = name, icon = icon, predict = predict } or false
indicator_cache[spellId] = record
end
if record then table.insert(indicators, record) end
end
local glow = { local glow = {
edgeFile = pfUI.media["img:glow"], edgeSize = 8, edgeFile = pfUI.media["img:glow"], edgeSize = 8,
@@ -88,16 +123,21 @@ local function DebuffOnEnter()
local parent = this:GetParent() local parent = this:GetParent()
-- selfdebuff filters the displayed list to player-cast harmful auras, but -- selfdebuff filters the displayed list to player-cast harmful auras, but
-- SetUnitAura's index has to be into the engine's full HARMFUL list. Look -- SetUnitAura's index has to be into the unfiltered HARMFUL list. Look up
-- up the displayed aura via the PLAYER filter, then scan engine slots for -- the displayed aura via the PLAYER filter, then find its position in the
-- one whose name + sourceGUID match. -- unfiltered list by name + sourceGUID.
--
-- The unfiltered list is NOT capped at 16: once a unit's 16 debuff slots
-- are full the server parks further debuffs in buff slots, and C_UnitAuras
-- reports those as harmful too. So enumerate however many the unit has.
if parent.config and parent.config.selfdebuff == "1" then if parent.config and parent.config.selfdebuff == "1" then
local ownAura = C_UnitAuras.GetAuraDataByIndex(unitstr, this.id, "HARMFUL|PLAYER") local ownAura = C_UnitAuras.GetAuraDataByIndex(unitstr, this.id, "HARMFUL|PLAYER")
if ownAura then if ownAura then
for gameSlot = 1, 16 do local n = ScanAuraSlots(unitstr, "HARMFUL", tooltipSlots)
local check = C_UnitAuras.GetDebuffDataByIndex(unitstr, gameSlot) for i = 1, n do
local check = C_UnitAuras.GetAuraDataBySlot(unitstr, tooltipSlots[i])
if check and check.name == ownAura.name and check.sourceGUID == ownAura.sourceGUID then if check and check.name == ownAura.name and check.sourceGUID == ownAura.sourceGUID then
GameTooltip:SetUnitAura(unitstr, gameSlot, "HARMFUL") GameTooltip:SetUnitAura(unitstr, i, "HARMFUL")
return return
end end
end end
@@ -273,9 +313,10 @@ function pfUI.uf:UpdateVisibility()
self._label, self._id = nil, nil self._label, self._id = nil, nil
end end
local unitstr = string.format("%s%s", self.label or "", self.id or "") local unitstr = ("%s%s"):format(self.label or "", self.id or "")
self:SetAttribute("unit", unitstr ~= "" and unitstr or nil) self.unitstr = unitstr ~= "" and unitstr or nil
local visibility = string.format("[target=%s,exists] show; hide", unitstr) self:SetAttribute("unit", self.unitstr)
local visibility = ("[target=%s,exists] show; hide"):format(unitstr)
-- Group frames are redundant when the group is already shown as a raid grid: -- Group frames are redundant when the group is already shown as a raid grid:
-- either an actual raid, or a party promoted to the raid grid via -- either an actual raid, or a party promoted to the raid grid via
@@ -304,6 +345,15 @@ function pfUI.uf:UpdateVisibility()
self.visible = nil self.visible = nil
end end
-- This is the single place a frame's unit is ever assigned, so it is also the
-- place its subscriptions follow it to: the engine then delivers only this
-- unit's events, and OnEvent compares against the cached string instead of
-- rebuilding label..id -- and calling UnitGUID -- for every unit event fired
-- by anything, anywhere. A frame that is not in use drops them entirely; the
-- roster events that would bring it back are registered plainly, and
-- visibilityscan re-runs this every 0.2s regardless, so it recovers on its own.
self:RegisterUnitEvents(visibility ~= "hide" and self.unitstr or nil)
-- vanilla visibility -- vanilla visibility
if self.unitname then if self.unitname then
self:Show() self:Show()
@@ -584,11 +634,14 @@ function pfUI.uf:UpdateConfig()
f.feedbackText:ClearAllPoints() f.feedbackText:ClearAllPoints()
f.feedbackText:SetPoint("CENTER", f.portrait, "CENTER") f.feedbackText:SetPoint("CENTER", f.portrait, "CENTER")
end end
f:RegisterEvent("UNIT_COMBAT") f.combatfeedback = true
else else
f.feedbackText:Hide() f.feedbackText:Hide()
f:UnregisterEvent("UNIT_COMBAT") f.combatfeedback = nil
end end
-- RegisterUnitEvents owns UNIT_COMBAT; clearing the cached unit makes the
-- next UpdateVisibility re-run it against the new combatfeedback state.
f.eventunit = nil
f.hpLeftText:SetFontObject(GameFontWhite) f.hpLeftText:SetFontObject(GameFontWhite)
f.hpLeftText:SetFont(fontname, fontsize, fontstyle) f.hpLeftText:SetFont(fontname, fontsize, fontstyle)
@@ -761,7 +814,7 @@ function pfUI.uf:UpdateConfig()
if not f.buffs[i].cd then if not f.buffs[i].cd then
if cooldown_anim == 1 then if cooldown_anim == 1 then
-- Animation enabled: Use Model frame with CooldownFrameTemplate -- Animation enabled: Use Model frame with CooldownFrameTemplate
f.buffs[i].cd = CreateFrame(COOLDOWN_FRAME_TYPE, f.buffs[i]:GetName() .. "Cooldown", f.buffs[i], "CooldownFrameTemplate") f.buffs[i].cd = CreateFrame("Model", f.buffs[i]:GetName() .. "Cooldown", f.buffs[i], "CooldownFrameTemplate")
else else
-- Animation disabled: Use regular Frame with dummy functions -- Animation disabled: Use regular Frame with dummy functions
f.buffs[i].cd = CreateFrame("Frame", f.buffs[i]:GetName() .. "Cooldown", f.buffs[i]) f.buffs[i].cd = CreateFrame("Frame", f.buffs[i]:GetName() .. "Cooldown", f.buffs[i])
@@ -834,7 +887,7 @@ function pfUI.uf:UpdateConfig()
if not f.debuffs[i].cd then if not f.debuffs[i].cd then
if cooldown_anim == 1 then if cooldown_anim == 1 then
-- Animation enabled: Use Model frame with CooldownFrameTemplate -- Animation enabled: Use Model frame with CooldownFrameTemplate
f.debuffs[i].cd = CreateFrame(COOLDOWN_FRAME_TYPE, f.debuffs[i]:GetName() .. "Cooldown", f.debuffs[i], "CooldownFrameTemplate") f.debuffs[i].cd = CreateFrame("Model", f.debuffs[i]:GetName() .. "Cooldown", f.debuffs[i], "CooldownFrameTemplate")
else else
-- Animation disabled: Use regular Frame with dummy functions -- Animation disabled: Use regular Frame with dummy functions
f.debuffs[i].cd = CreateFrame("Frame", f.debuffs[i]:GetName() .. "Cooldown", f.debuffs[i]) f.debuffs[i].cd = CreateFrame("Frame", f.debuffs[i]:GetName() .. "Cooldown", f.debuffs[i])
@@ -878,6 +931,9 @@ function pfUI.uf:UpdateConfig()
f:UpdateFrameSize() f:UpdateFrameSize()
else else
f:UnregisterAllEvents() f:UnregisterAllEvents()
-- that dropped the unit filters along with the registrations, so the cache
-- has to go too or the next UpdateVisibility believes they are still set
f.eventunit = nil
f:Hide() f:Hide()
end end
end end
@@ -919,6 +975,10 @@ function pfUI.uf.OnEvent()
this:UnregisterAllEvents() this:UnregisterAllEvents()
this:SetScript("OnEvent", nil) this:SetScript("OnEvent", nil)
this:SetScript("OnUpdate", nil) this:SetScript("OnUpdate", nil)
-- visibilityscan is a separate frame and keeps ticking, so leaving this one
-- on its list would have UpdateVisibility re-register the unit events we
-- just dropped -- straight back into the crash 132 this branch prevents
visibilityscan.frames[this] = nil
return return
end end
@@ -985,8 +1045,12 @@ function pfUI.uf.OnEvent()
this.update_aura = true this.update_aura = true
elseif this.label == "pet" and event == "UNIT_HAPPINESS" then elseif this.label == "pet" and event == "UNIT_HAPPINESS" then
this.update_full = true this.update_full = true
-- UNIT_XXX Events -- UNIT_XXX Events. RegisterUnitEvents means arg1 can only be this frame's own
elseif arg1 and (arg1 == this.label .. this.id or (UnitGUID and arg1 == UnitGUID(this.label .. this.id))) then -- unit; the compare is kept for the case the filter sits out, which is when
-- arg1 is not a string. The old GUID alternative is gone: these events fire
-- once per token that resolves to the unit AND once with the raw GUID, so the
-- token form always arrives and the GUID form was only ever a duplicate wake.
elseif arg1 and arg1 == this.unitstr then
if event == "UNIT_PORTRAIT_UPDATE" or event == "UNIT_MODEL_CHANGED" then if event == "UNIT_PORTRAIT_UPDATE" or event == "UNIT_MODEL_CHANGED" then
this.update_portrait = true this.update_portrait = true
elseif event == "UNIT_AURA" then elseif event == "UNIT_AURA" then
@@ -1211,25 +1275,56 @@ function pfUI.uf.OnUpdate()
end end
end end
-- The unit events whose arg1 is the frame's OWN unit -- everything the OnEvent
-- routes through its "UNIT_XXX Events" branch. These are not registered here:
-- UpdateVisibility owns them, because it owns the frame's unit (below).
--
-- UNIT_PET and UNIT_HAPPINESS are deliberately absent. Their branches key on
-- the frame's label, and UNIT_PET's arg1 is the pet's OWNER ("player" for a
-- "pet" frame), so filtering them by the frame's own unit would drop them.
local UNIT_EVENTS = {
"UNIT_DISPLAYPOWER",
"UNIT_HEALTH", "UNIT_MAXHEALTH",
"UNIT_MANA", "UNIT_MAXMANA",
"UNIT_RAGE", "UNIT_MAXRAGE",
"UNIT_ENERGY", "UNIT_MAXENERGY",
"UNIT_FOCUS",
"UNIT_PORTRAIT_UPDATE", "UNIT_MODEL_CHANGED",
"UNIT_FACTION",
"UNIT_AURA", -- frame=buff, frame=debuff
}
-- Point this frame's unit-event subscriptions at `unitstr`, or drop them when
-- the frame has no unit. Cheap to call repeatedly: it no-ops unless the unit
-- actually changed, which matters because visibilityscan runs UpdateVisibility
-- for every frame five times a second.
function pfUI.uf:RegisterUnitEvents(unitstr)
if self.eventunit == unitstr then return end
self.eventunit = unitstr
for i = 1, table.getn(UNIT_EVENTS) do
if unitstr then
self:RegisterUnitEvent(UNIT_EVENTS[i], unitstr)
else
self:UnregisterEvent(UNIT_EVENTS[i])
end
end
-- UNIT_COMBAT rides along only while the frame draws combat feedback text.
-- UpdateConfig clears eventunit when it toggles that, so the next
-- UpdateVisibility re-runs this.
if unitstr and self.combatfeedback then
self:RegisterUnitEvent("UNIT_COMBAT", unitstr)
else
self:UnregisterEvent("UNIT_COMBAT")
end
end
function pfUI.uf:EnableEvents() function pfUI.uf:EnableEvents()
local f = self local f = self
f:RegisterEvent("PLAYER_ENTERING_WORLD") f:RegisterEvent("PLAYER_ENTERING_WORLD")
f:RegisterEvent("PLAYER_LOGOUT") f:RegisterEvent("PLAYER_LOGOUT")
f:RegisterEvent("UNIT_DISPLAYPOWER")
f:RegisterEvent("UNIT_HEALTH")
f:RegisterEvent("UNIT_MAXHEALTH")
f:RegisterEvent("UNIT_MANA")
f:RegisterEvent("UNIT_MAXMANA")
f:RegisterEvent("UNIT_RAGE")
f:RegisterEvent("UNIT_MAXRAGE")
f:RegisterEvent("UNIT_ENERGY")
f:RegisterEvent("UNIT_MAXENERGY")
f:RegisterEvent("UNIT_FOCUS")
f:RegisterEvent("UNIT_PORTRAIT_UPDATE")
f:RegisterEvent("UNIT_MODEL_CHANGED")
f:RegisterEvent("UNIT_FACTION")
f:RegisterEvent("UNIT_AURA") -- frame=buff, frame=debuff
f:RegisterEvent("PLAYER_AURAS_CHANGED") -- label=player && frame=buff f:RegisterEvent("PLAYER_AURAS_CHANGED") -- label=player && frame=buff
f:RegisterEvent("PLAYER_EQUIPMENT_CHANGED") -- label=player && frame=buff (ClassicAPI: weapon-enchant buffs) f:RegisterEvent("PLAYER_EQUIPMENT_CHANGED") -- label=player && frame=buff (ClassicAPI: weapon-enchant buffs)
f:RegisterEvent("PARTY_MEMBERS_CHANGED") -- label=party, frame=leaderIcon f:RegisterEvent("PARTY_MEMBERS_CHANGED") -- label=party, frame=leaderIcon
@@ -1347,6 +1442,7 @@ function pfUI.uf:CreateUnitFrame(unit, id, config, tick)
f.UpdateConfig = pfUI.uf.UpdateConfig f.UpdateConfig = pfUI.uf.UpdateConfig
f.EnableScripts = pfUI.uf.EnableScripts f.EnableScripts = pfUI.uf.EnableScripts
f.EnableEvents = pfUI.uf.EnableEvents f.EnableEvents = pfUI.uf.EnableEvents
f.RegisterUnitEvents = pfUI.uf.RegisterUnitEvents
f.EnableClickCast = pfUI.uf.EnableClickCast f.EnableClickCast = pfUI.uf.EnableClickCast
f.GetColor = pfUI.uf.GetColor f.GetColor = pfUI.uf.GetColor
@@ -1463,6 +1559,9 @@ function pfUI.uf:CreateUnitFrame(unit, id, config, tick)
f:UpdateFrameSize() f:UpdateFrameSize()
else else
f:UnregisterAllEvents() f:UnregisterAllEvents()
-- that dropped the unit filters along with the registrations, so the cache
-- has to go too or the next UpdateVisibility believes they are still set
f.eventunit = nil
f:Hide() f:Hide()
end end
@@ -1635,11 +1734,13 @@ function pfUI.uf:RefreshUnit(unit, component)
-- buffs -- buffs
if unit.buffs and ( component == "all" or component == "aura" ) then if unit.buffs and ( component == "all" or component == "aura" ) then
-- one GetAuraSlots enumeration per refresh, then a positional read per
-- slot id: allocates nothing and never re-walks the aura array per icon
ScanAuraSlots(unitstr, "HELPFUL", auraSlots, unit.config.bufflimit)
for i=1, unit.config.bufflimit do for i=1, unit.config.bufflimit do
if not unit.buffs[i] then break end if not unit.buffs[i] then break end
-- positional UnitBuff allocates nothing (vs a table per icon per refresh) local name, icon, count, _, duration, expirationTime, _, _, _, spellId = C_UnitAuras.UnitAuraBySlot(unitstr, auraSlots[i])
local name, icon, count, _, duration, expirationTime, _, _, _, spellId = C_UnitAuras.UnitBuff(unitstr, i)
if name then if name then
unit.buffs[i].texture:SetTexture(icon) unit.buffs[i].texture:SetTexture(icon)
@@ -1723,6 +1824,13 @@ function pfUI.uf:RefreshUnit(unit, component)
reposition = true reposition = true
end end
-- 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). One GetAuraSlots enumeration per refresh; the i-th slot
-- is the i-th aura of the filtered list, so `i` stays the tooltip index.
local filter = (unit.label ~= "player" and selfdebuff == "1") and "HARMFUL|PLAYER" or "HARMFUL"
ScanAuraSlots(unitstr, filter, auraSlots, unit.config.debufflimit)
for i=1, unit.config.debufflimit do for i=1, unit.config.debufflimit do
if not unit.debuffs[i] then break end if not unit.debuffs[i] then break end
@@ -1739,12 +1847,8 @@ function pfUI.uf:RefreshUnit(unit, component)
invert_h * ((row+buffrow)*(multiply*default_border + unit.config.debuffsize + 1) + (multiply*default_border + 1))) invert_h * ((row+buffrow)*(multiply*default_border + unit.config.debuffsize + 1) + (multiply*default_border + 1)))
end end
-- selfdebuff narrows to player-cast harmful auras via the PLAYER filter. -- positional read by slot id allocates nothing
-- Player-frame debuffs aren't gated on it (it'd hide most party-applied local name, icon, count, dispelType, duration, expirationTime = C_UnitAuras.UnitAuraBySlot(unitstr, auraSlots[i])
-- effects on you).
-- 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 if name then
texture, stacks, dtype = icon, count, dispelType texture, stacks, dtype = icon, count, dispelType
else else
@@ -1834,8 +1938,9 @@ function pfUI.uf:RefreshUnit(unit, component)
local present = pfUI.uf.dispelPresent or {} local present = pfUI.uf.dispelPresent or {}
pfUI.uf.dispelPresent = present pfUI.uf.dispelPresent = present
for k in pairs(present) do present[k] = nil end for k in pairs(present) do present[k] = nil end
for i=1,16 do local n = ScanAuraSlots(unitstr, "HARMFUL", auraSlots)
local name, _, _, dispelType = C_UnitAuras.UnitDebuff(unitstr, i) for i=1,n do
local name, _, _, dispelType = C_UnitAuras.UnitAuraBySlot(unitstr, auraSlots[i])
if name and dispelType and dispelType ~= "" then present[dispelType] = true end if name and dispelType and dispelType ~= "" then present[dispelType] = true end
end end
@@ -1917,7 +2022,7 @@ function pfUI.uf:RefreshUnit(unit, component)
if not unit.indicator_custom and unit.config.buff_indicator == "1" then if not unit.indicator_custom and unit.config.buff_indicator == "1" then
unit.indicator_custom = {} unit.indicator_custom = {}
for k, v in pairs({strsplit("#", unit.config.custom_indicator)}) do for k, v in pairs({strsplit("#", unit.config.custom_indicator)}) do
unit.indicator_custom[k] = string.lower(v) unit.indicator_custom[k] = v:lower()
end end
elseif not unit.indicator_custom then elseif not unit.indicator_custom then
unit.indicator_custom = {} unit.indicator_custom = {}
@@ -1925,21 +2030,16 @@ function pfUI.uf:RefreshUnit(unit, component)
local pos = 1 local pos = 1
if table.getn(unit.indicators) > 0 then if table.getn(unit.indicators) > 0 then
local i = 1 local n = ScanAuraSlots(unitstr, "HELPFUL", auraSlots)
while true do for i=1,n do
local name, icon, count, _, _, expirationTime = C_UnitAuras.UnitBuff(unitstr, i) local name, icon, count, _, _, expirationTime = C_UnitAuras.UnitAuraBySlot(unitstr, auraSlots[i])
if not name then break end if not name then break end
local texLower = string.lower(icon)
local timeleft = expirationTime > 0 and (expirationTime - GetTime()) or nil local timeleft = expirationTime > 0 and (expirationTime - GetTime()) or nil
for _, filter in pairs(unit.indicators) do for _, filter in pairs(unit.indicators) do
if filter == texLower then if filter.icon == icon and filter.name == name then
local hot = HOT_INDICATORS[texLower] if filter.predict then
if hot and string.lower(name) ~= hot.name then local start, duration, prediction = libpredict:GetHotDuration(unitstr, filter.predict)
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, icon, timeleft or prediction, count, tonumber(start), tonumber(duration)) pfUI.uf:AddIcon(unit, pos, icon, timeleft or prediction, count, tonumber(start), tonumber(duration))
else else
pfUI.uf:AddIcon(unit, pos, icon, timeleft, count) pfUI.uf:AddIcon(unit, pos, icon, timeleft, count)
@@ -1948,17 +2048,16 @@ function pfUI.uf:RefreshUnit(unit, component)
break break
end end
end end
i = i + 1
end end
end end
if table.getn(unit.indicator_custom) > 0 then if table.getn(unit.indicator_custom) > 0 then
local ai = 1 local n = ScanAuraSlots(unitstr, "HELPFUL", auraSlots)
while true do for i=1,n do
local name, icon, count, _, _, expirationTime = C_UnitAuras.UnitBuff(unitstr, ai) local name, icon, count, _, _, expirationTime = C_UnitAuras.UnitAuraBySlot(unitstr, auraSlots[i])
if not name then break end if not name then break end
local timeleft = expirationTime > 0 and (expirationTime - GetTime()) or nil local timeleft = expirationTime > 0 and (expirationTime - GetTime()) or nil
local lowerName = string.lower(name) local lowerName = name:lower()
for _, filter in pairs(unit.indicator_custom) do for _, filter in pairs(unit.indicator_custom) do
if filter == lowerName then if filter == lowerName then
pfUI.uf:AddIcon(unit, pos, icon, timeleft, count) pfUI.uf:AddIcon(unit, pos, icon, timeleft, count)
@@ -1966,16 +2065,16 @@ function pfUI.uf:RefreshUnit(unit, component)
break break
end end
end end
ai = ai + 1
end end
local debuffFilter = unit.config.selfdebuff == "1" and "PLAYER" or nil local debuffFilter = unit.config.selfdebuff == "1" and "HARMFUL|PLAYER" or "HARMFUL"
for i=1,16 do -- scan for custom debuffs n = ScanAuraSlots(unitstr, debuffFilter, auraSlots)
local name, icon, count, _, _, expirationTime = C_UnitAuras.UnitDebuff(unitstr, i, debuffFilter) for i=1,n do -- scan for custom debuffs
local name, icon, count, _, _, expirationTime = C_UnitAuras.UnitAuraBySlot(unitstr, auraSlots[i])
if name then if name then
local timeleft = expirationTime > 0 and (expirationTime - GetTime()) or nil local timeleft = expirationTime > 0 and (expirationTime - GetTime()) or nil
for _, filter in pairs(unit.indicator_custom) do for _, filter in pairs(unit.indicator_custom) do
if filter == string.lower(name) then if filter == name:lower() then
pfUI.uf:AddIcon(unit, pos, icon, timeleft, count) pfUI.uf:AddIcon(unit, pos, icon, timeleft, count)
pos = pos + 1 pos = pos + 1
break break
@@ -2184,17 +2283,17 @@ function pfUI.uf:EnableClickCast()
local action = pfUI_config.unitframes["clickcast"..bconf..mconf] local action = pfUI_config.unitframes["clickcast"..bconf..mconf]
if action and action ~= "" then if action and action ~= "" then
local prefix = modifier ~= "" and (modifier .. "-") or "" local prefix = modifier ~= "" and (modifier .. "-") or ""
local low = string.lower(action) local low = action:lower()
if low == "menu" then if low == "menu" then
self:SetAttribute(prefix .. "type" .. bid, "menu") self:SetAttribute(prefix .. "type" .. bid, "menu")
elseif low == "target" then elseif low == "target" then
self:SetAttribute(prefix .. "type" .. bid, "target") self:SetAttribute(prefix .. "type" .. bid, "target")
elseif low == "focus" then elseif low == "focus" then
self:SetAttribute(prefix .. "type" .. bid, "focus") self:SetAttribute(prefix .. "type" .. bid, "focus")
elseif string.find(low, "^macro:") then elseif low:find("^macro:") then
self:SetAttribute(prefix .. "type" .. bid, "macro") self:SetAttribute(prefix .. "type" .. bid, "macro")
self:SetAttribute(prefix .. "macro" .. bid, string.gsub(string.sub(action, 7), "^%s+", "")) self:SetAttribute(prefix .. "macro" .. bid, action:sub(7):gsub("^%s+", ""))
elseif string.find(action, "^/") then elseif action:find("^/") then
self:SetAttribute(prefix .. "type" .. bid, "macro") self:SetAttribute(prefix .. "type" .. bid, "macro")
self:SetAttribute(prefix .. "macrotext" .. bid, action) self:SetAttribute(prefix .. "macrotext" .. bid, action)
else else
@@ -2233,7 +2332,7 @@ function pfUI.uf:AddIcon(frame, pos, icon, timeleft, stacks, start, duration)
-- Check if parent frame has cooldown animation enabled -- Check if parent frame has cooldown animation enabled
local parent_cooldown_anim = frame.config and tonumber(frame.config.cooldown_anim) or 1 local parent_cooldown_anim = frame.config and tonumber(frame.config.cooldown_anim) or 1
if parent_cooldown_anim == 1 then if parent_cooldown_anim == 1 then
frame.icon[pos].cd = CreateFrame(COOLDOWN_FRAME_TYPE, nil, frame.icon[pos]) frame.icon[pos].cd = CreateFrame("Model", nil, frame.icon[pos])
else else
frame.icon[pos].cd = CreateFrame("Frame", nil, frame.icon[pos]) frame.icon[pos].cd = CreateFrame("Frame", nil, frame.icon[pos])
frame.icon[pos].cd.AdvanceTime = DoNothing frame.icon[pos].cd.AdvanceTime = DoNothing
@@ -2332,175 +2431,122 @@ function pfUI.uf:SetupBuffIndicators(config)
if config.show_buffs == "1" then -- buffs if config.show_buffs == "1" then -- buffs
if myclass == "DRUID" then if myclass == "DRUID" then
-- Mark of the Wild AddIndicator(indicators, 1126) -- Mark of the Wild
table.insert(indicators, "interface\\icons\\spell_nature_regeneration") AddIndicator(indicators, 21849) -- Gift of the Wild
-- Gift of the Wild AddIndicator(indicators, 467) -- Thorns
table.insert(indicators, "interface\\icons\\spell_nature_giftofthewild")
-- Thorns
table.insert(indicators, "interface\\icons\\spell_nature_thorns")
end end
if myclass == "PRIEST" then if myclass == "PRIEST" then
-- Prayer Of Fortitude" AddIndicator(indicators, 1243) -- Power Word: Fortitude
table.insert(indicators, "interface\\icons\\spell_holy_wordfortitude") AddIndicator(indicators, 21562) -- Prayer of Fortitude
table.insert(indicators, "interface\\icons\\spell_holy_prayeroffortitude") AddIndicator(indicators, 6386) -- Divine Spirit
-- Prayer of Spirit AddIndicator(indicators, 27681) -- Prayer of Spirit
table.insert(indicators, "interface\\icons\\spell_holy_divinespirit") AddIndicator(indicators, 976) -- Shadow Protection
table.insert(indicators, "interface\\icons\\spell_holy_prayerofspirit") AddIndicator(indicators, 27683) -- Prayer of Shadow Protection
-- Shadow Protection AddIndicator(indicators, 6346) -- Fear Ward
table.insert(indicators, "interface\\icons\\spell_shadow_antishadow")
table.insert(indicators, "interface\\icons\\spell_holy_prayerofshadowprotection")
-- Fear Ward
table.insert(indicators, "interface\\icons\\spell_holy_excorcism")
end end
if myclass == "PALADIN" then if myclass == "PALADIN" then
-- Blessing of Salvation AddIndicator(indicators, 1038) -- Blessing of Salvation
table.insert(indicators, "interface\\icons\\spell_holy_greaterblessingofsalvation") AddIndicator(indicators, 25895) -- Greater Blessing of Salvation
table.insert(indicators, "interface\\icons\\spell_holy_sealofsalvation") AddIndicator(indicators, 19742) -- Blessing of Wisdom
-- Blessing of Wisdom AddIndicator(indicators, 25894) -- Greater Blessing of Wisdom
table.insert(indicators, "interface\\icons\\spell_holy_sealofwisdom") AddIndicator(indicators, 20204) -- Blessing of Sanctuary
table.insert(indicators, "interface\\icons\\spell_holy_greaterblessingofwisdom") AddIndicator(indicators, 25899) -- Greater Blessing of Sanctuary
-- Blessing of Sanctuary AddIndicator(indicators, 20217) -- Blessing of Kings
table.insert(indicators, "interface\\icons\\spell_nature_lightningshield") AddIndicator(indicators, 25898) -- Greater Blessing of Kings
table.insert(indicators, "interface\\icons\\spell_holy_greaterblessingofsanctuary") AddIndicator(indicators, 19740) -- Blessing of Might
-- Blessing of Kings AddIndicator(indicators, 25782) -- Greater Blessing of Might
table.insert(indicators, "interface\\icons\\spell_magic_magearmor") AddIndicator(indicators, 19977) -- Blessing of Light
table.insert(indicators, "interface\\icons\\spell_magic_greaterblessingofkings") AddIndicator(indicators, 25890) -- Greater Blessing of Light
-- Blessing of Might AddIndicator(indicators, 6940) -- Hand of Sacrifice
table.insert(indicators, "interface\\icons\\spell_holy_fistofjustice") AddIndicator(indicators, 45801) -- Greater Blessing of Sacrifice
table.insert(indicators, "interface\\icons\\spell_holy_greaterblessingofkings") AddIndicator(indicators, 1044) -- Hand of Freedom
-- Blessing of Light AddIndicator(indicators, 1022) -- Hand of Protection
table.insert(indicators, "interface\\icons\\spell_holy_prayerofhealing02")
table.insert(indicators, "interface\\icons\\spell_holy_greaterblessingoflight")
-- Blessing of Sacrifice
table.insert(indicators, "interface\\icons\\spell_holy_sealofsacrifice")
-- Blessing of Freedom
table.insert(indicators, "interface\\icons\\spell_holy_sealofvalor")
-- Blessing of Protection
table.insert(indicators, "interface\\icons\\spell_holy_sealofprotection")
end end
if myclass == "WARLOCK" then if myclass == "WARLOCK" then
-- Fire Shield AddIndicator(indicators, 1167) -- Fire Shield
table.insert(indicators, "interface\\icons\\spell_fire_firearmor") AddIndicator(indicators, 6307) -- Blood Pact
-- Blood Pact AddIndicator(indicators, 20707) -- Soulstone Resurrection
table.insert(indicators, "interface\\icons\\spell_shadow_bloodboil") AddIndicator(indicators, 5697) -- Unending Breath
-- Soulstone AddIndicator(indicators, 2970) -- Detect Invisibility
table.insert(indicators, "interface\\icons\\spell_shadow_soulgem") AddIndicator(indicators, 11743) -- Detect Greater Invisibility
-- Unending Breath AddIndicator(indicators, 132) -- Detect Lesser Invisibility
table.insert(indicators, "interface\\icons\\spell_shadow_demonbreath") AddIndicator(indicators, 19480) -- Paranoia
-- Detect Greater Invisibility or Detect Invisibility
table.insert(indicators, "interface\\icons\\spell_shadow_detectinvisibility")
-- Detect Lesser Invisibility
table.insert(indicators, "interface\\icons\\spell_shadow_detectlesserinvisibility")
-- Paranoia
table.insert(indicators, "interface\\icons\\Spell_Shadow_AuraOfDarkness")
end end
if myclass == "WARRIOR" then if myclass == "WARRIOR" then
-- Battle Shout AddIndicator(indicators, 5242) -- Battle Shout
table.insert(indicators, "interface\\icons\\ability_warrior_battleshout") AddIndicator(indicators, 45580) -- Commanding Shout
-- Commanding Shout (TBC)
table.insert(indicators, "interface\\icons\\ability_warrior_rallyingcry")
end end
if myclass == "MAGE" then if myclass == "MAGE" then
-- Arcane Intellect AddIndicator(indicators, 1459) -- Arcane Intellect
table.insert(indicators, "interface\\icons\\spell_holy_magicalsentry") AddIndicator(indicators, 23028) -- Arcane Brilliance
table.insert(indicators, "interface\\icons\\spell_holy_arcaneintellect") AddIndicator(indicators, 604) -- Dampen Magic
-- Dampen Magic AddIndicator(indicators, 1008) -- Amplify Magic
table.insert(indicators, "interface\\icons\\spell_nature_abolishmagic")
-- Amplify Magic
table.insert(indicators, "interface\\icons\\spell_holy_flashheal")
end end
if myclass == "HUNTER" then if myclass == "HUNTER" then
-- Aspect of the Wild AddIndicator(indicators, 20043) -- Aspect of the Wild
table.insert(indicators, "interface\\icons\\spell_nature_protectionformnature") AddIndicator(indicators, 13159) -- Aspect of the Pack
-- Aspect of the Pack
table.insert(indicators, "interface\\icons\\ability_mount_whitetiger")
-- Misdirection (TBC)
table.insert(indicators, "interface\\icons\\ability_hunter_misdirection")
end end
if myclass == "SHAMAN" then if myclass == "SHAMAN" then
-- Earth Shield (TBC) AddIndicator(indicators, 45525) -- Earth Shield
table.insert(indicators, "interface\\icons\\spell_nature_skinofearth")
end end
end end
if config.show_procs == "1" then -- procs if config.show_procs == "1" then -- procs
if myclass == "SHAMAN" or config.all_procs == "1" then if myclass == "SHAMAN" or config.all_procs == "1" then
-- Ancestral Fortitude AddIndicator(indicators, 16177) -- Ancestral Fortitude
table.insert(indicators, "interface\\icons\\spell_nature_undyingstrength") AddIndicator(indicators, 29202) -- Healing Way
-- Healing Way -- Totemic Power is four auras, one per totem school, each with its own icon
table.insert(indicators, "interface\\icons\\spell_nature_healingway") AddIndicator(indicators, 28824)
-- Totemic Power (known issue: one conflicts with Blessed Sunfruit buff) AddIndicator(indicators, 28825)
table.insert(indicators, "interface\\icons\\spell_holy_spiritualguidence") AddIndicator(indicators, 28826)
table.insert(indicators, "interface\\icons\\spell_holy_devotion") AddIndicator(indicators, 28827)
table.insert(indicators, "interface\\icons\\spell_holy_holynova")
table.insert(indicators, "interface\\icons\\spell_magic_magearmor")
end end
if myclass == "PRIEST" or config.all_procs == "1" then if myclass == "PRIEST" or config.all_procs == "1" then
-- Inspiration AddIndicator(indicators, 14893) -- Inspiration
table.insert(indicators, "interface\\icons\\inv_shield_06")
end end
end end
if config.show_hots == "1" then -- hots if config.show_hots == "1" then -- hots
if myclass == "PRIEST" or config.all_hots == "1" then if myclass == "PRIEST" or config.all_hots == "1" then
-- Renew AddIndicator(indicators, 139, "Renew") -- Renew
table.insert(indicators, "interface\\icons\\spell_holy_renew") AddIndicator(indicators, 17) -- Power Word: Shield
-- Power Word: Shield
table.insert(indicators, "interface\\icons\\spell_holy_powerwordshield")
-- Prayer of Mending (TBC)
table.insert(indicators, "interface\\icons\\spell_holy_prayerofmendingtga")
end end
if myclass == "DRUID" or config.all_hots == "1" then if myclass == "DRUID" or config.all_hots == "1" then
-- Regrowth AddIndicator(indicators, 8936, "Regr") -- Regrowth
table.insert(indicators, "interface\\icons\\spell_nature_resistnature") AddIndicator(indicators, 774, "Reju") -- Rejuvenation
-- Rejuvenation
table.insert(indicators, "interface\\icons\\spell_nature_rejuvenation")
-- Lifebloom
table.insert(indicators, "interface\\icons\\inv_misc_herb_felblossom")
end end
end end
if config.show_totems == "1" and myclass == "SHAMAN" then -- totems if config.show_totems == "1" and myclass == "SHAMAN" then -- totems
-- Strength of Earth Totem -- the aura each totem applies, not the cast that drops it: they share an
table.insert(indicators, "interface\\icons\\spell_nature_earthbindtotem") -- icon but the totem's own name carries a " Totem" suffix the aura lacks
-- Stoneskin Totem AddIndicator(indicators, 8076) -- Strength of Earth
table.insert(indicators, "interface\\icons\\spell_nature_stoneskintotem") AddIndicator(indicators, 8072) -- Stoneskin
-- Mana Spring Totem AddIndicator(indicators, 5677) -- Mana Spring
table.insert(indicators, "interface\\icons\\spell_nature_manaregentotem") AddIndicator(indicators, 16191) -- Mana Tide
-- Mana Tide Totem AddIndicator(indicators, 5672) -- Healing Stream
table.insert(indicators, "interface\\icons\\spell_frost_summonwaterelemental") AddIndicator(indicators, 25909) -- Tranquil Air
-- Healing Spring Totem AddIndicator(indicators, 8836) -- Grace of Air
table.insert(indicators, "interface\\icons\\inv_spear_04") AddIndicator(indicators, 8177) -- Grounding Totem
-- Tranquil Air Totem AddIndicator(indicators, 10596) -- Nature Resistance
table.insert(indicators, "interface\\icons\\spell_nature_brilliance") AddIndicator(indicators, 8185) -- Fire Resistance
-- Grace of Air Totem AddIndicator(indicators, 8182) -- Frost Resistance
table.insert(indicators, "interface\\icons\\spell_nature_invisibilitytotem")
-- Grounding Totem
table.insert(indicators, "interface\\icons\\spell_nature_groundingtotem")
-- Nature Resistance Totem
table.insert(indicators, "interface\\icons\\spell_nature_natureresistancetotem")
-- Fire Resistance Totem
table.insert(indicators, "interface\\icons\\spell_fireresistancetotem_01")
-- Frost Resistance Totem
table.insert(indicators, "interface\\icons\\spell_frostresistancetotem_01")
end end
return indicators return indicators
end end
local function abbrevname(t) local function abbrevname(t)
return string.sub(t,1,1)..". " return t:sub(1,1)..". "
end end
function pfUI.uf:GetNameString(unitstr) function pfUI.uf:GetNameString(unitstr)
@@ -2510,12 +2556,12 @@ function pfUI.uf:GetNameString(unitstr)
-- first try to only abbreviate the first word -- first try to only abbreviate the first word
if abbrev and name and strlen(name) > size then if abbrev and name and strlen(name) > size then
name = string.gsub(name, "^(%S+) ", abbrevname) name = name:gsub("^(%S+) ", abbrevname)
end end
-- abbreviate all if it still doesn't fit -- abbreviate all if it still doesn't fit
if abbrev and name and strlen(name) > size then if abbrev and name and strlen(name) > size then
name = string.gsub(name, "(%S+) ", abbrevname) name = name:gsub("(%S+) ", abbrevname)
end end
return name return name
-43
View File
@@ -1,43 +0,0 @@
-- load pfUI environment
setfenv(1, pfUI:GetEnvironment())
-- [[ Constants ]]--
EVENTS_MINIMAP_ZONE_UPDATE = {"PLAYER_ENTERING_WORLD", "MINIMAP_ZONE_CHANGED"}
MICRO_BUTTONS = {
'CharacterMicroButton', 'SpellbookMicroButton', 'TalentMicroButton',
'QuestLogMicroButton', 'SocialsMicroButton', 'WorldMapMicroButton',
'MainMenuMicroButton', 'HelpMicroButton',
}
NAMEPLATE_OBJECTORDER = { "border", "glow", "name", "level", "levelicon", "raidicon" }
NAMEPLATE_FRAMETYPE = "Button"
MINIMAP_TRACKING_FRAME = _G.MiniMapTrackingFrame
FRIENDS_NAME_LOCATION = "ButtonTextNameLocation"
COOLDOWN_FRAME_TYPE = "Model"
LOOT_BUTTON_FRAME_TYPE = "LootButton"
PLAYER_BUFF_START_ID = -1
ACTIONBAR_SECURE_TEMPLATE_BAR = nil
ACTIONBAR_SECURE_TEMPLATE_BUTTON = nil
--[[ Vanilla API Extensions ]]--
do -- RunMacroText
local obj = { ["GetText"] = function(self) return self.text end }
obj = setmetatable(obj, {__index = function(tab,key)
local value = function() return end
rawset(tab,key,value)
return value
end})
function RunMacroText(text)
obj.text = text
ChatEdit_ParseText(obj, 1)
end
end
-2358
View File
File diff suppressed because it is too large Load Diff
+2377
View File
File diff suppressed because it is too large Load Diff
+2991
View File
File diff suppressed because it is too large Load Diff
-1
View File
@@ -670,7 +670,6 @@ pfUI_translation["deDE"] = {
["Scale"] = nil, ["Scale"] = nil,
["Scale Border On HiDPI Displays"] = nil, ["Scale Border On HiDPI Displays"] = nil,
["Scaling"] = nil, ["Scaling"] = nil,
["Scan Macros For Spells"] = nil,
["Screen Edge Glow Intensity"] = nil, ["Screen Edge Glow Intensity"] = nil,
["Screen Resolution"] = nil, ["Screen Resolution"] = nil,
["Screenshot"] = nil, ["Screenshot"] = nil,
-1
View File
@@ -683,7 +683,6 @@ pfUI_translation["enUS"] = {
["Scale"] = nil, ["Scale"] = nil,
["Scale Border On HiDPI Displays"] = nil, ["Scale Border On HiDPI Displays"] = nil,
["Scaling"] = nil, ["Scaling"] = nil,
["Scan Macros For Spells"] = nil,
["Screen Edge Glow Intensity"] = nil, ["Screen Edge Glow Intensity"] = nil,
["Screen Resolution"] = nil, ["Screen Resolution"] = nil,
["Screenshot"] = nil, ["Screenshot"] = nil,
-1
View File
@@ -670,7 +670,6 @@ pfUI_translation["esES"] = {
["Scale"] = "Escala", ["Scale"] = "Escala",
["Scale Border On HiDPI Displays"] = "Escalar los bordes en las pantallas con DPI alto", ["Scale Border On HiDPI Displays"] = "Escalar los bordes en las pantallas con DPI alto",
["Scaling"] = "Escalada", ["Scaling"] = "Escalada",
["Scan Macros For Spells"] = nil,
["Screen Edge Glow Intensity"] = "Intensidad de brillo en los bordes de la pantalla", ["Screen Edge Glow Intensity"] = "Intensidad de brillo en los bordes de la pantalla",
["Screen Resolution"] = "Resolución de pantalla", ["Screen Resolution"] = "Resolución de pantalla",
["Screenshot"] = "Captura de pantalla", ["Screenshot"] = "Captura de pantalla",
-1
View File
@@ -670,7 +670,6 @@ pfUI_translation["frFR"] = {
["Scale"] = "Échelle", ["Scale"] = "Échelle",
["Scale Border On HiDPI Displays"] = "Échelle de bordure sur les écrans HiDPI", ["Scale Border On HiDPI Displays"] = "Échelle de bordure sur les écrans HiDPI",
["Scaling"] = "Mise à l'échelle", ["Scaling"] = "Mise à l'échelle",
["Scan Macros For Spells"] = nil,
["Screen Edge Glow Intensity"] = nil, ["Screen Edge Glow Intensity"] = nil,
["Screen Resolution"] = "Résolution d'écran", ["Screen Resolution"] = "Résolution d'écran",
["Screenshot"] = "Imprime écran", ["Screenshot"] = "Imprime écran",
-1
View File
@@ -670,7 +670,6 @@ pfUI_translation["koKR"] = {
["Scale"] = nil, ["Scale"] = nil,
["Scale Border On HiDPI Displays"] = nil, ["Scale Border On HiDPI Displays"] = nil,
["Scaling"] = nil, ["Scaling"] = nil,
["Scan Macros For Spells"] = nil,
["Screen Edge Glow Intensity"] = nil, ["Screen Edge Glow Intensity"] = nil,
["Screen Resolution"] = "화면 해상도", ["Screen Resolution"] = "화면 해상도",
["Screenshot"] = nil, ["Screenshot"] = nil,
-1
View File
@@ -670,7 +670,6 @@ pfUI_translation["ruRU"] = {
["Scale"] = "Масштаб", ["Scale"] = "Масштаб",
["Scale Border On HiDPI Displays"] = "Масштабировать границы на HiDPI мониторах", ["Scale Border On HiDPI Displays"] = "Масштабировать границы на HiDPI мониторах",
["Scaling"] = "Масштаб интерфейса", ["Scaling"] = "Масштаб интерфейса",
["Scan Macros For Spells"] = nil,
["Screen Edge Glow Intensity"] = "Интенсивность свечения на краях экрана", ["Screen Edge Glow Intensity"] = "Интенсивность свечения на краях экрана",
["Screen Resolution"] = "Разрешение экрана", ["Screen Resolution"] = "Разрешение экрана",
["Screenshot"] = "Снимок экрана", ["Screenshot"] = "Снимок экрана",
-1
View File
@@ -670,7 +670,6 @@ pfUI_translation["zhCN"] = {
["Scale"] = "比例", ["Scale"] = "比例",
["Scale Border On HiDPI Displays"] = "缩放高DPI显示器上的边框", ["Scale Border On HiDPI Displays"] = "缩放高DPI显示器上的边框",
["Scaling"] = "UI缩放", ["Scaling"] = "UI缩放",
["Scan Macros For Spells"] = "扫描宏命令中的法术",
["Screen Edge Glow Intensity"] = "屏幕边缘发光强度", ["Screen Edge Glow Intensity"] = "屏幕边缘发光强度",
["Screen Resolution"] = "屏幕分辨率", ["Screen Resolution"] = "屏幕分辨率",
["Screenshot"] = "屏幕截图", ["Screenshot"] = "屏幕截图",
-1
View File
@@ -670,7 +670,6 @@ pfUI_translation["zhTW"] = {
["Scale"] = "比例", ["Scale"] = "比例",
["Scale Border On HiDPI Displays"] = nil, ["Scale Border On HiDPI Displays"] = nil,
["Scaling"] = nil, ["Scaling"] = nil,
["Scan Macros For Spells"] = nil,
["Screen Edge Glow Intensity"] = nil, ["Screen Edge Glow Intensity"] = nil,
["Screen Resolution"] = "螢幕解析度", ["Screen Resolution"] = "螢幕解析度",
["Screenshot"] = nil, ["Screenshot"] = nil,
-2
View File
@@ -70,9 +70,7 @@
<Include file="..\modules\addoncompat.lua"/> <Include file="..\modules\addoncompat.lua"/>
<Include file="..\modules\energytick.lua"/> <Include file="..\modules\energytick.lua"/>
<Include file="..\modules\totems.lua"/> <Include file="..\modules\totems.lua"/>
<Include file="..\modules\macrotweak.lua"/>
<Include file="..\modules\macroicons.lua"/> <Include file="..\modules\macroicons.lua"/>
<Include file="..\modules\turtle-wow.lua"/>
<Include file="..\modules\superwow.lua"/> <Include file="..\modules\superwow.lua"/>
<Include file="..\modules\innervatecall.lua"/> <Include file="..\modules\innervatecall.lua"/>
<Include file="..\modules\nampower.lua"/> <Include file="..\modules\nampower.lua"/>
+1 -7
View File
@@ -35,12 +35,6 @@
<Include file="..\skins\blizzard\tooltips.lua"/> <Include file="..\skins\blizzard\tooltips.lua"/>
<Include file="..\skins\blizzard\tabard.lua"/> <Include file="..\skins\blizzard\tabard.lua"/>
<Include file="..\skins\blizzard\itemtext.lua"/> <Include file="..\skins\blizzard\itemtext.lua"/>
<Include file="..\skins\blizzard\lft.lua"/>
<Include file="..\skins\blizzard\turtle_shop.lua"/>
<!-- Turtle WoW -->
<Include file="..\skins\blizzard\barbershop.lua"/>
<Include file="..\skins\blizzard\transmog.lua"/>
<Include file="..\skins\blizzard\ebc.lua"/>
<!-- Turtle WoW skins live in init\turtle.xml (pfUI_Turtle.toc only). -->
</Ui> </Ui>
+1 -1
View File
@@ -1,3 +1,3 @@
<Ui xmlns="http://www.blizzard.com/wow/ui/"> <Ui xmlns="http://www.blizzard.com/wow/ui/">
<Include file="..\compat\vanilla.lua"/> <Include file="..\env\tables_stock.lua"/>
</Ui> </Ui>
+14
View File
@@ -0,0 +1,14 @@
<Ui xmlns="http://www.blizzard.com/wow/ui/">
<!-- Turtle WoW only: pulled in by pfUI_Turtle.toc, which ClassicAPI selects
when it detects a Turtle client. Everything in here targets frames that
only a Turtle client ever creates. -->
<Include file="..\env\tables_turtle.lua"/>
<Include file="..\skins\blizzard\lft.lua"/>
<Include file="..\skins\blizzard\turtle_shop.lua"/>
<Include file="..\skins\blizzard\barbershop.lua"/>
<Include file="..\skins\blizzard\transmog.lua"/>
<Include file="..\skins\blizzard\ebc.lua"/>
<Include file="..\modules\turtle-wow.lua"/>
</Ui>
+37 -14
View File
@@ -18,12 +18,7 @@ local ItemQuality = Enum.ItemQuality
local function SortCategoryPrefix(itemId, classID, quality) local function SortCategoryPrefix(itemId, classID, quality)
if itemId == HEARTHSTONE_ITEM_ID then return "00" end if itemId == HEARTHSTONE_ITEM_ID then return "00" end
if quality == ItemQuality.Poor then return "13" end -- gray always last if quality == ItemQuality.Poor then return "13" end -- gray always last
if classID == ItemClass.Weapon or classID == ItemClass.Armor then if classID == ItemClass.Weapon or classID == ItemClass.Armor then return "01" end
if quality and quality >= ItemQuality.Epic then return "01" end -- Epic+ gear
if quality == ItemQuality.Rare then return "02" end -- Rare gear
if quality == ItemQuality.Uncommon then return "03" end -- Uncommon gear
return "04" -- Common/poor gear
end
if classID == ItemClass.Consumable then return "05" end if classID == ItemClass.Consumable then return "05" end
if classID == ItemClass.Reagent then return "06" end if classID == ItemClass.Reagent then return "06" end
if classID == ItemClass.Tradegoods then return "07" end if classID == ItemClass.Tradegoods then return "07" end
@@ -42,12 +37,40 @@ local function SortCountSuffix(count)
return string.sub(s, -6) return string.sub(s, -6)
end end
local function SortKey(itemId, name, classID, subClassID, quality, count) -- Gear's slot order, keyed by the equipLoc token (INVTYPE_*). Not the
-- Zero-pad the class/subclass so the secondary grouping sorts numerically -- token's alphabetical order, which puts chest before head, and not its
-- (as a string, "10" would otherwise precede "2"). -- INVTYPE number, which parts robe (20) from chest (5). Same order as
return SortCategoryPrefix(itemId, classID, quality) -- ClassicAPI's C_Container.SortBags, taken from Baganator: weapons by hand,
.. string.format("%02d|%02d|", classID or 99, subClassID or 99) -- ranged and ammo, then armor with shields and off-hands first, head to
.. (name or "zzz") .. "|" .. SortCountSuffix(count) -- feet, then jewelry, shirt and tabard.
local SLOT_ORDER = {
"INVTYPE_2HWEAPON", "INVTYPE_WEAPON", "INVTYPE_WEAPONMAINHAND",
"INVTYPE_SHIELD", "INVTYPE_HOLDABLE", "INVTYPE_RANGEDRIGHT",
"INVTYPE_WEAPONOFFHAND", "INVTYPE_RANGED", "INVTYPE_THROWN",
"INVTYPE_AMMO", "INVTYPE_QUIVER", "INVTYPE_RELIC",
"INVTYPE_HEAD", "INVTYPE_SHOULDER", "INVTYPE_CLOAK", "INVTYPE_CHEST",
"INVTYPE_ROBE", "INVTYPE_WRIST", "INVTYPE_HAND", "INVTYPE_WAIST",
"INVTYPE_LEGS", "INVTYPE_FEET",
"INVTYPE_NECK", "INVTYPE_FINGER", "INVTYPE_TRINKET", "INVTYPE_BODY",
"INVTYPE_TABARD", "INVTYPE_BAG",
}
local SLOT_RANK = {}
for i, token in ipairs(SLOT_ORDER) do SLOT_RANK[token] = i end
local function SortKey(itemId, name, classID, subClassID, quality, count, equipLoc)
-- Zero-pad the numeric fields so they sort numerically as strings ("10"
-- would otherwise precede "2").
local key = SortCategoryPrefix(itemId, classID, quality)
.. string.format("%02d|", classID or 99)
if classID == ItemClass.Weapon or classID == ItemClass.Armor then
-- Gear: slot, then subtype, then best quality first, so all helms sit
-- together, then all shoulders.
key = key .. string.format("%02d|%02d|%d|",
SLOT_RANK[equipLoc] or 99, subClassID or 99, 9 - (quality or 0))
else
key = key .. string.format("%02d|", subClassID or 99)
end
return key .. (name or "zzz") .. "|" .. SortCountSuffix(count)
end end
local function ClearSortData() local function ClearSortData()
@@ -168,10 +191,10 @@ local function BuildSortGrid()
-- sit at positions 12/13. We categorize on those numeric class IDs -- sit at positions 12/13. We categorize on those numeric class IDs
-- rather than the localized itemType/itemSubType strings. (pfUI's -- rather than the localized itemType/itemSubType strings. (pfUI's
-- shimmed global GetItemInfo is only 10 fields and lacks them.) -- shimmed global GetItemInfo is only 10 fields and lacks them.)
local name, _, quality, _, _, _, _, _, _, _, _, classID, subClassID = C_Item.GetItemInfo(itemId) local name, _, quality, _, _, _, _, _, equipLoc, _, _, classID, subClassID = C_Item.GetItemInfo(itemId)
local _, count = GetContainerItemInfo(bag, slot) local _, count = GetContainerItemInfo(bag, slot)
local item = { local item = {
key = SortKey(itemId, name, classID, subClassID, quality, count), key = SortKey(itemId, name, classID, subClassID, quality, count, equipLoc),
-- vanilla items carry at most one family bit, so equality -- vanilla items carry at most one family bit, so equality
-- against a bag family suffices (no bit.band needed). -- against a bag family suffices (no bit.band needed).
family = C_Item.GetItemFamily(itemId) or 0, family = C_Item.GetItemFamily(itemId) or 0,
+1 -1
View File
@@ -937,7 +937,7 @@ if hasNampower then
local numMissed = arg7 or 0 local numMissed = arg7 or 0
-- Fire registered SPELL_GO_SELF hooks BEFORE miss guard -- Fire registered SPELL_GO_SELF hooks BEFORE miss guard
-- (Swingtimer needs to see ALL casts, even misses, for swing reset) -- (Swingtimer needs to see ALL casts, even misses, to clear its queue color)
if event == "SPELL_GO_SELF" and pfUI.libdebuff_spell_go_hooks then if event == "SPELL_GO_SELF" and pfUI.libdebuff_spell_go_hooks then
for _, fn in pairs(pfUI.libdebuff_spell_go_hooks) do for _, fn in pairs(pfUI.libdebuff_spell_go_hooks) do
fn(spellId, arg1, arg2, arg3, arg4, arg5, arg6, arg7) fn(spellId, arg1, arg2, arg3, arg4, arg5, arg6, arg7)
+2 -2
View File
@@ -10,8 +10,8 @@ local libhealth = CreateFrame("Frame")
libhealth.enabled = true libhealth.enabled = true
libhealth.reqhit = 4 libhealth.reqhit = 4
libhealth.reqdmg = 5 libhealth.reqdmg = 5
libhealth:RegisterEvent("UNIT_HEALTH") libhealth:RegisterUnitEvent("UNIT_HEALTH", "target")
libhealth:RegisterEvent("UNIT_COMBAT") libhealth:RegisterUnitEvent("UNIT_COMBAT", "target")
libhealth:RegisterEvent("PLAYER_TARGET_CHANGED") libhealth:RegisterEvent("PLAYER_TARGET_CHANGED")
libhealth:RegisterEvent("PLAYER_ENTERING_WORLD") libhealth:RegisterEvent("PLAYER_ENTERING_WORLD")
libhealth:SetScript("OnEvent", function() libhealth:SetScript("OnEvent", function()
+1 -1
View File
@@ -1134,7 +1134,7 @@ libpredict.sender:RegisterEvent("SPELL_HEAL_BY_SELF")
libpredict.sender:RegisterEvent("SPELL_HEAL_BY_OTHER") -- populates foreignCache for other healers libpredict.sender:RegisterEvent("SPELL_HEAL_BY_OTHER") -- populates foreignCache for other healers
-- force cache updates -- force cache updates
libpredict.sender:RegisterEvent("UNIT_INVENTORY_CHANGED") libpredict.sender:RegisterUnitEvent("UNIT_INVENTORY_CHANGED", "player")
libpredict.sender:RegisterEvent("SKILL_LINES_CHANGED") libpredict.sender:RegisterEvent("SKILL_LINES_CHANGED")
-- Shared cleanup helper for failed/interrupted casts -- Shared cleanup helper for failed/interrupted casts
+26 -76
View File
@@ -360,68 +360,6 @@ pfUI:RegisterModule("actionbar", function ()
end end
end end
local function ButtonMacroScan(self)
if self.bar > 10 then return end
if not self.scanmacro then return end
if pfUI.bars.skip_macro then return end
-- SuperCleveRoidMacros: for macros it manages, leave spellslot/booktype unset
-- so the button's icon, cooldown, and tooltip flow through the hooked
-- GetActionTexture / GetActionCooldown / GameTooltip:SetAction and follow the
-- 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, self.spellID = nil, nil, nil
return
end
local kind, slot = GetActionInfo(self.id)
self.spellslot, self.booktype, self.spellID = nil, nil, nil
if kind == 'macro' then
local name, _, body = GetMacroInfo(slot)
if name and body then
local match
for line in gfind(body, "[^%\n]+") do
_, _, match = string.find(line, '^#showtooltip (.+)')
-- allow the user to disable the scan
if match and strfind(match, "disable") then
return
end
if not match then
-- add support to specify custom tooltips via:
-- /run --showtooltip SPELLNAME
_, _, match = string.find(line, '%-%-showtooltip (.+)')
end
if not match then
_, _, match = string.find(line, '^/cast (.+)')
end
if not match then
_, _, match = string.find(line, '^/pfcast (.+)')
end
if not match then
_, _, match = string.find(line, '^/pfmouse (.+)')
end
if not match then
_, _, match = string.find(line, 'CastSpellByName%(%"(.+)%"%)')
end
if match then
self.spellslot, self.booktype, self.spellID = select(7, libspell.GetSpellInfo(match))
if self.spellslot and self.spellslot > 0 then return end
end
end
end
end
end
local function ButtonEnter(self) local function ButtonEnter(self)
self = self or this self = self or this
@@ -690,7 +628,6 @@ pfUI:RegisterModule("actionbar", function ()
local function ButtonFullUpdate(button) local function ButtonFullUpdate(button)
if not button then return end if not button then return end
ButtonMacroScan(button)
ButtonSlotUpdate(button) ButtonSlotUpdate(button)
ButtonRangeUpdate(button) ButtonRangeUpdate(button)
ButtonUsableUpdate(button) ButtonUsableUpdate(button)
@@ -807,10 +744,28 @@ pfUI:RegisterModule("actionbar", function ()
-- create the main event and update handler for pfUI actionbars -- create the main event and update handler for pfUI actionbars
local bars = CreateFrame("Frame", "pfActionBar", UIParent) local bars = CreateFrame("Frame", "pfActionBar", UIParent)
for event in pairs(special_events) do bars:RegisterEvent(event) end
for event in pairs(global_events) do bars:RegisterEvent(event) end -- The only unit events in the tables above; both concern the player alone.
for event in pairs(aura_events) do bars:RegisterEvent(event) end -- A registration keeps its kind, so these have to go in unit-filtered from
for event in pairs(pet_events) do bars:RegisterEvent(event) end -- the start -- RegisterUnitEvent over a plain registration stays plain.
local event_units = {
["UNIT_INVENTORY_CHANGED"] = "player",
["UNIT_PET"] = "player",
}
local function RegisterBarEvent(event)
local unit = event_units[event]
if unit then
bars:RegisterUnitEvent(event, unit)
else
bars:RegisterEvent(event)
end
end
for event in pairs(special_events) do RegisterBarEvent(event) end
for event in pairs(global_events) do RegisterBarEvent(event) end
for event in pairs(aura_events) do RegisterBarEvent(event) end
for event in pairs(pet_events) do RegisterBarEvent(event) end
-- refresh actionbar buttons on event -- refresh actionbar buttons on event
bars:SetScript("OnEvent", BarsEvent) bars:SetScript("OnEvent", BarsEvent)
@@ -1016,7 +971,7 @@ pfUI:RegisterModule("actionbar", function ()
local id = (bar-1)*12+button local id = (bar-1)*12+button
local exists = _G[button_name] and true or nil local exists = _G[button_name] and true or nil
local f = _G[button_name] or CreateFrame("Button", button_name, parent, ACTIONBAR_SECURE_TEMPLATE_BUTTON) local f = _G[button_name] or CreateFrame("Button", button_name, parent)
-- no button available, create a new one -- no button available, create a new one
if not exists then if not exists then
@@ -1042,7 +997,7 @@ pfUI:RegisterModule("actionbar", function ()
f.slot = id f.slot = id
-- cooldown -- cooldown
f.cd = CreateFrame(COOLDOWN_FRAME_TYPE, f:GetName() .. "Cooldown", f, "CooldownFrameTemplate") f.cd = CreateFrame("Model", f:GetName() .. "Cooldown", f, "CooldownFrameTemplate")
f.cd.pfCooldownStyleAnimation = 1 f.cd.pfCooldownStyleAnimation = 1
f.cd.pfCooldownType = "NOGCD" f.cd.pfCooldownType = "NOGCD"
f.cd.pfCooldownSize = cd_size f.cd.pfCooldownSize = cd_size
@@ -1148,12 +1103,7 @@ pfUI:RegisterModule("actionbar", function ()
f.count:SetJustifyH("RIGHT") f.count:SetJustifyH("RIGHT")
f.count:SetJustifyV("BOTTOM") f.count:SetJustifyV("BOTTOM")
-- macro spell scan (disabled when macro addons are loaded) f.scanmacro, f.spellslot, f.booktype = nil, nil, nil
if C.bars.macroscan == "0" or pfUI:MacroAddonsLoaded() then
f.scanmacro, f.spellslot, f.booktype = nil, nil, nil
else
f.scanmacro = true
end
-- range glow color -- range glow color
f.rangeColor = GetStringColorObject(C.bars.rangecolor) f.rangeColor = GetStringColorObject(C.bars.rangecolor)
@@ -1233,7 +1183,7 @@ pfUI:RegisterModule("actionbar", function ()
-- create frame -- create frame
local init = not bars[i] local init = not bars[i]
bars[i] = bars[i] or CreateFrame("Frame", "pfActionBar" .. barnames[i], UIParent, ACTIONBAR_SECURE_TEMPLATE_BAR) bars[i] = bars[i] or CreateFrame("Frame", "pfActionBar" .. barnames[i], UIParent)
bars[i]:SetID(i) bars[i]:SetID(i)
-- autohide -- autohide
+1 -1
View File
@@ -396,7 +396,7 @@ pfUI:RegisterModule("bags", function ()
if tpl == "BankItemButtonGenericTemplate" then if tpl == "BankItemButtonGenericTemplate" then
local bankslot = pfUI.bags[bag].slots[slot].frame local bankslot = pfUI.bags[bag].slots[slot].frame
local name = "pfBag" .. bag .. "item" .. slot .. "Cooldown" local name = "pfBag" .. bag .. "item" .. slot .. "Cooldown"
bankslot.cd = CreateFrame(COOLDOWN_FRAME_TYPE, name, bankslot, "CooldownFrameTemplate") bankslot.cd = CreateFrame("Model", name, bankslot, "CooldownFrameTemplate")
bankslot.cd:SetAllPoints(bankslot) bankslot.cd:SetAllPoints(bankslot)
bankslot.cd.pfCooldownStyleAnimation = 1 bankslot.cd.pfCooldownStyleAnimation = 1
bankslot.cd.pfCooldownType = "ALL" bankslot.cd.pfCooldownType = "ALL"
+17 -2
View File
@@ -7,6 +7,15 @@ pfUI:RegisterModule("buff", function ()
local br, bg, bb, ba = GetStringColor(pfUI_config.appearance.border.color) local br, bg, bb, ba = GetStringColor(pfUI_config.appearance.border.color)
-- Player aura slot ids, enumerated once per refresh pass (ScanPlayerAuraSlots)
-- and read per button by its aura index: one GetAuraSlots walk per range
-- instead of a by-index walk per button.
local helpfulSlots, harmfulSlots = {}, {}
local function ScanPlayerAuraSlots()
ScanAuraSlots("player", "HELPFUL", helpfulSlots, 32)
ScanAuraSlots("player", "HARMFUL", harmfulSlots, 16)
end
local function RefreshBuffButton(buff) local function RefreshBuffButton(buff)
if buff.btype == "HELPFUL" then if buff.btype == "HELPFUL" then
if C.buffs.separateweapons == "1" then if C.buffs.separateweapons == "1" then
@@ -23,7 +32,8 @@ pfUI:RegisterModule("buff", function ()
CreateBackdropShadow(buff) CreateBackdropShadow(buff)
end end
local name, icon, count, dispelType, _, expirationTime, _, _, _, spellId = C_UnitAuras.UnitAura("player", buff.id, buff.btype) local slots = buff.btype == "HELPFUL" and helpfulSlots or harmfulSlots
local name, icon, count, dispelType, _, expirationTime, _, _, _, spellId = C_UnitAuras.UnitAuraBySlot("player", slots[buff.id])
--detect weapon buffs --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 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
@@ -154,7 +164,7 @@ pfUI:RegisterModule("buff", function ()
pfUI.buff = CreateFrame("Frame", "pfGlobalBuffFrame", UIParent) pfUI.buff = CreateFrame("Frame", "pfGlobalBuffFrame", UIParent)
pfUI.buff:RegisterEvent("PLAYER_AURAS_CHANGED") pfUI.buff:RegisterEvent("PLAYER_AURAS_CHANGED")
pfUI.buff:RegisterEvent("PLAYER_EQUIPMENT_CHANGED") pfUI.buff:RegisterEvent("PLAYER_EQUIPMENT_CHANGED")
pfUI.buff:RegisterEvent("UNIT_MODEL_CHANGED") pfUI.buff:RegisterUnitEvent("UNIT_MODEL_CHANGED", "player")
pfUI.buff:RegisterEvent("BUFF_UPDATE_DURATION_SELF") pfUI.buff:RegisterEvent("BUFF_UPDATE_DURATION_SELF")
pfUI.buff:RegisterEvent("DEBUFF_UPDATE_DURATION_SELF") pfUI.buff:RegisterEvent("DEBUFF_UPDATE_DURATION_SELF")
pfUI.buff:SetScript("OnEvent", function() pfUI.buff:SetScript("OnEvent", function()
@@ -166,6 +176,8 @@ pfUI:RegisterModule("buff", function ()
pfUI.buff.wepbuffs.count = 0 pfUI.buff.wepbuffs.count = 0
end end
ScanPlayerAuraSlots()
for i=1,32 do for i=1,32 do
RefreshBuffButton(pfUI.buff.buffs.buttons[i]) RefreshBuffButton(pfUI.buff.buffs.buttons[i])
end end
@@ -246,6 +258,9 @@ pfUI:RegisterModule("buff", function ()
end end
end) end)
-- CreateBuffButton refreshes each new button from the slot buffers
ScanPlayerAuraSlots()
-- Weapon Buffs -- Weapon Buffs
pfUI.buff.wepbuffs = CreateFrame("Frame", "pfWepBuffFrame", UIParent) pfUI.buff.wepbuffs = CreateFrame("Frame", "pfWepBuffFrame", UIParent)
pfUI.buff.wepbuffs.count = 0 pfUI.buff.wepbuffs.count = 0
+28 -10
View File
@@ -73,9 +73,16 @@ pfUI:RegisterModule("buffwatch", function ()
return anchor return anchor
end end
local function GetBuffData(unit, id, type, selfdebuff) -- reusable GetAuraSlots buffer, filled once per RefreshBuffBarFrame
local filter = (selfdebuff and type == "HARMFUL") and "HARMFUL|PLAYER" or type local auraSlots = {}
local name, icon, count, dispelType, _, expirationTime = C_UnitAuras.UnitAura(unit, id, filter)
-- Separate buffer for the tooltip handler: OnEnter can fire while a refresh
-- is showing/hiding bars under the cursor, so it must not share the above.
local tooltipSlots = {}
-- Reads one aura by the slot id GetAuraSlots returned (nil slot -> nil).
local function GetBuffData(unit, slot)
local name, icon, count, dispelType, _, expirationTime = C_UnitAuras.UnitAuraBySlot(unit, slot)
if not name then return end if not name then return end
local remaining = expirationTime > 0 and (expirationTime - GetTime()) or 0 local remaining = expirationTime > 0 and (expirationTime - GetTime()) or 0
return remaining, icon, name, count, dispelType return remaining, icon, name, count, dispelType
@@ -113,17 +120,22 @@ pfUI:RegisterModule("buffwatch", function ()
GameTooltip:SetUnitAura("player", this.id, this.type) GameTooltip:SetUnitAura("player", this.id, this.type)
elseif this.type == "HARMFUL" then elseif this.type == "HARMFUL" then
-- selfdebuff filters the displayed list to player-cast harmful auras, but -- selfdebuff filters the displayed list to player-cast harmful auras, but
-- SetUnitAura's index has to be into the engine's full HARMFUL list. Look -- SetUnitAura's index has to be into the unfiltered HARMFUL list. Look up
-- up the displayed aura via the PLAYER filter, then scan engine slots for -- the displayed aura via the PLAYER filter, then find its position in the
-- one whose name + sourceGUID match. -- unfiltered list by name + sourceGUID.
--
-- The unfiltered list is NOT capped at 16: once a unit's 16 debuff slots
-- are full the server parks further debuffs in buff slots, and
-- C_UnitAuras reports those as harmful too. So enumerate what it has.
local config = this.parent and this.parent.config local config = this.parent and this.parent.config
if config and config.selfdebuff == "1" then if config and config.selfdebuff == "1" then
local ownAura = C_UnitAuras.GetAuraDataByIndex(this.unit, this.id, "HARMFUL|PLAYER") local ownAura = C_UnitAuras.GetAuraDataByIndex(this.unit, this.id, "HARMFUL|PLAYER")
if ownAura then if ownAura then
for gameSlot = 1, 16 do local n = ScanAuraSlots(this.unit, "HARMFUL", tooltipSlots)
local check = C_UnitAuras.GetDebuffDataByIndex(this.unit, gameSlot) for i = 1, n do
local check = C_UnitAuras.GetAuraDataBySlot(this.unit, tooltipSlots[i])
if check and check.name == ownAura.name and check.sourceGUID == ownAura.sourceGUID then if check and check.name == ownAura.name and check.sourceGUID == ownAura.sourceGUID then
GameTooltip:SetUnitAura(this.unit, gameSlot, "HARMFUL") GameTooltip:SetUnitAura(this.unit, i, "HARMFUL")
break break
end end
end end
@@ -234,9 +246,15 @@ pfUI:RegisterModule("buffwatch", function ()
local function RefreshBuffBarFrame(frame) local function RefreshBuffBarFrame(frame)
-- reinitialize all active buffs -- reinitialize all active buffs
local selfdebuff = frame.config.selfdebuff == "1" local selfdebuff = frame.config.selfdebuff == "1"
local filter = (selfdebuff and frame.type == "HARMFUL") and "HARMFUL|PLAYER" or frame.type
-- one GetAuraSlots enumeration per refresh instead of a by-index walk per
-- bar; the i-th slot is the i-th aura of the filtered list, so `i` stays
-- the index the tooltip / cancel handlers pass to the by-index API
ScanAuraSlots(frame.unit, filter, auraSlots, 32)
for i=1,32 do for i=1,32 do
local timeleft, texture, name, stacks, dtype = GetBuffData(frame.unit, i, frame.type, selfdebuff) local timeleft, texture, name, stacks, dtype = GetBuffData(frame.unit, auraSlots[i])
timeleft = timeleft or 0 timeleft = timeleft or 0
if texture and name and name ~= "" and BuffIsVisible(frame.config, name) then if texture and name and name ~= "" and BuffIsVisible(frame.config, name) then
+23 -14
View File
@@ -86,7 +86,7 @@ pfUI:RegisterModule("castbar", function ()
-- Stamp the bar with cast data and render text/icon/lag once. OnUpdate -- Stamp the bar with cast data and render text/icon/lag once. OnUpdate
-- then animates the fill from this state without touching C_Spell. -- then animates the fill from this state without touching C_Spell.
local function StampBar(cb, name, tex, startMs, endMs, spellID, isChannel, delayMs, isTradeskill, rank) local function StampBar(cb, name, tex, startMs, endMs, spellID, isChannel, delayMs, isTradeskill, rank, noInterrupt)
cb.startTime = startMs cb.startTime = startMs
cb.endTime = endMs cb.endTime = endMs
cb.isChannel = isChannel cb.isChannel = isChannel
@@ -97,7 +97,10 @@ pfUI:RegisterModule("castbar", function ()
cb:SetAlpha(1) cb:SetAlpha(1)
cb.fadeout = nil cb.fadeout = nil
cb.bar:SetStatusBarColor(GetStringColor(C.appearance.castbar[isChannel and "channelcolor" or "castbarcolor"])) -- Shield an uninterruptible cast. Not on the player's own bar: the flag is
-- measured against the reader's own interrupts and silences, so on your own
-- cast it answers a question nobody asked.
SetCastbarShield(cb.bar, cb.icon, noInterrupt and cb.unitstr ~= "player", isChannel)
-- Rank: prefer the value the UNIT_SPELLCAST_* event delivered (arg5, passed -- 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 -- through by RefreshBar). Only the retarget re-poll has no event in hand, so
@@ -152,10 +155,10 @@ pfUI:RegisterModule("castbar", function ()
ClearBar(cb) ClearBar(cb)
return return
end end
local name, _, tex, startMs, endMs, isTradeskill, _, _, spellID, _, delayMs = C_Spell.UnitCastingInfo(query) local name, _, tex, startMs, endMs, isTradeskill, _, noInterrupt, spellID, _, delayMs = C_Spell.UnitCastingInfo(query)
local isChan local isChan
if not name then if not name then
name, _, tex, startMs, endMs, _, _, spellID = C_Spell.UnitChannelInfo(query) name, _, tex, startMs, endMs, _, noInterrupt, spellID = C_Spell.UnitChannelInfo(query)
isChan = true isChan = true
end end
-- Synthetic fallback for abilities the engine treats as instant-cast but -- Synthetic fallback for abilities the engine treats as instant-cast but
@@ -169,7 +172,7 @@ pfUI:RegisterModule("castbar", function ()
end end
end end
if name and startMs and endMs then if name and startMs and endMs then
StampBar(cb, name, tex, startMs, endMs, spellID, isChan, delayMs, isTradeskill, rank) StampBar(cb, name, tex, startMs, endMs, spellID, isChan, delayMs, isTradeskill, rank, noInterrupt)
else else
ClearBar(cb) ClearBar(cb)
end end
@@ -322,15 +325,21 @@ pfUI:RegisterModule("castbar", function ()
-- casts only ever fire arg1=="player" -- when the bar's unit resolves to the -- 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 -- player (target=self). PLAYER_TARGET/FOCUS_CHANGED re-polls so a unit
-- already mid-cast when it becomes the target/focus still shows. -- already mid-cast when it becomes the target/focus still shows.
cb:RegisterEvent("UNIT_SPELLCAST_START") -- Filter to this bar's unit, plus "player" for the target/focus bars: the
cb:RegisterEvent("UNIT_SPELLCAST_STOP") -- player's own casts only ever fire arg1=="player", so a self-targeted cast
cb:RegisterEvent("UNIT_SPELLCAST_FAILED") -- has to reach them too (nil for the player bar itself, which the filter
cb:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED") -- skips). This only narrows what arrives -- the arg1/UnitIsUnit test below
cb:RegisterEvent("UNIT_SPELLCAST_DELAYED") -- still decides whether the bar acts on it.
cb:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED") local selfunit = unitstr ~= "player" and "player" or nil
cb:RegisterEvent("UNIT_SPELLCAST_CHANNEL_START") cb:RegisterUnitEvent("UNIT_SPELLCAST_START", unitstr, selfunit)
cb:RegisterEvent("UNIT_SPELLCAST_CHANNEL_STOP") cb:RegisterUnitEvent("UNIT_SPELLCAST_STOP", unitstr, selfunit)
cb:RegisterEvent("UNIT_SPELLCAST_CHANNEL_UPDATE") cb:RegisterUnitEvent("UNIT_SPELLCAST_FAILED", unitstr, selfunit)
cb:RegisterUnitEvent("UNIT_SPELLCAST_INTERRUPTED", unitstr, selfunit)
cb:RegisterUnitEvent("UNIT_SPELLCAST_DELAYED", unitstr, selfunit)
cb:RegisterUnitEvent("UNIT_SPELLCAST_SUCCEEDED", unitstr, selfunit)
cb:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_START", unitstr, selfunit)
cb:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_STOP", unitstr, selfunit)
cb:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_UPDATE", unitstr, selfunit)
if unitstr == "target" then if unitstr == "target" then
cb:RegisterEvent("PLAYER_TARGET_CHANGED") cb:RegisterEvent("PLAYER_TARGET_CHANGED")
elseif unitstr == "focus" then elseif unitstr == "focus" then
+40 -21
View File
@@ -63,6 +63,23 @@ pfUI:RegisterModule("chat", function ()
return pfUI_cache["chathistory"][realm][player][id] return pfUI_cache["chathistory"][realm][player][id]
end end
-- [ Chat Panel Colors ]
-- The panels normally inherit pfUI's global appearance theme; custom colors let chat
-- deviate from it. CreateBackdrop is re-run first to restore the theme, so toggling
-- custom colors back off doesn't leave the previous override behind.
local function ApplyPanelColors(panel)
if not panel then return end
CreateBackdrop(panel, default_border, nil, .8)
if C.chat.global.custombg ~= "1" then return end
local r, g, b, a = GetStringColor(C.chat.global.background)
panel.backdrop:SetBackdropColor(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
local r, g, b, a = GetStringColor(C.chat.global.border)
panel.backdrop:SetBackdropBorderColor(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
end
pfUI.chat = CreateFrame("Frame",nil,UIParent) pfUI.chat = CreateFrame("Frame",nil,UIParent)
pfUI.chat.left = CreateFrame("Frame", "pfChatLeft", UIParent) pfUI.chat.left = CreateFrame("Frame", "pfChatLeft", UIParent)
@@ -76,19 +93,11 @@ pfUI:RegisterModule("chat", function ()
pfUI.chat.left:SetPoint("BOTTOMLEFT", 2*default_border,2*default_border) pfUI.chat.left:SetPoint("BOTTOMLEFT", 2*default_border,2*default_border)
pfUI.chat.left:SetScript("OnShow", function() pfUI.chat:RefreshChat() end) pfUI.chat.left:SetScript("OnShow", function() pfUI.chat:RefreshChat() end)
UpdateMovable(pfUI.chat.left) UpdateMovable(pfUI.chat.left)
CreateBackdrop(pfUI.chat.left, default_border, nil, .8) ApplyPanelColors(pfUI.chat.left)
if C.chat.global.frameshadow == "1" then if C.chat.global.frameshadow == "1" then
CreateBackdropShadow(pfUI.chat.left) CreateBackdropShadow(pfUI.chat.left)
end end
if C.chat.global.custombg == "1" then
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 = GetStringColor(C.chat.global.border)
pfUI.chat.left.backdrop:SetBackdropBorderColor(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
end
pfUI.chat.left.panelTop = CreateFrame("Frame", "leftChatPanelTop", pfUI.chat.left) pfUI.chat.left.panelTop = CreateFrame("Frame", "leftChatPanelTop", pfUI.chat.left)
pfUI.chat.left.panelTop:ClearAllPoints() pfUI.chat.left.panelTop:ClearAllPoints()
pfUI.chat.left.panelTop:SetHeight(C.global.font_size+default_border*2) pfUI.chat.left.panelTop:SetHeight(C.global.font_size+default_border*2)
@@ -251,19 +260,11 @@ pfUI:RegisterModule("chat", function ()
pfUI.chat.right:SetPoint("BOTTOMRIGHT", -2*default_border,2*default_border) pfUI.chat.right:SetPoint("BOTTOMRIGHT", -2*default_border,2*default_border)
pfUI.chat.right:SetScript("OnShow", function() pfUI.chat:RefreshChat() end) pfUI.chat.right:SetScript("OnShow", function() pfUI.chat:RefreshChat() end)
UpdateMovable(pfUI.chat.right) UpdateMovable(pfUI.chat.right)
CreateBackdrop(pfUI.chat.right, default_border, nil, .8) ApplyPanelColors(pfUI.chat.right)
if C.chat.global.frameshadow == "1" then if C.chat.global.frameshadow == "1" then
CreateBackdropShadow(pfUI.chat.right) CreateBackdropShadow(pfUI.chat.right)
end end
if C.chat.global.custombg == "1" then
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 = GetStringColor(C.chat.global.border)
pfUI.chat.right.backdrop:SetBackdropBorderColor(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
end
pfUI.chat.right.panelTop = CreateFrame("Frame", "rightChatPanelTop", pfUI.chat.right) pfUI.chat.right.panelTop = CreateFrame("Frame", "rightChatPanelTop", pfUI.chat.right)
pfUI.chat.right.panelTop:ClearAllPoints() pfUI.chat.right.panelTop:ClearAllPoints()
pfUI.chat.right.panelTop:SetHeight(C.global.font_size+default_border*2) pfUI.chat.right.panelTop:SetHeight(C.global.font_size+default_border*2)
@@ -300,7 +301,7 @@ pfUI:RegisterModule("chat", function ()
-- Blizzard's per-window transparency slider drives FCF_SetWindowAlpha, which only -- Blizzard's per-window transparency slider drives FCF_SetWindowAlpha, which only
-- touches the ChatFrame*Background textures that pfUI hides on docked frames. Mirror -- 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 -- 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. -- controls it directly.
local function ApplyChatBackgroundAlpha(panel, frame) local function ApplyChatBackgroundAlpha(panel, frame)
if not (panel and panel.backdrop and frame) then return end if not (panel and panel.backdrop and frame) then return end
local _, _, _, _, _, alpha = GetChatWindowInfo(frame:GetID()) local _, _, _, _, _, alpha = GetChatWindowInfo(frame:GetID())
@@ -311,6 +312,12 @@ pfUI:RegisterModule("chat", function ()
end end
function pfUI.chat:RefreshBackgroundAlpha() function pfUI.chat:RefreshBackgroundAlpha()
-- Custom colors own the alpha. C.chat.global.background carries its own alpha, is
-- set by profiles and is shared with the meter skins, so the native slider must not
-- overwrite it -- doing so reverted the configured opacity on every refresh. The
-- slider only drives the panel when the user hasn't opted into pfUI's chat colors.
if C.chat.global.custombg == "1" then return end
-- left panel follows the currently selected docked tab -- left panel follows the currently selected docked tab
local selected = SELECTED_CHAT_FRAME local selected = SELECTED_CHAT_FRAME
if not (selected and selected:GetParent() == pfUI.chat.left) then if not (selected and selected:GetParent() == pfUI.chat.left) then
@@ -323,6 +330,14 @@ pfUI:RegisterModule("chat", function ()
end end
end end
-- live config apply, resolved by the gui as U["chat"]. Lets the color pickers
-- take effect on the spot instead of waiting for a /reload.
function pfUI.chat:UpdateConfig()
ApplyPanelColors(pfUI.chat.left)
ApplyPanelColors(pfUI.chat.right)
pfUI.chat:RefreshBackgroundAlpha()
end
function pfUI.chat:MigrateBackgroundAlpha() function pfUI.chat:MigrateBackgroundAlpha()
-- Runs once per character. The previous SetupPositions default stored a hard 0 window -- 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 -- alpha, which now renders the pfUI backdrop fully transparent. Restore the historical
@@ -812,7 +827,7 @@ pfUI:RegisterModule("chat", function ()
local real, _ = strsplit(":", name) local real, _ = strsplit(":", name)
local color = unknowncolorhex local color = unknowncolorhex
local match = false local match = false
local _, class = C_PlayerCache.GetPlayerInfoByName(real) local _, class, _, raceKey, sex = C_PlayerCache.GetPlayerInfoByName(real)
-- local guid = GetCurrentChatGUID() -- local guid = GetCurrentChatGUID()
-- if guid then -- if guid then
-- _, class = GetPlayerInfoByGUID(guid) -- _, class = GetPlayerInfoByGUID(guid)
@@ -830,8 +845,12 @@ pfUI:RegisterModule("chat", function ()
end end
if C.chat.text.tintunknown == "1" or match then if C.chat.text.tintunknown == "1" or match then
local icon = ""
if match and C.chat.text.playericons == "1" then
icon = GetFactionIcon(raceKey) .. GetRaceIcon(raceKey, sex)
end
text = string.gsub(text, "|Hplayer:"..name.."|h%["..real.."%]|h(.-:-)", text = string.gsub(text, "|Hplayer:"..name.."|h%["..real.."%]|h(.-:-)",
left..color.."|Hplayer:"..name.."|h" .. color .. real .. "|h|r"..right.."%1") left..icon..color.."|Hplayer:"..name.."|h" .. color .. real .. "|h|r"..right.."%1")
end end
end end
end end
+1 -1
View File
@@ -52,7 +52,7 @@ pfUI:RegisterModule("combopoints", function ()
-- combo -- combo
if class == "DRUID" or class == "ROGUE" then if class == "DRUID" or class == "ROGUE" then
local combo = CreateFrame("Frame") local combo = CreateFrame("Frame")
combo:RegisterEvent("UNIT_COMBO_POINTS") combo:RegisterUnitEvent("UNIT_COMBO_POINTS", "player")
combo:RegisterEvent("PLAYER_COMBO_POINTS") combo:RegisterEvent("PLAYER_COMBO_POINTS")
combo:RegisterEvent("PLAYER_TARGET_CHANGED") combo:RegisterEvent("PLAYER_TARGET_CHANGED")
combo:RegisterEvent("PLAYER_ENTERING_WORLD") combo:RegisterEvent("PLAYER_ENTERING_WORLD")
+28 -18
View File
@@ -6,30 +6,35 @@ pfUI:RegisterModule("cooldown", function ()
-- local hourcolor = {strsplit(",", C.appearance.cd.hourcolor)} -- local hourcolor = {strsplit(",", C.appearance.cd.hourcolor)}
-- local daycolor = {strsplit(",", C.appearance.cd.daycolor)} -- local daycolor = {strsplit(",", C.appearance.cd.daycolor)}
local parent, parent_name
local function pfCooldownOnUpdate() local function pfCooldownOnUpdate()
parent = this:GetParent() -- Throttle FIRST. One of these runs per visible cooldown text, every frame,
-- so anything above this gate is multiplied by the frame rate and by how
-- many cooldowns are ticking.
local now = GetTime()
if (this.tick or 0) > now then return end
this.tick = now + .1
local parent = this:GetParent()
if not parent then this:Hide() return end if not parent then this:Hide() return end
parent_name = parent:GetName()
-- avoid to set cooldowns on invalid frames -- avoid to set cooldowns on invalid frames. The cooldown frame is stashed
if parent_name and _G[parent_name .. "Cooldown"] then -- at creation: resolving it as _G[parent:GetName() .. "Cooldown"] built and
if not _G[parent_name .. "Cooldown"]:IsShown() then -- interned that string twice per call, and this is the hottest path in the
this:Hide() -- UI. The stashed reference is also the frame itself rather than a guess
end -- from its parent's name, so it holds for cooldowns named anything else.
if this.cooldown and not this.cooldown:IsShown() then
this:Hide()
return
end end
-- only run every 0.1 seconds from here on
if ( this.tick or .1) > GetTime() then return else this.tick = GetTime() + .1 end
-- fix own alpha value (should be inherited, but somehow isn't always) -- fix own alpha value (should be inherited, but somehow isn't always)
if this:GetAlpha() ~= parent:GetAlpha() then if this:GetAlpha() ~= parent:GetAlpha() then
this:SetAlpha(parent:GetAlpha()) this:SetAlpha(parent:GetAlpha())
end end
if this.start < GetTime() then if this.start < now then
-- calculating remaining time as it should be -- calculating remaining time as it should be
local remaining = this.duration - (GetTime() - this.start) local remaining = this.duration - (now - this.start)
if remaining >= 0 then if remaining >= 0 then
this.text:SetText(GetColoredTimeString(remaining)) this.text:SetText(GetColoredTimeString(remaining))
else else
@@ -38,13 +43,13 @@ pfUI:RegisterModule("cooldown", function ()
else else
-- I have absolutely no idea, but it works: -- I have absolutely no idea, but it works:
-- https://github.com/Stanzilla/WoWUIBugs/issues/47 -- https://github.com/Stanzilla/WoWUIBugs/issues/47
local time = time() local currentTime = time()
local startupTime = time - GetTime() local startupTime = currentTime - now
-- just a simplification of: ((2^32) - (start * 1000)) / 1000 -- just a simplification of: ((2^32) - (start * 1000)) / 1000
local cdTime = (2 ^ 32) / 1000 - this.start local cdTime = (2 ^ 32) / 1000 - this.start
local cdStartTime = startupTime - cdTime local cdStartTime = startupTime - cdTime
local cdEndTime = cdStartTime + this.duration local cdEndTime = cdStartTime + this.duration
local remaining = cdEndTime - time local remaining = cdEndTime - currentTime
if remaining >= 0 then if remaining >= 0 then
this.text:SetText(GetColoredTimeString(remaining)) this.text:SetText(GetColoredTimeString(remaining))
@@ -55,11 +60,16 @@ pfUI:RegisterModule("cooldown", function ()
end end
local height, size local height, size
local textcount = 0
local function pfCreateCoolDown(cooldown, start, duration) local function pfCreateCoolDown(cooldown, start, duration)
cooldown.pfCooldownText = CreateFrame("Frame", "pfCooldownFrame", cooldown:GetParent()) textcount = textcount + 1
local name = cooldown.GetName and cooldown:GetName() or "pfCooldown" .. textcount
cooldown.pfCooldownText = CreateFrame("Frame", name .. "Text", cooldown:GetParent())
cooldown.pfCooldownText.cooldown = cooldown
cooldown.pfCooldownText:SetAllPoints(cooldown) cooldown.pfCooldownText:SetAllPoints(cooldown)
cooldown.pfCooldownText:SetFrameLevel(cooldown:GetParent():GetFrameLevel() + 2) cooldown.pfCooldownText:SetFrameLevel(cooldown:GetParent():GetFrameLevel() + 2)
cooldown.pfCooldownText.text = cooldown.pfCooldownText:CreateFontString("pfCooldownFrameText", "OVERLAY") cooldown.pfCooldownText.text = cooldown.pfCooldownText:CreateFontString(name .. "TextString", "OVERLAY")
if not cooldown.pfCooldownType then if not cooldown.pfCooldownType then
size = tonumber(C.appearance.cd.font_size_foreign) size = tonumber(C.appearance.cd.font_size_foreign)
+221 -59
View File
@@ -1,20 +1,82 @@
local function getAdjustedTickTimer() -- One server clock drives every power: Player::RegenerateAll fires every
local adjustedEnergyTick = 2 -- REGEN_TIME_FULL (2s), re-arms with `+=`, and is never reset by casting. The
-- five-second rule (SetLastManaUse on any mana-costing cast) changes what a tick
-- pays, never when it lands; mp5 and the player's MOD_MANA_REGEN_INTERRUPT share
-- still come in. That share can't be computed here -- item sources are equip
-- auras absent from the buff list and m_modManaRegenInterrupt is never sent --
-- so the spark shows it instead: dim through the window until a tick lands.
--
-- The sweep free-runs on that clock and phase-locks to observed gains. A gain
-- mid-sweep (Illumination, Judgement of Wisdom, potions, a Mana Spring totem on
-- its own phase) is not the tick and never moves it.
-- Check rogue talents and compute energy tick timing reduction for Combat spec (1.18.0 Blade Rush Talent) local FIVE_SECOND_RULE = 5
if UnitClassBase("player") == "ROGUE" then
local _, _, _, _, currRank = GetTalentInfo(2, 16)
local bladeRushRank = currRank or 0
if bladeRushRank > 0 then -- gains farther than this from the predicted boundary are not the tick
local agility = UnitStat("player", 2) -- 2 is agility stat index local TICK_TOLERANCE = .25
local reductionPerAgi = 0.0006 * bladeRushRank -- 0.0006 for rank 1, 0.0012 for rank 2
local totalReduction = agility * reductionPerAgi -- arrival jitter; a tick inside this band confirms the sweep rather than
adjustedEnergyTick = adjustedEnergyTick - totalReduction -- re-anchoring it, or the spark hitches at every wrap
local TICK_JITTER = .08
-- Player::RegenerateAll:
-- mod = GetTotalAuraModifier(SPELL_AURA_MOD_ENERGY_REGEN_TIME)
-- if mod > 0 then mod = mod * agility / 10 end
-- m_regenTimer += max(1, REGEN_TIME_FULL - mod) -- milliseconds
local REGEN_TIME_FULL = 2
local ENERGY_REGEN_TIME_AURA = 217 -- SPELL_AURA_MOD_ENERGY_REGEN_TIME
-- fixed magnitude is basePoints + baseDice (stored 11 -> 12); a die above 1 is
-- a roll the client can't know, so it counts as nothing rather than a guess
local amountCache = {}
local function auraAmount(spellID)
local amount = amountCache[spellID]
if amount then return amount end
amount = 0
local effects = C_Spell.GetSpellEffectInfo(spellID) -- nil for an id with no record
if effects then
for i = 1, 3 do
local fx = effects[i]
if fx.auraName == ENERGY_REGEN_TIME_AURA and fx.dieSides <= 1 then
amount = fx.basePoints + fx.baseDice
break
end
end end
end end
amountCache[spellID] = amount
return amount
end
return adjustedEnergyTick -- A passive is in effect exactly while known (current rank only, never in the
-- buff list); anything castable or cast on us counts only while it is up.
local function getEnergyRegenTimeMod()
local sum = 0
for _, spellID in ipairs(C_SpellBook.GetPlayerSpellsByAura(ENERGY_REGEN_TIME_AURA)) do
if C_Spell.IsSpellPassive(spellID) then
sum = sum + auraAmount(spellID)
end
end
for i = 1, 32 do
local spellID = select(10, C_UnitAuras.UnitAura("player", i, "HELPFUL"))
if not spellID then break end
sum = sum + auraAmount(spellID)
end
return sum
end
-- cleared on SPELLS_CHANGED (passives) and PLAYER_AURAS_CHANGED (buffs), and
-- recomputed by the next tick that asks. Agility stays live: it's one call.
local energyRegenTimeMod
local function getAdjustedTickTimer()
if not energyRegenTimeMod then
energyRegenTimeMod = getEnergyRegenTimeMod()
end
if energyRegenTimeMod == 0 then return REGEN_TIME_FULL end
-- ms on the server, seconds here; the 1ms floor is the server's and this is a divisor
local reduction = energyRegenTimeMod * UnitStat("player", 2) / 10000
return math.max(0.001, REGEN_TIME_FULL - reduction)
end end
pfUI:RegisterModule("energytick", function() pfUI:RegisterModule("energytick", function()
@@ -22,14 +84,60 @@ pfUI:RegisterModule("energytick", function()
return return
end end
-- inside the module body on purpose: C is on pfUI.env, not _G
local function getBarWidth()
return C.unitframes.player.pwidth ~= "-1" and C.unitframes.player.pwidth or C.unitframes.player.width
end
local rawborder, default_border = GetBorderSize("unitframes")
local cbtexture = pfUI.media[C.appearance.castbar.texture]
-- style is read once: switching it in the GUI needs a /reload, same as the
-- other structural (not cosmetic) toggles in this file
local fsrOverlay = C.unitframes.player.fsrstyle == "1"
-- was this gain the regen tick? if so, re-anchor the sweep on it
local function lockTick(frame)
local now, period = GetTime(), getAdjustedTickTimer()
if frame.start then
-- signed distance to the nearest predicted boundary
local err = mod(now - frame.start, period)
if err > period / 2 then err = err - period end
if math.abs(err) <= TICK_TOLERANCE then
-- correct only what lies beyond normal jitter
if err > TICK_JITTER then
frame.start = frame.start + (err - TICK_JITTER)
elseif err < -TICK_JITTER then
frame.start = frame.start + (err + TICK_JITTER)
end
frame.max, frame.rejected = period, nil
return true
end
-- two rejected gains one period apart are the real clock: relock to it
local periodic = frame.rejected and math.abs(now - frame.rejected - period) <= TICK_TOLERANCE
if not periodic then
frame.rejected = now
return false
end
end
frame.start, frame.max, frame.rejected = now, period, nil
return true
end
local energytick = CreateFrame("Frame", nil, pfUI.uf.player.power.bar) local energytick = CreateFrame("Frame", nil, pfUI.uf.player.power.bar)
energytick:SetAllPoints(pfUI.uf.player.power.bar) energytick:SetAllPoints(pfUI.uf.player.power.bar)
energytick:RegisterEvent("PLAYER_ENTERING_WORLD") energytick:RegisterEvent("PLAYER_ENTERING_WORLD")
energytick:RegisterEvent("UNIT_DISPLAYPOWER") energytick:RegisterUnitEvent("UNIT_DISPLAYPOWER", "player")
energytick:RegisterEvent("UNIT_ENERGY") energytick:RegisterUnitEvent("UNIT_ENERGY", "player")
energytick:RegisterEvent("UNIT_MANA") energytick:RegisterUnitEvent("UNIT_MANA", "player")
energytick:RegisterEvent("CHAT_MSG_SPELL_SELF_BUFF") energytick:RegisterUnitEvent("UNIT_SPELLCAST_SUCCEEDED", "player")
energytick:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_SELF_BUFFS") energytick:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_STOP", "player")
energytick:RegisterEvent("SPELLS_CHANGED")
energytick:RegisterEvent("PLAYER_AURAS_CHANGED")
energytick:SetScript("OnEvent", function() energytick:SetScript("OnEvent", function()
if UnitPowerType("player") == Enum.PowerType.Mana and C.unitframes.player.manatick == "1" then if UnitPowerType("player") == Enum.PowerType.Mana and C.unitframes.player.manatick == "1" then
@@ -42,44 +150,52 @@ pfUI:RegisterModule("energytick", function()
this:Hide() this:Hide()
end end
-- Filter nur eigene Energy-Gewinne von Talents/Buffs if event == "SPELLS_CHANGED" or event == "PLAYER_AURAS_CHANGED" then
if event == "CHAT_MSG_SPELL_SELF_BUFF" or event == "CHAT_MSG_SPELL_PERIODIC_SELF_BUFFS" then energyRegenTimeMod = nil
if string.find(arg1, "You gain") and string.find(arg1, "Energy from") then
this.ignoreNextGain = true
end
return return
end end
if event == "PLAYER_ENTERING_WORLD" then if event == "PLAYER_ENTERING_WORLD" then
this.lastMana = UnitPower("player") this.lastPower = UnitPower("player")
end
-- the rule arms on the cast (Spell::TakePower: mana powerType, cost > 0),
-- not on a mana drop -- Mana Burn lowers mana without arming it
if event == "UNIT_SPELLCAST_SUCCEEDED" and arg1 == "player" then
local cost = C_Spell.GetSpellPowerCost(arg3)
cost = cost and cost[1]
if cost and cost.type == Enum.PowerType.Mana and cost.cost > 0 then
this.fsrSpell, this.fsrEnd = arg3, GetTime() + FIVE_SECOND_RULE
this.fsrGain = nil
end
return
end
-- Unit::Update won't expire the rule while the spending spell still channels
if event == "UNIT_SPELLCAST_CHANNEL_STOP" and arg1 == "player" then
if this.fsrSpell and this.fsrSpell == arg3 then
this.fsrEnd, this.fsrGain = GetTime() + FIVE_SECOND_RULE, nil
end
return
end end
if (event == "UNIT_MANA" or event == "UNIT_ENERGY") and arg1 == "player" then if (event == "UNIT_MANA" or event == "UNIT_ENERGY") and arg1 == "player" then
this.currentMana = UnitPower("player") local power = UnitPower("player")
local diff = 0 local diff = this.lastPower and (power - this.lastPower) or 0
if this.lastMana then this.lastPower = power
diff = this.currentMana - this.lastMana
-- only a gain can be the tick; a spend never touches the phase
if diff > 0 and lockTick(this) then
-- a tick inside the window proves regen continues through it
if this.fsrEnd and this.fsrEnd > GetTime() then
this.fsrGain = true
end
end end
if this.mode == "MANA" and diff < 0 then -- phase is kept while hidden; OnUpdate catches up by whole periods
this.target = 5 if this.mode == "MANA" and power >= UnitPowerMax("player") then
elseif this.mode == "MANA" and diff > 0 then this:Hide()
if UnitPower("player") >= UnitPowerMax("player") then
this.start = nil
this.spark:SetAlpha(0)
this:Hide()
elseif this.max ~= 5 and diff > (this.badtick and this.badtick * 1.2 or 5) then
this.target = 2
else
this.badtick = diff
end
elseif this.mode == "ENERGY" and diff >= 0 then
if not this.ignoreNextGain then
this.target = getAdjustedTickTimer()
end
this.ignoreNextGain = false
end end
this.lastMana = this.currentMana
end end
end) end)
@@ -90,37 +206,79 @@ pfUI:RegisterModule("energytick", function()
end end
this.tick = GetTime() + 0.020 -- ~50 FPS this.tick = GetTime() + 0.020 -- ~50 FPS
if this.target then -- five-second rule drains to nothing. The display is cosmetic and can be
this.start, this.max = GetTime(), this.target -- hidden on its own; the window itself (fsrEnd/fsrGain) keeps running
this.target = nil -- either way -- it still dims the sweep spark below.
this.spark:SetAlpha(1) local remaining = this.fsrEnd and (this.fsrEnd - GetTime()) or 0
this:Show() if this.mode == "MANA" and remaining > 0 then
if C.unitframes.player.showfsr == "1" then
if fsrOverlay then
this.fsrbar:SetWidth(getBarWidth() * remaining / FIVE_SECOND_RULE)
else
this.fsrbar:SetValue(remaining)
end
this.fsrbar:Show()
else
this.fsrbar:Hide()
end
else
this.fsrSpell, this.fsrEnd, this.fsrGain = nil, nil, nil
this.fsrbar:Hide()
end end
if not this.start then if not this.start then
this.spark:SetAlpha(0)
return
end
if this.mode == "MANA" and UnitPower("player") >= UnitPowerMax("player") then
this.spark:SetAlpha(0)
return return
end end
this.current = GetTime() - this.start this.current = GetTime() - this.start
-- roll over by whole periods, not from now: restarting bakes frame
-- overshoot into the phase as drift the lock then has to chase
if this.current > this.max then if this.current > this.max then
-- Don't restart tick timer if mana is full this.start = this.start + this.max * math.floor(this.current / this.max)
if this.mode == "MANA" and UnitPower("player") >= UnitPowerMax("player") then this.max = getAdjustedTickTimer()
this.start = nil this.current = GetTime() - this.start
this.spark:SetAlpha(0)
return
end
this.start, this.max, this.current = GetTime(), getAdjustedTickTimer(), 0
end end
local pos = (C.unitframes.player.pwidth ~= "-1" and C.unitframes.player.pwidth or C.unitframes.player.width) -- dim while the rule is up and nothing has ticked inside it yet
* (this.current / this.max) this.spark:SetAlpha((remaining > 0 and not this.fsrGain) and .4 or 1)
if not C.unitframes.player.pheight then if not C.unitframes.player.pheight then
return return
end end
local pos = getBarWidth() * (this.current / this.max)
this.spark:SetPoint("LEFT", pos - ((C.unitframes.player.pheight + 5) / 2), 0) this.spark:SetPoint("LEFT", pos - ((C.unitframes.player.pheight + 5) / 2), 0)
end) end)
if fsrOverlay then
-- classic style: a translucent shade on top of the power bar's own fill
energytick.fsrbar = energytick:CreateTexture(nil, "ARTWORK")
energytick.fsrbar:SetTexture(1, 1, 1, .15)
energytick.fsrbar:SetPoint("TOPLEFT", 0, 0)
energytick.fsrbar:SetPoint("BOTTOMLEFT", 0, 0)
else
-- a real bar below the power bar, not an overlay on top of it -- the same
-- treatment nameplate castbars get relative to the health bar, so the
-- window can't be mistaken for missing mana
energytick.fsrbar = CreateFrame("StatusBar", nil, pfUI.uf.player)
energytick.fsrbar:SetStatusBarTexture(cbtexture)
energytick.fsrbar:SetMinMaxValues(0, FIVE_SECOND_RULE)
energytick.fsrbar:SetPoint("TOPLEFT", pfUI.uf.player.power.bar, "BOTTOMLEFT", 0, -default_border * 2)
energytick.fsrbar:SetPoint("TOPRIGHT", pfUI.uf.player.power.bar, "BOTTOMRIGHT", 0, -default_border * 2)
energytick.fsrbar:SetHeight(tonumber(C.unitframes.player.fsrheight) or 4)
energytick.fsrbar:SetStatusBarColor(GetStringColor(C.appearance.castbar.fsrcolor))
CreateBackdrop(energytick.fsrbar, default_border)
CreateBackdropShadow(energytick.fsrbar)
end
energytick.fsrbar:Hide()
energytick.spark = energytick:CreateTexture(nil, "OVERLAY") energytick.spark = energytick:CreateTexture(nil, "OVERLAY")
energytick.spark:SetTexture("Interface\\CastingBar\\UI-CastingBar-Spark") energytick.spark:SetTexture("Interface\\CastingBar\\UI-CastingBar-Spark")
energytick.spark:SetHeight(C.unitframes.player.pheight + 15) energytick.spark:SetHeight(C.unitframes.player.pheight + 15)
@@ -131,6 +289,10 @@ pfUI:RegisterModule("energytick", function()
function pfUI.uf.player.UpdateConfig() function pfUI.uf.player.UpdateConfig()
energytick.spark:SetHeight(C.unitframes.player.pheight + 15) energytick.spark:SetHeight(C.unitframes.player.pheight + 15)
energytick.spark:SetWidth(C.unitframes.player.pheight + 5) energytick.spark:SetWidth(C.unitframes.player.pheight + 5)
if not fsrOverlay then
energytick.fsrbar:SetHeight(tonumber(C.unitframes.player.fsrheight) or 4)
energytick.fsrbar:SetStatusBarColor(GetStringColor(C.appearance.castbar.fsrcolor))
end
hookUpdateConfig(pfUI.uf.player) hookUpdateConfig(pfUI.uf.player)
end end
end) end)
+20 -1
View File
@@ -130,9 +130,28 @@ pfUI:RegisterModule("eqcompare", function ()
SetTradeTargetItem = GetTradeTargetItemLink SetTradeTargetItem = GetTradeTargetItemLink
} }
-- Guda anchors its item tooltips with ANCHOR_NONE and its own SetPoint, and
-- the tooltip module then moves every ANCHOR_NONE tooltip to its configured
-- spot. Reading the rect inside the Set* call picks the side against the
-- position the tooltip is about to leave, so place the compare a frame later.
local deferred
if C_AddOns.DoesAddOnExist("Guda") then
EventUtil.ContinueOnAddOnLoaded("Guda", function()
deferred = true
end)
end
local function makeHook(getter) local function makeHook(getter)
return function(tooltip, arg1, arg2, arg3) return function(tooltip, arg1, arg2, arg3)
ShowCompareItem(tooltip, getter(arg1, arg2, arg3)) local link = getter(arg1, arg2, arg3)
if not deferred then
return ShowCompareItem(tooltip, link)
end
RunNextFrame(function()
if tooltip:IsShown() then
ShowCompareItem(tooltip, link)
end
end)
end end
end end
+10
View File
@@ -301,6 +301,16 @@ pfUI:RegisterModule("equipmentmanager", function()
pfUI.equipmentmanager.Refresh() pfUI.equipmentmanager.Refresh()
end) end)
-- Drag a set onto an action bar. PickupEquipmentSet is newer than
-- the rest of C_EquipmentSet, so dragging stays inert on ClassicAPI
-- builds that don't ship it yet.
if C_EquipmentSet.PickupEquipmentSet then
row:RegisterForDrag("LeftButton")
row:SetScript("OnDragStart", function()
if row.setID then C_EquipmentSet.PickupEquipmentSet(row.setID) end
end)
end
-- Show the gear and tooltip while the cursor is over the row or its -- 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 -- 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 -- MouseIsOver(row) check covers both. OnLeave on the row and the gear
+16 -10
View File
@@ -2072,8 +2072,6 @@ pfUI:RegisterModule("gui", function ()
CreateConfig(nil, T["Combopoint Width"], C.unitframes, "combowidth") CreateConfig(nil, T["Combopoint Width"], C.unitframes, "combowidth")
CreateConfig(nil, T["Combopoint Height"], C.unitframes, "comboheight") CreateConfig(nil, T["Combopoint Height"], C.unitframes, "comboheight")
CreateConfig(nil, T["Show Resting"], C.unitframes.player, "showRest", "checkbox") CreateConfig(nil, T["Show Resting"], C.unitframes.player, "showRest", "checkbox")
CreateConfig(nil, T["Enable Energy Ticks"], C.unitframes.player, "energy", "checkbox")
CreateConfig(nil, T["Enable Mana Ticks"], C.unitframes.player, "manatick", "checkbox")
CreateConfig(nil, T["Detect Enemy Buffs"], C.unitframes, "buffdetect", "checkbox", nil, nil, nil, nil) CreateConfig(nil, T["Detect Enemy Buffs"], C.unitframes, "buffdetect", "checkbox", nil, nil, nil, nil)
CreateConfig(nil, T["Raid Mark Icon Style"], C.unitframes, "blizzard_raidicons", "dropdown", function() return {"1:Original Blizzard", "0:pfUI Design"} end) CreateConfig(nil, T["Raid Mark Icon Style"], C.unitframes, "blizzard_raidicons", "dropdown", function() return {"1:Original Blizzard", "0:pfUI Design"} end)
@@ -2320,6 +2318,15 @@ pfUI:RegisterModule("gui", function ()
CreateConfig(U[c], T["Right Text Y Offset"], C.unitframes[c], "txtpowerrightoffy") CreateConfig(U[c], T["Right Text Y Offset"], C.unitframes[c], "txtpowerrightoffy")
CreateConfig(U[c], T["Power Bar Anchor"], C.unitframes[c], "panchor", "dropdown", pfUI.gui.dropdowns.uf_powerbar_position) CreateConfig(U[c], T["Power Bar Anchor"], C.unitframes[c], "panchor", "dropdown", pfUI.gui.dropdowns.uf_powerbar_position)
if c == "player" then
CreateConfig(nil, T["Energy/Mana Ticks"], nil, nil, "header")
CreateConfig(nil, T["Enable Energy Ticks"], C.unitframes[c], "energy", "checkbox")
CreateConfig(nil, T["Enable Mana Ticks"], C.unitframes[c], "manatick", "checkbox")
CreateConfig(nil, T["Show Five Second Rule"], C.unitframes[c], "showfsr", "checkbox")
CreateConfig(nil, T["Five Second Rule Style"], C.unitframes[c], "fsrstyle", "dropdown", function() return {"0:Separate Bar", "1:Overlay"} end)
CreateConfig(nil, T["Five Second Rule Height"], C.unitframes[c], "fsrheight")
end
CreateConfig(U[c], T["Combat Text"], nil, nil, "header") CreateConfig(U[c], T["Combat Text"], nil, nil, "header")
CreateConfig(U[c], T["Show Combat Text"], C.unitframes[c], "hitindicator", "checkbox") CreateConfig(U[c], T["Show Combat Text"], C.unitframes[c], "hitindicator", "checkbox")
CreateConfig(U[c], T["Combat Text Font"], C.unitframes[c], "hitindicatorfont", "dropdown", pfUI.gui.dropdowns.fonts) CreateConfig(U[c], T["Combat Text Font"], C.unitframes[c], "hitindicatorfont", "dropdown", pfUI.gui.dropdowns.fonts)
@@ -2556,9 +2563,6 @@ pfUI:RegisterModule("gui", function ()
CreateConfig(U["bars"], T["Button Animation"], C.bars, "animation", "dropdown", pfUI.gui.dropdowns.actionbuttonanimations) CreateConfig(U["bars"], T["Button Animation"], C.bars, "animation", "dropdown", pfUI.gui.dropdowns.actionbuttonanimations)
CreateConfig(U["bars"], T["Button Animation Trigger"], C.bars, "animmode", "dropdown", pfUI.gui.dropdowns.animationmode) CreateConfig(U["bars"], T["Button Animation Trigger"], C.bars, "animmode", "dropdown", pfUI.gui.dropdowns.animationmode)
CreateConfig(U["bars"], T["Show Animation On Hidden Bars"], C.bars, "animalways", "checkbox") CreateConfig(U["bars"], T["Show Animation On Hidden Bars"], C.bars, "animalways", "checkbox")
if not pfUI:MacroAddonsLoaded() then
CreateConfig(U["bars"], T["Scan Macros For Spells"], C.bars, "macroscan", "checkbox", nil, nil, nil, nil)
end
CreateConfig(U["bars"], T["Show Reagent Count"], C.bars, "reagents", "checkbox") CreateConfig(U["bars"], T["Show Reagent Count"], C.bars, "reagents", "checkbox")
CreateConfig(U["bars"], T["Highlight Equipped Items"], C.bars, "showequipped", "checkbox") CreateConfig(U["bars"], T["Highlight Equipped Items"], C.bars, "showequipped", "checkbox")
CreateConfig(U["bars"], T["Equipped Item Color"], C.bars, "eqcolor", "color") CreateConfig(U["bars"], T["Equipped Item Color"], C.bars, "eqcolor", "color")
@@ -2795,6 +2799,8 @@ pfUI:RegisterModule("gui", function ()
CreateConfig(nil, T["Use Unit Fonts"], C.castbar, "use_unitfonts", "checkbox") CreateConfig(nil, T["Use Unit Fonts"], C.castbar, "use_unitfonts", "checkbox")
CreateConfig(nil, T["Casting Color"], C.appearance.castbar, "castbarcolor", "color") CreateConfig(nil, T["Casting Color"], C.appearance.castbar, "castbarcolor", "color")
CreateConfig(nil, T["Channeling Color"], C.appearance.castbar, "channelcolor", "color") CreateConfig(nil, T["Channeling Color"], C.appearance.castbar, "channelcolor", "color")
CreateConfig(nil, T["Uninterruptible Color"], C.appearance.castbar, "shieldcolor", "color")
CreateConfig(nil, T["Five Second Rule Color"], C.appearance.castbar, "fsrcolor", "color")
CreateConfig(nil, T["Castbar Texture"], C.appearance.castbar, "texture", "dropdown", pfUI.gui.dropdowns.uf_bartexture) CreateConfig(nil, T["Castbar Texture"], C.appearance.castbar, "texture", "dropdown", pfUI.gui.dropdowns.uf_bartexture)
CreateConfig(nil, T["Disable Blizzard Castbar"], C.castbar.player, "hide_blizz", "checkbox") CreateConfig(nil, T["Disable Blizzard Castbar"], C.castbar.player, "hide_blizz", "checkbox")
end) end)
@@ -2867,6 +2873,7 @@ pfUI:RegisterModule("gui", function ()
CreateConfig(nil, T["Generate Playerlinks"], C.chat.text, "playerlinks", "checkbox") CreateConfig(nil, T["Generate Playerlinks"], C.chat.text, "playerlinks", "checkbox")
CreateConfig(nil, T["Enable URL Detection"], C.chat.text, "detecturl", "checkbox") CreateConfig(nil, T["Enable URL Detection"], C.chat.text, "detecturl", "checkbox")
CreateConfig(nil, T["Enable Class Colors"], C.chat.text, "classcolor", "checkbox") CreateConfig(nil, T["Enable Class Colors"], C.chat.text, "classcolor", "checkbox")
CreateConfig(nil, T["Show Faction & Race Icons"] .. " " .. GetPlayerRaceIcons(), C.chat.text, "playericons", "checkbox")
CreateConfig(nil, T["Enable Player Levels"], C.chat.text, "playerlevel", "checkbox") CreateConfig(nil, T["Enable Player Levels"], C.chat.text, "playerlevel", "checkbox")
CreateConfig(nil, T["Who Search Unknown Classes (|cffffaaaaExperimental|r)"], C.chat.text, "whosearchunknown", "checkbox") CreateConfig(nil, T["Who Search Unknown Classes (|cffffaaaaExperimental|r)"], C.chat.text, "whosearchunknown", "checkbox")
CreateConfig(nil, T["Colorize Unknown Classes"], C.chat.text, "tintunknown", "checkbox") CreateConfig(nil, T["Colorize Unknown Classes"], C.chat.text, "tintunknown", "checkbox")
@@ -2880,9 +2887,9 @@ pfUI:RegisterModule("gui", function ()
CreateConfig(nil, T["Only Show Chat Dock On Mouseover"], C.chat.global, "tabmouse", "checkbox") CreateConfig(nil, T["Only Show Chat Dock On Mouseover"], C.chat.global, "tabmouse", "checkbox")
CreateConfig(nil, T["Enable Chat Tab Flashing"], C.chat.global, "chatflash", "checkbox") CreateConfig(nil, T["Enable Chat Tab Flashing"], C.chat.global, "chatflash", "checkbox")
CreateConfig(nil, T["Enable Frame Shadow"], C.chat.global, "frameshadow", "checkbox") CreateConfig(nil, T["Enable Frame Shadow"], C.chat.global, "frameshadow", "checkbox")
CreateConfig(nil, T["Enable Custom Colors"], C.chat.global, "custombg", "checkbox") CreateConfig(U["chat"], T["Enable Custom Colors"], C.chat.global, "custombg", "checkbox")
CreateConfig(nil, T["Chat Background Color"], C.chat.global, "background", "color") CreateConfig(U["chat"], T["Chat Background Color"], C.chat.global, "background", "color")
CreateConfig(nil, T["Chat Border Color"], C.chat.global, "border", "color") CreateConfig(U["chat"], T["Chat Border Color"], C.chat.global, "border", "color")
CreateConfig(nil, T["Enable Custom Incoming Whispers Layout"], C.chat.global, "whispermod", "checkbox") CreateConfig(nil, T["Enable Custom Incoming Whispers Layout"], C.chat.global, "whispermod", "checkbox")
CreateConfig(nil, T["Incoming Whispers Color"], C.chat.global, "whisper", "color") CreateConfig(nil, T["Incoming Whispers Color"], C.chat.global, "whisper", "color")
CreateConfig(nil, T["Enable Sticky Chat"], C.chat.global, "sticky", "checkbox") CreateConfig(nil, T["Enable Sticky Chat"], C.chat.global, "sticky", "checkbox")
@@ -3028,8 +3035,7 @@ pfUI:RegisterModule("gui", function ()
CreateGUIEntry(T["Components"], T["Modules"], function() CreateGUIEntry(T["Components"], T["Modules"], function()
table.sort(pfUI.modules) table.sort(pfUI.modules)
for i,m in pairs(pfUI.modules) do for i,m in pairs(pfUI.modules) do
-- skip gui and macrotweak when macro addons are loaded if m ~= "gui" then
if m ~= "gui" and not (m == "macrotweak" and pfUI:MacroAddonsLoaded()) then
-- create disabled entry if not existing and display -- create disabled entry if not existing and display
pfUI:UpdateConfig("disabled", nil, m, "0") pfUI:UpdateConfig("disabled", nil, m, "0")
CreateConfig(nil, T["Disable Module"] .. " " .. m, C.disabled, m, "checkbox") CreateConfig(nil, T["Disable Module"] .. " " .. m, C.disabled, m, "checkbox")
+1 -1
View File
@@ -511,7 +511,7 @@ pfUI:RegisterModule("loot", function ()
end end
function pfUI.loot:CreateSlot(id) function pfUI.loot:CreateSlot(id)
local frame = CreateFrame(LOOT_BUTTON_FRAME_TYPE, 'pfLootButton'..id, pfUI.loot) local frame = CreateFrame("LootButton", 'pfLootButton'..id, pfUI.loot)
frame:RegisterForClicks('LeftButtonUp', 'RightButtonUp') frame:RegisterForClicks('LeftButtonUp', 'RightButtonUp')
frame:SetPoint("LEFT", border*2, 0) frame:SetPoint("LEFT", border*2, 0)
frame:SetPoint("RIGHT", -border*2, 0) frame:SetPoint("RIGHT", -border*2, 0)
+6 -17
View File
@@ -104,7 +104,6 @@ pfUI:RegisterModule("loothistory", function ()
-- Frame pools -- Frame pools
-- ========================================================================== -- ==========================================================================
local itemFrames = {} local itemFrames = {}
local usedPlayers, freePlayers = {}, {}
local FullUpdate -- forward declaration (toggle handlers call it) local FullUpdate -- forward declaration (toggle handlers call it)
@@ -204,20 +203,10 @@ pfUI:RegisterModule("loothistory", function ()
return f return f
end end
local function RecycleAllPlayers() local playerPool = CreateObjectPool(CreatePlayerFrame, function(_, pf)
for i = 1, table.getn(usedPlayers) do pf:Hide()
local pf = usedPlayers[i] pf:ClearAllPoints()
pf:Hide() end)
table.insert(freePlayers, pf)
end
usedPlayers = {}
end
local function GetPlayerFrame()
local pf = table.remove(freePlayers) or CreatePlayerFrame()
table.insert(usedPlayers, pf)
return pf
end
local function SetToggleTexture(toggle, isExpanded) local function SetToggleTexture(toggle, isExpanded)
if isExpanded then if isExpanded then
@@ -309,7 +298,7 @@ pfUI:RegisterModule("loothistory", function ()
function FullUpdate() function FullUpdate()
if not pfUI.loothistory:IsShown() then return end if not pfUI.loothistory:IsShown() then return end
RecycleAllPlayers() playerPool:ReleaseAll()
local num = C_LootHistory.GetNumItems() local num = C_LootHistory.GetNumItems()
local y = -2 local y = -2
@@ -327,7 +316,7 @@ pfUI:RegisterModule("loothistory", function ()
for p = 1, f.numPlayers do for p = 1, f.numPlayers do
local name, class, rollType, roll, isWinner, isMe = C_LootHistory.GetPlayerInfo(i, p) local name, class, rollType, roll, isWinner, isMe = C_LootHistory.GetPlayerInfo(i, p)
if ShouldDisplayPlayer(f.isDone, roll, isMe) then if ShouldDisplayPlayer(f.isDone, roll, isMe) then
local pf = GetPlayerFrame() local pf = playerPool:Acquire()
RenderPlayerFrame(pf, name, class, rollType, roll, isWinner) RenderPlayerFrame(pf, name, class, rollType, roll, isWinner)
pf:ClearAllPoints() pf:ClearAllPoints()
pf:SetPoint("TOPLEFT", list, "TOPLEFT", 22, y) pf:SetPoint("TOPLEFT", list, "TOPLEFT", 22, y)
-67
View File
@@ -1,67 +0,0 @@
pfUI:RegisterModule("macrotweak", function ()
local conflictAddons = { "Supermacro", "SuperCleveRoidMacros", "UltimaMacros" }
local disabled = false
for _, addon in pairs(conflictAddons) do
local name = addon
EventUtil.ContinueOnAddOnLoaded(name, function()
if not disabled then
DEFAULT_CHAT_FRAME:AddMessage("|cff33ffccpfUI|r: " .. name .. " found, macrotweak disabled.")
end
disabled = true
end)
end
-- do not write macro calls into chat input history
-- (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)
if disabled then return ChatFrameEditBox._AddHistoryLine(self, text) end
if not userinput and text and string.find(text, "^/run(.+)") then return end
if not userinput and string.find(text, "^/script(.+)") then return end
if not userinput and string.find(text, "^/cast(.+)") then return end
ChatFrameEditBox._AddHistoryLine(self, text)
end
local OnEnter = ChatFrameEditBox:GetScript("OnEnterPressed")
ChatFrameEditBox:SetScript("OnEnterPressed", function(a1,a2,a3,a4)
userinput = true
OnEnter(a1,a2,a3,a4)
userinput = nil
end)
end
-- make sure #showtooltip inside macros won't be sent
local hookSendChatMessage = SendChatMessage
function _G.SendChatMessage(msg, ...)
if disabled then return hookSendChatMessage(msg, unpack(arg)) end
if msg and string.find(msg, "^#showtooltip ") then return end
hookSendChatMessage(msg, unpack(arg))
end
-- add /use and /equip to the macro api:
-- https://wowwiki.fandom.com/wiki/Making_a_macro
-- supported arguments:
-- /use <itemname>
-- /use <inventory slot>
-- /use <bag> <slot>
pfUI.api.RegisterSlashCommand("PFUSE", { "/equip" , "/use", "/pfequip", "/pfuse" }, function (msg)
if not msg or msg == "" then return end
local bag, slot, _
if string.find(msg, "%d+%s+%d+") then
_, _, bag, slot = string.find(msg, "(%d+)%s+(%d+)")
elseif string.find(msg, "%d+") then
_, _, slot = string.find(msg, "(%d+)")
else
bag, slot = FindItem(msg)
end
if bag and slot then
UseContainerItem(bag, slot)
elseif not bag and slot then
UseInventoryItem(slot)
end
end)
end)
+42 -44
View File
@@ -30,20 +30,14 @@ pfUI:RegisterModule("mapreveal", function ()
pfUI.mapreveal:UpdateConfig() pfUI.mapreveal:UpdateConfig()
end) end)
local explores = {}
local explorecaches = {} local explorecaches = {}
local alreadyknown = {} -- per-zone accumulator: { [zone] = { [texName] = true } } local alreadyknown = {} -- per-zone accumulator: { [zone] = { [texName] = true } }
-- Own texture pool - separate from Blizzard's WorldMapOverlay textures -- Own texture pool - separate from Blizzard's WorldMapOverlay textures
local pfOverlays = {} local overlayPool = CreateTexturePool(WorldMapDetailFrame, "BORDER", nil, nil, function(_, tex)
local pfOverlayMax = 0 tex:Hide()
tex:ClearAllPoints()
local function pfGetOverlay(idx) end)
if not pfOverlays[idx] then
pfOverlays[idx] = WorldMapDetailFrame:CreateTexture("pfReveal"..idx, "BORDER")
end
return pfOverlays[idx]
end
local exploreEnter = function() local exploreEnter = function()
WorldMapTooltip:ClearLines() WorldMapTooltip:ClearLines()
@@ -69,14 +63,38 @@ pfUI:RegisterModule("mapreveal", function ()
end end
end end
-- Magnifying-glass icons for unexplored overlays. Everything constant lives
-- in the creator; the update only anchors and labels what it acquires -- and
-- it only acquires the ones it is going to show, where the old table grew an
-- icon for every overlay in the zone and hid most of them again.
local function CreateExplore()
local explore = CreateFrame("Frame", nil, WorldMapDetailFrame)
explore:SetSize(16, 16)
explore:SetScript("OnEnter", exploreEnter)
explore:SetScript("OnLeave", exploreLeave)
explore:EnableMouse(true)
explore:SetFrameLevel(255)
explore.tex = explore:CreateTexture(nil, "OVERLAY")
explore.tex:SetTexture("Interface\\WorldMap\\WorldMap-MagnifyingGlass")
explore.tex:SetBlendMode("ADD")
explore.tex:SetTexCoord(.08, .92, .08, .92)
explore.tex:SetAllPoints()
return explore
end
local explorePool = CreateObjectPool(CreateExplore, function(_, explore)
explore:Hide()
explore:ClearAllPoints()
end)
local function pfWorldMapFrame_Update() local function pfWorldMapFrame_Update()
-- clear stale caches -- clear stale caches
for k in pairs(explorecaches) do explorecaches[k] = nil end for k in pairs(explorecaches) do explorecaches[k] = nil end
-- hide all our textures from last frame -- hide all our textures from last frame
for i = 1, pfOverlayMax do overlayPool:ReleaseAll()
pfOverlays[i]:Hide()
end
local r,g,b,a = GetStringColor(C.appearance.worldmap.mapreveal_color) local r,g,b,a = GetStringColor(C.appearance.worldmap.mapreveal_color)
local mapFileName = GetMapInfo() local mapFileName = GetMapInfo()
@@ -94,14 +112,13 @@ pfUI:RegisterModule("mapreveal", function ()
local zoneKnown = alreadyknown[mapFileName] local zoneKnown = alreadyknown[mapFileName]
-- hide explore icons -- hide explore icons
for _, frame in pairs(explores) do frame:Hide() end explorePool:ReleaseAll()
-- ClassicAPI: full overlay list for the viewed zone (explored + unexplored), -- ClassicAPI: full overlay list for the viewed zone (explored + unexplored),
-- read straight from WorldMapOverlay.dbc. Replaces the hand-measured pfMapOverlayData. -- read straight from WorldMapOverlay.dbc. Replaces the hand-measured pfMapOverlayData.
local zoneData = C_Map.GetMapOverlays() or {} local zoneData = C_Map.GetMapOverlays() or {}
local textureCount = 0
for i, overlay in ipairs(zoneData) do for _, overlay in ipairs(zoneData) do
local name = overlay.textureName -- bare, e.g. "DRYGULCHRAVINE" local name = overlay.textureName -- bare, e.g. "DRYGULCHRAVINE"
local textureName = overlay.texturePath -- full engine path (for SetTexture) local textureName = overlay.texturePath -- full engine path (for SetTexture)
local textureWidth = overlay.textureWidth local textureWidth = overlay.textureWidth
@@ -109,30 +126,15 @@ pfUI:RegisterModule("mapreveal", function ()
local offsetX = overlay.offsetX local offsetX = overlay.offsetX
local offsetY = overlay.offsetY local offsetY = overlay.offsetY
-- explore magnifying glass icon -- explore magnifying glass icon. `alreadyknown` stores the FULL paths
explores[i] = explores[i] or CreateFrame("Frame", nil, WorldMapDetailFrame) -- GetMapOverlayInfo returns, so compare with the full path, not the bare
local explore = explores[i] -- name.
explore:SetWidth(16)
explore:SetHeight(16)
explore:SetPoint("TOPLEFT", "WorldMapDetailFrame", "TOPLEFT", offsetX + textureWidth/2, -offsetY - textureHeight/2)
explore:SetScript("OnEnter", exploreEnter)
explore:SetScript("OnLeave", exploreLeave)
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)
explore.tex:SetAllPoints()
-- `alreadyknown` stores the FULL paths GetMapOverlayInfo returns,
-- so compare with the full path, not the bare name.
if C.appearance.worldmap.mapexploration == "1" and not zoneKnown[string.upper(textureName)] then if C.appearance.worldmap.mapexploration == "1" and not zoneKnown[string.upper(textureName)] then
explore.tex:SetTexture("Interface\\WorldMap\\WorldMap-MagnifyingGlass") local explore = explorePool:Acquire()
explore:SetPoint("TOPLEFT", "WorldMapDetailFrame", "TOPLEFT", offsetX + textureWidth/2, -offsetY - textureHeight/2)
explore.name = mapFileName .. " (" .. name .. ")"
explore.area = name -- cache key: explorecaches is keyed by the plain area name
explore:Show() explore:Show()
else
explore:Hide()
end end
-- render overlay texture tiles on BORDER draw layer -- render overlay texture tiles on BORDER draw layer
@@ -147,11 +149,9 @@ pfUI:RegisterModule("mapreveal", function ()
-- exactly what shears quirky overlays (e.g. Icepoint's Kaneq'nuun). -- exactly what shears quirky overlays (e.g. Icepoint's Kaneq'nuun).
if C.appearance.worldmap.mapreveal == "1" then if C.appearance.worldmap.mapreveal == "1" then
for _, tile in ipairs(overlay.tiles) do for _, tile in ipairs(overlay.tiles) do
textureCount = textureCount + 1 local tex = overlayPool:Acquire()
local tex = pfGetOverlay(textureCount)
tex:SetWidth(tile.width) tex:SetSize(tile.width, tile.height)
tex:SetHeight(tile.height)
tex:SetTexCoord(0, tile.texCoordX, 0, tile.texCoordY) tex:SetTexCoord(0, tile.texCoordX, 0, tile.texCoordY)
tex:ClearAllPoints() tex:ClearAllPoints()
tex:SetPoint("TOPLEFT", "WorldMapDetailFrame", "TOPLEFT", tile.offsetX, -tile.offsetY) tex:SetPoint("TOPLEFT", "WorldMapDetailFrame", "TOPLEFT", tile.offsetX, -tile.offsetY)
@@ -165,8 +165,6 @@ pfUI:RegisterModule("mapreveal", function ()
end end
end end
end end
pfOverlayMax = math.max(pfOverlayMax, textureCount)
end end
-- hook WorldMapFrame_Update -- hook WorldMapFrame_Update
+38 -18
View File
@@ -1,12 +1,9 @@
pfUI:RegisterModule("marktracking", function () pfUI:RegisterModule("marktracking", function ()
if not UnitExists("mark1") and not UnitExists("mark8") then
if not pcall(function() UnitExists("mark1") end) then return end
end
local rawborder, border = GetBorderSize() local rawborder, border = GetBorderSize()
local markerOrder = { 8, 7, 6, 5, 4, 3, 2, 1 } -- skull, cross, square, moon, triangle, diamond, circle, star local markerOrder = { 8, 7, 6, 5, 4, 3, 2, 1 } -- skull, cross, square, moon, triangle, diamond, circle, star
local markerTokens = {} local markerTokens = {} -- [i] = "markN"
local markerIndex = {} -- ["markN"] = i, for the event handler's arg1
local markerConfigKeys = { local markerConfigKeys = {
"raidmarkercolor_star", "raidmarkercolor_star",
@@ -22,6 +19,7 @@ pfUI:RegisterModule("marktracking", function ()
local markerColors = {} local markerColors = {}
for i, markKey in ipairs(markerConfigKeys) do for i, markKey in ipairs(markerConfigKeys) do
markerTokens[i] = "mark" .. i markerTokens[i] = "mark" .. i
markerIndex[markerTokens[i]] = i
local r, g, b, a = GetStringColor(C.unitframes[markKey]) local r, g, b, a = GetStringColor(C.unitframes[markKey])
markerColors[i] = { tonumber(r), tonumber(g), tonumber(b), tonumber(a) } markerColors[i] = { tonumber(r), tonumber(g), tonumber(b), tonumber(a) }
end end
@@ -73,8 +71,7 @@ pfUI.marktracking = CreateFrame("Frame", "pfMarkTracking", UIParent)
else else
pfUI.marktracking:SetPoint("TOP", UIParent, "CENTER", 0, 0) pfUI.marktracking:SetPoint("TOP", UIParent, "CENTER", 0, 0)
end end
pfUI.marktracking:SetWidth(TOTAL_ROW_WIDTH) pfUI.marktracking:SetSize(TOTAL_ROW_WIDTH, 8 * (ROW_HEIGHT + 1) + border * 2 - 1)
pfUI.marktracking:SetHeight(8 * (ROW_HEIGHT + 1) + border * 2 - 1)
pfUI.marktracking:Hide() pfUI.marktracking:Hide()
CreateBackdrop(pfUI.marktracking) CreateBackdrop(pfUI.marktracking)
@@ -284,27 +281,50 @@ pfUI.marktracking = CreateFrame("Frame", "pfMarkTracking", UIParent)
-- Event-driven scanner frame -- Event-driven scanner frame
local scanner = CreateFrame("Frame") local scanner = CreateFrame("Frame")
-- 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). UpdateDisplay
-- is a full eight-row rebuild, so it exists only while grouped -- raid markers
-- are a group feature and there is nothing to discover alone. The group events
-- below start and cancel it, so outside a group there is no timer queued at
-- all rather than one waking every second to return early.
--
-- Deliberately NOT keyed on a mark being visible: a marker set on a unit that
-- is out of range shows no row, and that is exactly what this poll catches.
local poll
local function UpdatePoll()
local grouped = IsInGroup()
if grouped and not poll then
poll = C_Timer.NewTicker(FALLBACK_INTERVAL, UpdateDisplay)
elseif not grouped and poll then
poll:Cancel()
poll = nil
end
end
-- RAID_TARGET_UPDATE: a raid marker was set/cleared -> full refresh -- RAID_TARGET_UPDATE: a raid marker was set/cleared -> full refresh
-- PLAYER_ENTERING_WORLD: login/reload/zone -> full refresh -- PLAYER_ENTERING_WORLD: login/reload/zone -> full refresh
-- UNIT_HEALTH/UNIT_MAXHEALTH: ClassicAPI fires these per token; with the mark -- PARTY_MEMBERS_CHANGED/RAID_ROSTER_UPDATE: joined or left a group -> the rows
-- tokens observed they arrive as arg1 == "markN", so we refresh just that -- can change, and the fallback poll starts or stops with it
-- one row (UpdateRow) instead of rescanning all eight. -- UNIT_HEALTH/UNIT_MAXHEALTH: filtered to the eight mark tokens, so arg1 is
-- always "markN" and we refresh just that row (UpdateRow) instead of
-- rescanning all eight.
scanner:RegisterEvent("RAID_TARGET_UPDATE") scanner:RegisterEvent("RAID_TARGET_UPDATE")
scanner:RegisterEvent("PLAYER_ENTERING_WORLD") scanner:RegisterEvent("PLAYER_ENTERING_WORLD")
scanner:RegisterEvent("UNIT_HEALTH") scanner:RegisterEvent("PARTY_MEMBERS_CHANGED")
scanner:RegisterEvent("UNIT_MAXHEALTH") scanner:RegisterEvent("RAID_ROSTER_UPDATE")
scanner:RegisterUnitEvent("UNIT_HEALTH", "mark1", "mark2", "mark3", "mark4", "mark5", "mark6", "mark7", "mark8")
scanner:RegisterUnitEvent("UNIT_MAXHEALTH", "mark1", "mark2", "mark3", "mark4", "mark5", "mark6", "mark7", "mark8")
scanner:SetScript("OnEvent", function() scanner:SetScript("OnEvent", function()
if event == "UNIT_HEALTH" or event == "UNIT_MAXHEALTH" then if event == "UNIT_HEALTH" or event == "UNIT_MAXHEALTH" then
-- arg1 is the token; "markN" -> N, nil for any non-mark token. -- arg1 is one of the eight tokens we registered for, so this is a lookup
local i = arg1 and tonumber(string.match(arg1, "^mark(%d)")) -- rather than a parse -- string.match would allocate a capture and
-- tonumber would parse it, on every health tick of every marked unit.
local i = arg1 and markerIndex[arg1]
if i then UpdateRow(i) end if i then UpdateRow(i) end
return return
end end
if event ~= "RAID_TARGET_UPDATE" then UpdatePoll() end
UpdateDisplay() UpdateDisplay()
end) end)
-- 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)
C_Timer.NewTicker(FALLBACK_INTERVAL, UpdateDisplay)
end) end)
+1 -1
View File
@@ -225,7 +225,7 @@ pfUI:RegisterModule("minimap", function ()
pfUI.minimap.pvpicon = CreateFrame("Frame", nil, pfUI.minimap) pfUI.minimap.pvpicon = CreateFrame("Frame", nil, pfUI.minimap)
pfUI.minimap.pvpicon:Hide() pfUI.minimap.pvpicon:Hide()
pfUI.minimap.pvpicon:RegisterEvent("UPDATE_FACTION") pfUI.minimap.pvpicon:RegisterEvent("UPDATE_FACTION")
pfUI.minimap.pvpicon:RegisterEvent("UNIT_FACTION") pfUI.minimap.pvpicon:RegisterUnitEvent("UNIT_FACTION", "player")
pfUI.minimap.pvpicon:SetFrameStrata("HIGH") pfUI.minimap.pvpicon:SetFrameStrata("HIGH")
pfUI.minimap.pvpicon:SetSize(16, 16) pfUI.minimap.pvpicon:SetSize(16, 16)
pfUI.minimap.pvpicon:SetAlpha(.5) pfUI.minimap.pvpicon:SetAlpha(.5)
+130 -111
View File
@@ -5,10 +5,7 @@ pfUI:RegisterModule("nameplates", function ()
-- Local function references for performance -- Local function references for performance
local GetTime = GetTime local GetTime = GetTime
local UnitName = UnitName local UnitName = UnitName
local UnitClass = UnitClass
local UnitLevel = UnitLevel
local UnitIsPlayer = UnitIsPlayer local UnitIsPlayer = UnitIsPlayer
local UnitIsDead = UnitIsDead
local UnitAffectingCombat = UnitAffectingCombat local UnitAffectingCombat = UnitAffectingCombat
local UnitIsUnit = UnitIsUnit local UnitIsUnit = UnitIsUnit
local UnitCanAssist = UnitCanAssist local UnitCanAssist = UnitCanAssist
@@ -80,10 +77,10 @@ pfUI:RegisterModule("nameplates", function ()
-- vs channel itself. Never called per frame. -- vs channel itself. Never called per frame.
local function PollCastInfo(unit) local function PollCastInfo(unit)
if not unit then return nil end if not unit then return nil end
local name, _, texture, startMs, endMs, _, _, _, spellID = C_Spell.UnitCastingInfo(unit) local name, _, texture, startMs, endMs, _, _, noInterrupt, spellID = C_Spell.UnitCastingInfo(unit)
local isChannel local isChannel
if not name then if not name then
name, _, texture, startMs, endMs, _, _, spellID = C_Spell.UnitChannelInfo(unit) name, _, texture, startMs, endMs, _, noInterrupt, spellID = C_Spell.UnitChannelInfo(unit)
isChannel = true isChannel = true
end end
if not name or not startMs or not endMs then return nil end if not name or not startMs or not endMs then return nil end
@@ -95,6 +92,7 @@ pfUI:RegisterModule("nameplates", function ()
endTime = endMs / 1000, endTime = endMs / 1000,
duration = (endMs - startMs) / 1000, duration = (endMs - startMs) / 1000,
isChannel = isChannel, isChannel = isChannel,
noInterrupt = noInterrupt,
} }
end end
@@ -116,6 +114,7 @@ pfUI:RegisterModule("nameplates", function ()
-- Reusable per-plate debuff display buffer (avoid GC churn from per-call table creation) -- Reusable per-plate debuff display buffer (avoid GC churn from per-call table creation)
local debuffDisplayBuf = {} -- [i] = { effect, texture, stacks, dtype, duration, timeleft } local debuffDisplayBuf = {} -- [i] = { effect, texture, stacks, dtype, duration, timeleft }
for i = 1, 16 do debuffDisplayBuf[i] = {} end for i = 1, 16 do debuffDisplayBuf[i] = {} end
local auraSlots = {} -- reusable GetAuraSlots buffer for the per-plate aura scan
local threatMemory = {} -- guid -> true if mob had player targeted local threatMemory = {} -- guid -> true if mob had player targeted
-- local debuffSeen = {} -- reusable table for debuff tracking (avoid GC churn) -- local debuffSeen = {} -- reusable table for debuff tracking (avoid GC churn)
@@ -161,6 +160,24 @@ pfUI:RegisterModule("nameplates", function ()
cfg.debuffanim = tonumber(C.nameplates.debuffanim) or 0 cfg.debuffanim = tonumber(C.nameplates.debuffanim) or 0
cfg.debufftext = tonumber(C.nameplates.debufftext) or 1 cfg.debufftext = tonumber(C.nameplates.debufftext) or 1
-- Throttle delays, resolved once instead of per plate per tick.
-- libthrottle:Get walks the saved-variable table, a defaults fallback and a
-- preset table, and can build a "<category>_custom" key -- the per-plate
-- OnUpdate was calling it one or two times for every visible plate, a
-- hundred times a second, just to decide it had nothing to do.
--
-- cfg.throttle_min is the floor across all four. No plate can ever be due
-- sooner than that, so the update can bail on it before working out which
-- category it actually belongs to.
cfg.throttle_target = pfUI.throttle:Get("nameplates_target")
cfg.throttle_mass = pfUI.throttle:Get("nameplates_mass")
cfg.throttle_normal = pfUI.throttle:Get("nameplates")
cfg.throttle_castbar = pfUI.throttle:Get("nameplates_castbar")
cfg.throttle_min = cfg.throttle_target
if cfg.throttle_mass < cfg.throttle_min then cfg.throttle_min = cfg.throttle_mass end
if cfg.throttle_normal < cfg.throttle_min then cfg.throttle_min = cfg.throttle_normal end
if cfg.throttle_castbar < cfg.throttle_min then cfg.throttle_min = cfg.throttle_castbar end
-- Rebuild offtanks lookup table -- Rebuild offtanks lookup table
offtanks = {} offtanks = {}
for k, v in pairs({strsplit("#", C.nameplates.combatofftanks)}) do for k, v in pairs({strsplit("#", C.nameplates.combatofftanks)}) do
@@ -296,18 +313,23 @@ pfUI:RegisterModule("nameplates", function ()
return plate.creatureType return plate.creatureType
end end
-- Totem icon, read straight from the game: -- Totem icon: UnitCreatedBySpell returns the totem-drop spell, whose icon IS
-- * Passive totems self-cast their provided buff, so they carry exactly one -- the totem's icon. It's a broadcast descriptor field the client has for every
-- aura whose icon IS the totem's icon (Totem::Summon TOTEM_PASSIVE). -- summoned unit in range, so it resolves immediately for passive and active
-- * Active totems (Searing/Magma/Fire Nova) cast at enemies and hold no -- totems alike -- no self-aura read or attack-cast capture needed.
-- self-aura, so their icon arrives via UNIT_SPELLCAST_SUCCEEDED (cached --
-- into plate.totemIcon by the event handler); nil here until then. -- Re-read the spell every call rather than caching the icon outright: a shaman
-- can swap the totem in place (same unit, new drop spell) with no plate re-add
-- to invalidate a cache, so key the cached texture on the spell id and refresh
-- it only when the spell changes.
local function TotemPlate(plate) local function TotemPlate(plate)
if C.nameplates.totemicons ~= "1" then return nil end if C.nameplates.totemicons ~= "1" then return nil end
if CreatureType(plate) ~= 11 then return nil end if CreatureType(plate) ~= 11 then return nil end
if plate.totemIcon then return plate.totemIcon end local spellId = UnitCreatedBySpell(plate.cachedGuid)
local aura = C_UnitAuras.GetBuffDataByIndex(plate.cachedGuid, 1) if spellId ~= plate.totemSpell then
if aura then plate.totemIcon = aura.icon end plate.totemSpell = spellId
plate.totemIcon = spellId and C_Spell.GetSpellTexture(spellId) or nil
end
return plate.totemIcon return plate.totemIcon
end end
@@ -427,7 +449,7 @@ pfUI:RegisterModule("nameplates", function ()
plate.debuffs[index].cd.SetSequenceTime = DoNothing plate.debuffs[index].cd.SetSequenceTime = DoNothing
else else
-- Use CooldownFrameTemplate for animation -- Use CooldownFrameTemplate for animation
plate.debuffs[index].cd = CreateFrame(COOLDOWN_FRAME_TYPE, plate.platename.."Debuff"..index.."Cooldown", plate.debuffs[index], "CooldownFrameTemplate") plate.debuffs[index].cd = CreateFrame("Model", plate.platename.."Debuff"..index.."Cooldown", plate.debuffs[index], "CooldownFrameTemplate")
plate.debuffs[index].cd:SetAllPoints(plate.debuffs[index]) plate.debuffs[index].cd:SetAllPoints(plate.debuffs[index])
plate.debuffs[index].cd:SetFrameLevel(6) plate.debuffs[index].cd:SetFrameLevel(6)
end end
@@ -484,7 +506,7 @@ local nameplates = CreateFrame("Frame", "pfNameplates", UIParent)
nameplates:RegisterEvent("PLAYER_ENTERING_WORLD") nameplates:RegisterEvent("PLAYER_ENTERING_WORLD")
nameplates:RegisterEvent("PLAYER_TARGET_CHANGED") nameplates:RegisterEvent("PLAYER_TARGET_CHANGED")
nameplates:RegisterEvent("PLAYER_LOGOUT") nameplates:RegisterEvent("PLAYER_LOGOUT")
nameplates:RegisterEvent("UNIT_COMBO_POINTS") nameplates:RegisterUnitEvent("UNIT_COMBO_POINTS", "player")
nameplates:RegisterEvent("PLAYER_COMBO_POINTS") nameplates:RegisterEvent("PLAYER_COMBO_POINTS")
nameplates:RegisterEvent("ZONE_CHANGED_NEW_AREA") nameplates:RegisterEvent("ZONE_CHANGED_NEW_AREA")
nameplates:RegisterEvent("RAID_ROSTER_UPDATE") nameplates:RegisterEvent("RAID_ROSTER_UPDATE")
@@ -492,14 +514,10 @@ nameplates:RegisterEvent("PARTY_MEMBERS_CHANGED")
nameplates:RegisterEvent("NAME_PLATE_CREATED") nameplates:RegisterEvent("NAME_PLATE_CREATED")
nameplates:RegisterEvent("NAME_PLATE_UNIT_ADDED") nameplates:RegisterEvent("NAME_PLATE_UNIT_ADDED")
nameplates:RegisterEvent("NAME_PLATE_UNIT_REMOVED") nameplates:RegisterEvent("NAME_PLATE_UNIT_REMOVED")
nameplates:RegisterEvent("UNIT_AURA")
nameplates:RegisterEvent("UNIT_FLAGS")
nameplates:RegisterEvent("UPDATE_MOUSEOVER_UNIT") nameplates:RegisterEvent("UPDATE_MOUSEOVER_UNIT")
nameplates:RegisterEvent("UNIT_SPELLCAST_START") -- UNIT_AURA / UNIT_FLAGS / UNIT_SPELLCAST_* are registered per plate, on the
nameplates:RegisterEvent("UNIT_SPELLCAST_CHANNEL_START") -- plate's own frame, against its own token -- see OnCreate and
nameplates:RegisterEvent("UNIT_SPELLCAST_STOP") -- NAME_PLATE_UNIT_ADDED.
nameplates:RegisterEvent("UNIT_SPELLCAST_CHANNEL_STOP")
nameplates:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED")
nameplates:RegisterEvent("PLAYER_GUILD_UPDATE") nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
nameplates:SetScript("OnEvent", function() nameplates:SetScript("OnEvent", function()
@@ -511,6 +529,15 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
if nameplates.mouselook then if nameplates.mouselook then
nameplates.mouselook:SetScript("OnUpdate", nil) nameplates.mouselook:SetScript("OnUpdate", nil)
end end
-- The plates hold their own unit subscriptions now, so silencing this
-- frame alone would leave them dispatching through logout -- exactly what
-- this branch exists to prevent.
for plate in pairs(registry) do
if plate.nameplate then
plate.nameplate:UnregisterAllEvents()
plate.nameplate:SetScript("OnEvent", nil)
end
end
return return
elseif event == "PLAYER_GUILD_UPDATE" and arg1 == 'player' then elseif event == "PLAYER_GUILD_UPDATE" and arg1 == 'player' then
@@ -586,8 +613,19 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
local guid = UnitGUID(arg1) local guid = UnitGUID(arg1)
plate.nameplate.cachedGuid = guid plate.nameplate.cachedGuid = guid
plate.nameplate.unit = arg1 plate.nameplate.unit = arg1
-- Point this plate's own subscriptions at the token it just took. On a
-- recycled frame these replace the previous unit rather than stacking:
-- RegisterUnitEvent on an already-filtered registration swaps the units.
plate.nameplate:RegisterUnitEvent("UNIT_AURA", arg1)
plate.nameplate:RegisterUnitEvent("UNIT_FLAGS", arg1)
plate.nameplate:RegisterUnitEvent("UNIT_SPELLCAST_START", arg1)
plate.nameplate:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_START", arg1)
plate.nameplate:RegisterUnitEvent("UNIT_SPELLCAST_STOP", arg1)
plate.nameplate:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_STOP", arg1)
plate.nameplate.creatureType = nil -- recompute for the new unit plate.nameplate.creatureType = nil -- recompute for the new unit
plate.nameplate.totemIcon = nil plate.nameplate.totemIcon = nil
plate.nameplate.totemSpell = nil
if guid then if guid then
plateByGuid[guid] = plate.nameplate plateByGuid[guid] = plate.nameplate
-- Seed: the unit may already be mid-cast (its UNIT_SPELLCAST_START -- Seed: the unit may already be mid-cast (its UNIT_SPELLCAST_START
@@ -615,14 +653,9 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
if plate and plate.nameplate and plate.nameplate.cachedGuid == guid then if plate and plate.nameplate and plate.nameplate.cachedGuid == guid then
plate.nameplate.cachedGuid = nil plate.nameplate.cachedGuid = nil
plate.nameplate.unit = nil plate.nameplate.unit = nil
end -- Drop the subscriptions with the token: this slot is now free and the
end -- next plate to take it would otherwise feed this frame its events.
plate.nameplate:UnregisterAllEvents()
elseif event == "UNIT_FLAGS" then
if arg1 and strfind(arg1, "^nameplate") then
local plate = C_NamePlate.GetNamePlateForUnit(arg1)
if plate and plate.nameplate then
plate.nameplate.eventcache = true
end end
end end
@@ -637,64 +670,6 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
if pn then pn.eventcache = true end if pn then pn.eventcache = true end
end end
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.
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 == "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
-- ClassicAPI: fires with arg1 == "nameplateN" when a unit's aura set
-- changes (add/remove/modify). Flag the matching plate so OnUpdate does a
-- fresh C_UnitAuras read next tick instead of waiting on the 0.5s
-- throttle -- covers expirations, dispels, refreshes, and stack changes
-- in one event. Guard on the token prefix (UNIT_AURA also fires for
-- target/party/raid).
if arg1 and strfind(arg1, "^nameplate") then
local plate = C_NamePlate.GetNamePlateForUnit(arg1)
if plate and plate.nameplate then
plate.nameplate.auraUpdate = true
end
end
elseif event == "PLAYER_TARGET_CHANGED" then elseif event == "PLAYER_TARGET_CHANGED" then
frameState.targetGuid = UnitGUID('target') frameState.targetGuid = UnitGUID('target')
-- Flag the target's plate for update -- Flag the target's plate for update
@@ -776,11 +751,47 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
nameplate.cache = {} nameplate.cache = {}
nameplate.original = {} nameplate.original = {}
-- Each plate watches its own unit. With RegisterUnitEvent the token IS the
-- subscription, so there is no central listener sifting every unit event in
-- the world for a "^nameplate" prefix and then resolving the plate back out
-- of arg1 -- the event arrives only at the plate it concerns, and `this` is
-- already that plate. NAME_PLATE_UNIT_ADDED points the registration at the
-- new token; _REMOVED drops it, which matters because freed slots are
-- reused and a stale token would feed this frame another unit's events.
nameplate:SetScript("OnEvent", function()
if event == "UNIT_AURA" then
-- a fresh C_UnitAuras read next tick rather than waiting out the 0.5s
-- throttle -- covers expiry, dispels, refreshes and stack changes
this.auraUpdate = true
elseif event == "UNIT_FLAGS" then
this.eventcache = true
elseif event == "UNIT_SPELLCAST_START" or event == "UNIT_SPELLCAST_CHANNEL_START" then
-- the payload carries no timing, so poll it (PollCastInfo picks cast
-- vs channel)
local guid = this.cachedGuid
if guid then
castState[guid] = PollCastInfo(this.unit)
if castState[guid] then
this.castUpdate = true -- bypass the throttle so the bar shows now
end
end
elseif event == "UNIT_SPELLCAST_STOP" or event == "UNIT_SPELLCAST_CHANNEL_STOP" then
-- ended: natural, interrupted or cancelled -- the poll fires STOP for all
local guid = this.cachedGuid
if guid and castState[guid] then
castState[guid] = nil
this.castUpdate = true
end
end
end)
-- create shortcuts for all known elements and disable them -- create shortcuts for all known elements and disable them
nameplate.original.healthbar, nameplate.original.castbar = parent:GetChildren() nameplate.original.healthbar, nameplate.original.castbar = parent:GetChildren()
DisableObject(nameplate.original.healthbar) DisableObject(nameplate.original.healthbar)
DisableObject(nameplate.original.castbar) DisableObject(nameplate.original.castbar)
local NAMEPLATE_OBJECTORDER = { "border", "glow", "name", "level", "levelicon", "raidicon" }
for i, object in pairs({parent:GetRegions()}) do for i, object in pairs({parent:GetRegions()}) do
if NAMEPLATE_OBJECTORDER[i] and NAMEPLATE_OBJECTORDER[i] == "raidicon" then if NAMEPLATE_OBJECTORDER[i] and NAMEPLATE_OBJECTORDER[i] == "raidicon" then
nameplate[NAMEPLATE_OBJECTORDER[i]] = object nameplate[NAMEPLATE_OBJECTORDER[i]] = object
@@ -1119,7 +1130,7 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
local TotemIcon = TotemPlate(plate) local TotemIcon = TotemPlate(plate)
if TotemIcon then if TotemIcon then
-- icon resolved from the totem's aura / attack cast (already a full path) -- icon resolved from the totem-drop spell (already a full path)
plate.totem.icon:SetTexture(TotemIcon) plate.totem.icon:SetTexture(TotemIcon)
plate.glow:Hide() plate.glow:Hide()
@@ -1323,11 +1334,12 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
if unitstr then if unitstr then
local filter = cfg.owndebuffs and "HARMFUL|PLAYER" or "HARMFUL" local filter = cfg.owndebuffs and "HARMFUL|PLAYER" or "HARMFUL"
local now = GetTime() local now = GetTime()
-- positional UnitAura writes straight into the reusable buffer, so the -- one GetAuraSlots enumeration, then positional by-slot reads straight
-- per-plate scan allocates nothing (no per-aura table, no result array) -- into the reusable buffer: the per-plate scan allocates nothing (no
local i = 1 -- per-aura table, no result array) and walks the aura array once
while debuffCount < 16 do local n = ScanAuraSlots(unitstr, filter, auraSlots, 16)
local aname, icon, count, dispelType, duration, expirationTime = C_UnitAuras.UnitAura(unitstr, i, filter) for i = 1, n do
local aname, icon, count, dispelType, duration, expirationTime = C_UnitAuras.UnitAuraBySlot(unitstr, auraSlots[i])
if not aname then break end if not aname then break end
debuffCount = debuffCount + 1 debuffCount = debuffCount + 1
local b = debuffDisplayBuf[debuffCount] local b = debuffDisplayBuf[debuffCount]
@@ -1337,7 +1349,6 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
b.dtype = dispelType b.dtype = dispelType
b.duration = duration b.duration = duration
b.timeleft = (expirationTime and expirationTime > 0) and (expirationTime - now) or nil b.timeleft = (expirationTime and expirationTime > 0) and (expirationTime - now) or nil
i = i + 1
end end
end end
for i = 1, 16 do for i = 1, 16 do
@@ -1417,6 +1428,16 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
-- cachedGuid is maintained by NAME_PLATE_UNIT_ADDED / _REMOVED events. -- cachedGuid is maintained by NAME_PLATE_UNIT_ADDED / _REMOVED events.
-- Cheap gate first. The central loop calls this for every visible plate ~100
-- times a second, and classifying the plate below costs two C calls, a cast
-- lookup and a throttle resolution -- all of it wasted on a plate that is
-- throttled to 10fps. cfg.throttle_min is the floor across every category,
-- so nothing that would have updated can be turned away here; the real
-- category-specific throttle is still applied after the classification.
-- Event flags bypass both gates, as before.
local hasEventUpdate = nameplate.eventcache or nameplate.auraUpdate or nameplate.castUpdate or nameplate.targetUpdate or nameplate.comboUpdate
if not hasEventUpdate and (nameplate.lasttick or 0) + cfg.throttle_min > now then return end
-- PERF: Intelligent throttling based on target/castbar status and plate count -- PERF: Intelligent throttling based on target/castbar status and plate count
-- Use GUID comparison as primary target detection: instant, immune to alpha transitions, -- Use GUID comparison as primary target detection: instant, immune to alpha transitions,
-- and immediately correct on de-target (unlike istarget which updates one tick later) -- and immediately correct on de-target (unlike istarget which updates one tick later)
@@ -1443,25 +1464,24 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
end end
end end
-- Resolved in CacheConfig, so these are table reads rather than a walk
-- through the saved variables and preset tables.
local throttle local throttle
if target then if target then
throttle = pfUI.throttle:Get("nameplates_target") throttle = cfg.throttle_target
elseif visiblePlateCount > 20 then elseif visiblePlateCount > 20 then
throttle = pfUI.throttle:Get("nameplates_mass") throttle = cfg.throttle_mass
else else
throttle = pfUI.throttle:Get("nameplates") throttle = cfg.throttle_normal
end end
-- Non-target plates with active castbar use the castbar throttle -- Non-target plates with active castbar use the castbar throttle
if isCastingNonTarget then if isCastingNonTarget and cfg.throttle_castbar < throttle then
local cbThrottle = pfUI.throttle:Get("nameplates_castbar") throttle = cfg.throttle_castbar
if cbThrottle < throttle then throttle = cbThrottle end
end end
-- Check for pending event updates (these bypass throttle for immediate response) -- The category-specific gate. hasEventUpdate was read above, before the
local hasEventUpdate = nameplate.eventcache or nameplate.auraUpdate or nameplate.castUpdate or nameplate.targetUpdate or nameplate.comboUpdate -- classification, and still bypasses the throttle.
-- Event updates bypass throttle
if not hasEventUpdate and (nameplate.lasttick or 0) + throttle > now then return end if not hasEventUpdate and (nameplate.lasttick or 0) + throttle > now then return end
nameplate.lasttick = now nameplate.lasttick = now
@@ -1663,10 +1683,9 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
-- engine framerate, decoupled from central loop). Only update non-target castbars here. -- engine framerate, decoupled from central loop). Only update non-target castbars here.
local isTargetPlate = target or nameplate.istarget or (nameplate.health and nameplate.health.zoomed) local isTargetPlate = target or nameplate.istarget or (nameplate.health and nameplate.health.zoomed)
if cfg.showcastbar and not cfg.targetcastbar and not isTargetPlate then if cfg.showcastbar and not cfg.targetcastbar and not isTargetPlate then
local cbThrottle = pfUI.throttle:Get("nameplates_castbar") local cbThrottle = cfg.throttle_castbar
if visiblePlateCount > 20 then if visiblePlateCount > 20 and cfg.throttle_mass > cbThrottle then
local massThrottle = pfUI.throttle:Get("nameplates_mass") cbThrottle = cfg.throttle_mass
if massThrottle > cbThrottle then cbThrottle = massThrottle end
end end
if (nameplate.castbar_tick or 0) + cbThrottle <= now then if (nameplate.castbar_tick or 0) + cbThrottle <= now then
nameplate.castbar_tick = now nameplate.castbar_tick = now
@@ -1733,7 +1752,7 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
-- Relative 0..duration range to avoid float precision loss with large -- Relative 0..duration range to avoid float precision loss with large
-- absolute timestamps. -- absolute timestamps.
nameplate.castbar:SetMinMaxValues(0, duration) nameplate.castbar:SetMinMaxValues(0, duration)
nameplate.castbar:SetStatusBarColor(GetStringColor(C.appearance.castbar[(isChannel and "channelcolor" or "castbarcolor")])) SetCastbarShield(nameplate.castbar, nameplate.castbar.icon, castInfo.noInterrupt, isChannel)
if castInfo.icon then if castInfo.icon then
nameplate.castbar.icon.tex:SetTexture(castInfo.icon) nameplate.castbar.icon.tex:SetTexture(castInfo.icon)
nameplate.castbar.icon.tex:SetTexCoord(.1,.9,.1,.9) nameplate.castbar.icon.tex:SetTexCoord(.1,.9,.1,.9)
+11 -5
View File
@@ -457,7 +457,7 @@ pfUI:RegisterModule("panel", function()
do -- Zone do -- Zone
local widget = CreateFrame("Frame", "pfPanelWidgetZone", UIParent) local widget = CreateFrame("Frame", "pfPanelWidgetZone", UIParent)
for _,event in pairs(EVENTS_MINIMAP_ZONE_UPDATE) do for _,event in pairs({"PLAYER_ENTERING_WORLD", "MINIMAP_ZONE_CHANGED"}) do
widget:RegisterEvent(event) widget:RegisterEvent(event)
end end
widget.Tooltip = function() widget.Tooltip = function()
@@ -486,7 +486,7 @@ pfUI:RegisterModule("panel", function()
do -- Ammo do -- Ammo
local widget = CreateFrame("Frame", "pfPanelWidgetAmmo", UIParent) local widget = CreateFrame("Frame", "pfPanelWidgetAmmo", UIParent)
widget:RegisterEvent("PLAYER_ENTERING_WORLD") widget:RegisterEvent("PLAYER_ENTERING_WORLD")
widget:RegisterEvent("UNIT_INVENTORY_CHANGED") widget:RegisterUnitEvent("UNIT_INVENTORY_CHANGED", "player")
widget:RegisterEvent("BAG_UPDATE_DELAYED") widget:RegisterEvent("BAG_UPDATE_DELAYED")
widget.Tooltip = function() widget.Tooltip = function()
if GetInventoryItemQuality("player", 0) then if GetInventoryItemQuality("player", 0) then
@@ -777,9 +777,15 @@ pfUI:RegisterModule("panel", function()
pfUI.panel.microbutton:SetSize(145, 23) pfUI.panel.microbutton:SetSize(145, 23)
pfUI.panel.microbutton:SetFrameStrata("MEDIUM") pfUI.panel.microbutton:SetFrameStrata("MEDIUM")
for i=1,table.getn(MICRO_BUTTONS) do local microButtons = {
local anchor = _G[MICRO_BUTTONS[i-1]] or pfUI.panel.microbutton 'CharacterMicroButton', 'SpellbookMicroButton', 'TalentMicroButton',
local button = _G[MICRO_BUTTONS[i]] 'QuestLogMicroButton', 'SocialsMicroButton', 'WorldMapMicroButton',
'MainMenuMicroButton', 'HelpMicroButton',
}
for i=1,table.getn(microButtons) do
local anchor = _G[microButtons[i-1]] or pfUI.panel.microbutton
local button = _G[microButtons[i]]
button:ClearAllPoints() button:ClearAllPoints()
button:SetParent(pfUI.panel.microbutton) button:SetParent(pfUI.panel.microbutton)
if i == 1 then if i == 1 then
+1 -1
View File
@@ -81,7 +81,7 @@ pfUI:RegisterModule("socialmod", function ()
if not info or not info.name or info.name == _G.UNKNOWN then break end if not info or not info.name or info.name == _G.UNKNOWN then break end
local name = info.name local name = info.name
local friendName = _G["FriendsFrameFriendButton"..i.."ButtonTextName"] local friendName = _G["FriendsFrameFriendButton"..i.."ButtonTextName"]
local friendLoc = _G["FriendsFrameFriendButton"..i..FRIENDS_NAME_LOCATION] local friendLoc = _G["FriendsFrameFriendButton"..i.."ButtonTextNameLocation"]
local friendInfo = _G["FriendsFrameFriendButton"..i.."ButtonTextInfo"] local friendInfo = _G["FriendsFrameFriendButton"..i.."ButtonTextInfo"]
local caption = friendName or friendLoc local caption = friendName or friendLoc
+32 -175
View File
@@ -1,10 +1,6 @@
pfUI:RegisterModule("swingtimer", function () pfUI:RegisterModule("swingtimer", function ()
local rawborder, border = GetBorderSize() local rawborder, border = GetBorderSize()
-- HitInfo flags (EVENTS.md)
local HITINFO_LEFTSWING = 4 -- 4: Off-hand attack
local HITINFO_NOACTION = 65536 -- 65536: server did not advance the swing clock
-- SPELL_QUEUE_EVENT codes (EVENTS.md) -- SPELL_QUEUE_EVENT codes (EVENTS.md)
local ON_SWING_QUEUED = 0 local ON_SWING_QUEUED = 0
local ON_SWING_QUEUE_POPPED = 1 local ON_SWING_QUEUE_POPPED = 1
@@ -19,8 +15,6 @@ pfUI:RegisterModule("swingtimer", function ()
lastMhMarkerX = -1, lastOhMarkerX = -1, lastRaMarkerX = -1, lastMhMarkerX = -1, lastOhMarkerX = -1, lastRaMarkerX = -1,
autoAttackActive = false, autoAttackActive = false,
inCombat = false, inCombat = false,
pendingCastSpellId = nil,
mhFrozenAt = nil,
hsQueued = false, cleaveQueued = false, maulQueued = false, hsQueued = false, cleaveQueued = false, maulQueued = false,
hsSeenCurrent = false, cleaveSeenCurrent = false, maulSeenCurrent = false, hsSeenCurrent = false, cleaveSeenCurrent = false, maulSeenCurrent = false,
isWarrior = false, isWarrior = false,
@@ -31,13 +25,6 @@ pfUI:RegisterModule("swingtimer", function ()
onSwingCache = {}, onSwingCache = {},
} }
-- Wand "Shoot" runs on the ranged bar but is INDEPENDENT of the melee
-- swing clock — casters melee-weave between mainhand swings and wand
-- fires, both timers tick concurrently. Every other ranged-auto-attack
-- (Hunter Auto Shot, any future auto-repeat ranged spell) replaces MH.
local WAND_SHOOT_SPELLID = 5019
local THROW_SPELLID = 2764 -- one-shot ranged, not auto-repeat
-- On-next-swing spells replace the next auto-attack swing. -- On-next-swing spells replace the next auto-attack swing.
-- Covers Raptor Strike, Maul, Mongoose Bite, Holy Strike, etc. automatically. -- Covers Raptor Strike, Maul, Mongoose Bite, Holy Strike, etc. automatically.
local function IsOnSwingSpell(spellId) local function IsOnSwingSpell(spellId)
@@ -304,45 +291,37 @@ pfUI:RegisterModule("swingtimer", function ()
S.raSpeed = (rs and rs > 0) and rs or 0 S.raSpeed = (rs and rs > 0) and rs or 0
end end
-- Reset MH countdown to full speed (server confirmed swing) -- Start* restart a countdown from a PLAYER_SWING reset. `remaining` can be
local function ResetMH() -- shorter than the weapon speed (parry haste, ranged wind-up), so the bar
if S.mhFrozenAt then -- length stays the full swing and the fill starts part-way along.
S.mhFrozenAt = nil local function StartMH(remaining)
end
UpdateWeaponSpeeds() UpdateWeaponSpeeds()
pfUI.swingtimer.mhGraceAt = nil -- cancel any pending hide pfUI.swingtimer.mhGraceAt = nil -- cancel any pending hide
S.mhTimerMax = S.mhSpeed S.mhTimerMax = math.max(S.mhSpeed, remaining)
S.mhTimer = S.mhSpeed S.mhTimer = remaining
S.mhActive = true S.mhActive = true
pfUI.swingtimer.mainhand:Show() pfUI.swingtimer.mainhand:Show()
pfUI.swingtimer:Show() pfUI.swingtimer:Show()
end end
-- Reset OH countdown to full speed local function StartOH(remaining)
local function ResetOH()
UpdateWeaponSpeeds() UpdateWeaponSpeeds()
-- The off-hand attack time stays set with a shield equipped
if S.ohSpeed <= 0 then return end if S.ohSpeed <= 0 then return end
pfUI.swingtimer.ohGraceAt = nil -- cancel any pending hide pfUI.swingtimer.ohGraceAt = nil -- cancel any pending hide
S.ohTimerMax = S.ohSpeed S.ohTimerMax = math.max(S.ohSpeed, remaining)
S.ohTimer = S.ohSpeed S.ohTimer = remaining
S.ohActive = true S.ohActive = true
if sw_showoh then pfUI.swingtimer.offhand:Show() end if sw_showoh then pfUI.swingtimer.offhand:Show() end
pfUI.swingtimer:Show() pfUI.swingtimer:Show()
end end
-- Reset ranged countdown. replaceMH=true (Hunter Auto Shot, Throw) stops local function StartRanged(remaining)
-- the melee swing clock while ranged ticks; replaceMH=false (wand Shoot)
-- leaves it running for melee weaving.
local function ResetRanged(replaceMH)
if not sw_showranged then return end if not sw_showranged then return end
UpdateWeaponSpeeds() UpdateWeaponSpeeds()
if S.raSpeed <= 0 then return end if S.raSpeed <= 0 then return end
if replaceMH then S.raTimerMax = math.max(S.raSpeed, remaining)
S.mhActive = false S.raTimer = remaining
pfUI.swingtimer.mainhand:Hide()
end
S.raTimerMax = S.raSpeed
S.raTimer = S.raSpeed
S.raActive = true S.raActive = true
if isHunter then if isHunter then
@@ -486,23 +465,17 @@ pfUI:RegisterModule("swingtimer", function ()
local anyActive = false local anyActive = false
-- Tick timers down. When a timer expires we give a short grace period before -- Tick timers down. When a timer expires we give a short grace period before
-- hiding the bar, to bridge the 1-2 frame gap until AUTO_ATTACK_SELF arrives. -- hiding the bar, to bridge the 1-2 frame gap until PLAYER_SWING arrives.
-- If AUTO_ATTACK_SELF arrives during the grace period it resets the timer -- If PLAYER_SWING arrives during the grace period it resets the timer
-- normally and the grace timer is cleared. If not (e.g. auto-attack was -- normally and the grace timer is cleared. If not (e.g. auto-attack was
-- turned off), the bar hides after the grace period ends. -- turned off), the bar hides after the grace period ends.
local GRACE = 0.15 -- seconds to wait after timer hits 0 before hiding local GRACE = 0.15 -- seconds to wait after timer hits 0 before hiding
if S.mhActive then if S.mhActive then
-- Don't tick while frozen (swing-delay spell like Slam is being cast) S.mhTimer = S.mhTimer - delta
if not S.mhFrozenAt then
S.mhTimer = S.mhTimer - delta
end
if S.mhTimer <= 0 then if S.mhTimer <= 0 then
S.mhTimer = 0 S.mhTimer = 0
if S.mhFrozenAt then if not pfUI.swingtimer.mhGraceAt then
-- Swing-delay spell cast in progress: bar holds at 0, skip grace/hide.
-- SPELL_GO handler will unfreeze and add cast duration to timer.
elseif not pfUI.swingtimer.mhGraceAt then
pfUI.swingtimer.mhGraceAt = GetTime() + GRACE pfUI.swingtimer.mhGraceAt = GetTime() + GRACE
elseif GetTime() >= pfUI.swingtimer.mhGraceAt then elseif GetTime() >= pfUI.swingtimer.mhGraceAt then
pfUI.swingtimer.mhGraceAt = nil pfUI.swingtimer.mhGraceAt = nil
@@ -722,80 +695,13 @@ pfUI:RegisterModule("swingtimer", function ()
end end
end) end)
-- SPELL_START_SELF: fires only for cast-time spells, never instants -- SPELL_GO hook via libdebuff: an on-next-swing ability (HS / Cleave /
local spellStartFrame = CreateFrame("Frame") -- Maul) resolving consumes the queue, so drop the queued color.
spellStartFrame:RegisterEvent("SPELL_START_SELF")
spellStartFrame:SetScript("OnEvent", function()
if not (arg1 and arg1 > 0) then return end
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. 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
if not C_Spell.ResetsMeleeSwing(arg1) then
S.mhFrozenAt = GetTime()
end
end
end)
-- Cast cancel/fail/interrupt: unfreeze swing timer if a delay spell was interrupted
local spellFailFrame = CreateFrame("Frame")
spellFailFrame:RegisterEvent("SPELLCAST_FAILED")
spellFailFrame:RegisterEvent("SPELLCAST_INTERRUPTED")
spellFailFrame:SetScript("OnEvent", function()
if S.mhFrozenAt then
S.mhFrozenAt = nil
end
S.pendingCastSpellId = nil
end)
-- SPELL_GO hook via libdebuff
pfUI.libdebuff_spell_go_hooks = pfUI.libdebuff_spell_go_hooks or {} pfUI.libdebuff_spell_go_hooks = pfUI.libdebuff_spell_go_hooks or {}
pfUI.libdebuff_spell_go_hooks["swingtimer"] = function(spellId) pfUI.libdebuff_spell_go_hooks["swingtimer"] = function(spellId)
-- C_Spell.IsRangedAutoAttackSpell catches both Auto Shot (75) and if IsOnSwingSpell(spellId) then
-- wand Shoot (5019) via Spell.dbc's AUTO_REPEAT attribute (covers
-- any future auto-repeat ranged spell automatically). Wand is the
-- one independent of the MH swing — everything else replaces it.
-- Throw isn't auto-repeat (single-shot) so it's handled explicitly.
if C_Spell.IsRangedAutoAttackSpell(spellId) then
ResetRanged(spellId ~= WAND_SHOOT_SPELLID)
return
elseif spellId == THROW_SPELLID then
ResetRanged(true)
return
elseif IsOnSwingSpell(spellId) then
-- On-next-swing ability (HS / Cleave / Maul / Raptor Strike / etc.)
-- — the swing fires as the spell consumes it. Drop the queued color.
S.hsQueued = false; S.cleaveQueued = false; S.maulQueued = false S.hsQueued = false; S.cleaveQueued = false; S.maulQueued = false
ResetMH()
else
-- 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
S.mhTimer = S.mhSpeed
end
if S.ohActive and S.ohSpeed > 0 then
S.ohTimerMax = S.ohSpeed
S.ohTimer = S.ohSpeed
end
elseif S.mhFrozenAt then
local castDuration = GetTime() - S.mhFrozenAt
S.mhTimer = S.mhTimer + castDuration
S.mhTimerMax = S.mhTimerMax + castDuration
S.mhFrozenAt = nil
end
end end
S.pendingCastSpellId = nil
end end
-- SPELL_CAST_EVENT hook: HS/Cleave/Maul queue tracking -- SPELL_CAST_EVENT hook: HS/Cleave/Maul queue tracking
@@ -809,77 +715,28 @@ pfUI:RegisterModule("swingtimer", function ()
local events = CreateFrame("Frame") local events = CreateFrame("Frame")
events:RegisterEvent("AUTO_ATTACK_SELF") events:RegisterEvent("PLAYER_SWING")
events:RegisterEvent("AUTO_ATTACK_OTHER")
events:RegisterEvent("PLAYER_ENTERING_WORLD") events:RegisterEvent("PLAYER_ENTERING_WORLD")
events:RegisterEvent("PLAYER_EQUIPMENT_CHANGED") -- ClassicAPI: per-slot, equipment-only events:RegisterEvent("PLAYER_EQUIPMENT_CHANGED") -- ClassicAPI: per-slot, equipment-only
events:RegisterEvent("PLAYER_REGEN_DISABLED") events:RegisterEvent("PLAYER_REGEN_DISABLED")
events:RegisterEvent("PLAYER_REGEN_ENABLED") events:RegisterEvent("PLAYER_REGEN_ENABLED")
events:RegisterEvent("ACTIONBAR_SLOT_CHANGED") events:RegisterEvent("ACTIONBAR_SLOT_CHANGED")
events:RegisterEvent("UNIT_DIED") events:RegisterUnitEvent("UNIT_DIED", UnitGUID("player"))
events:RegisterEvent("SPELL_QUEUE_EVENT") events:RegisterEvent("SPELL_QUEUE_EVENT")
events:RegisterEvent("START_AUTOATTACK") events:RegisterEvent("START_AUTOATTACK")
events:RegisterEvent("STOP_AUTOATTACK") events:RegisterEvent("STOP_AUTOATTACK")
events:SetScript("OnEvent", function() events:SetScript("OnEvent", function()
if event == "AUTO_ATTACK_SELF" then if event == "PLAYER_SWING" then
local hitInfo = arg4 or 0 -- ClassicAPI tracks the server's swing clock (white hits, on-next-swing
local isOffhand = bit.band(hitInfo, HITINFO_LEFTSWING) ~= 0 -- abilities, swing-reset casts, parry haste, extra attacks, weapon
local noAction = bit.band(hitInfo, HITINFO_NOACTION) ~= 0 -- swaps) and hands over the seconds left until the next swing.
-- NOACTION means server did not advance swing clock (dodge/parry/miss). if arg2 == Enum.PlayerSwingType.MainHand then
-- Only skip if the timer is already running - if it's not active yet StartMH(arg1)
-- (first swing ever), we still want to start it so the bar appears. elseif arg2 == Enum.PlayerSwingType.OffHand then
if noAction then StartOH(arg1)
if isOffhand and S.ohActive then return end elseif arg2 == Enum.PlayerSwingType.Ranged then
if not isOffhand and S.mhActive then return end StartRanged(arg1)
end
-- Extra attack detection: if timer still has >20% remaining for that hand,
-- the server did NOT reset the swing clock -> this is an extra attack, skip.
-- Use 20% here (SP_SwingTimer's ShouldResetTimer threshold).
-- Exception: if timer is already at 0 (expired), always accept.
if isOffhand then
local pct = S.ohActive and (S.ohTimer / S.ohTimerMax) or 0
if S.ohActive and S.ohTimer > 0 and pct > 0.20 then
return
end
ResetOH()
else
local pct = S.mhActive and (S.mhTimer / S.mhTimerMax) or 0
if S.mhActive and S.mhTimer > 0 and pct > 0.20 then
return
end
ResetMH()
end
elseif event == "AUTO_ATTACK_OTHER" then
-- Parry haste: enemy attacked the player and player parried
local targetGuid = arg2
if not targetGuid or not IsPlayerGuid(targetGuid) then return end
local victimState = arg5 or 0
-- VICTIMSTATE_PARRY = 3
-- Vanilla: parry reduces the NEXT swing timer by 40% of weapon speed,
-- minimum 20% of weapon speed remaining (SP_SwingTimer approach)
if victimState == 3 then
-- Apply to whichever swing comes next (smallest % remaining = closest to firing)
if S.ohActive and S.ohSpeed > 0 and (S.ohTimer / S.ohTimerMax) < (S.mhTimer / S.mhTimerMax) then
local minimum = S.ohSpeed * 0.20
if S.ohTimer > minimum then
local reduct = S.ohSpeed * 0.40
local before = S.ohTimer
S.ohTimer = S.ohTimer - reduct
if S.ohTimer < minimum then S.ohTimer = minimum end
end
elseif S.mhActive and S.mhSpeed > 0 then
local minimum = S.mhSpeed * 0.20
if S.mhTimer > minimum then
local reduct = S.mhSpeed * 0.40
local before = S.mhTimer
S.mhTimer = S.mhTimer - reduct
if S.mhTimer < minimum then S.mhTimer = minimum end
end
else
end
end end
elseif event == "SPELL_QUEUE_EVENT" then elseif event == "SPELL_QUEUE_EVENT" then
+11 -7
View File
@@ -189,6 +189,7 @@ pfUI:RegisterModule("tooltip", function ()
-- Icons come from a ClassicAPI object pool: ReleaseAll hides the whole -- Icons come from a ClassicAPI object pool: ReleaseAll hides the whole
-- set each refresh, then we Acquire and re-anchor left-to-right. -- set each refresh, then we Acquire and re-anchor left-to-right.
local BUFF_SIZE, BUFF_SPACING, BUFF_MAX = 20, 2, 32 local BUFF_SIZE, BUFF_SPACING, BUFF_MAX = 20, 2, 32
local auraSlots = {} -- reusable GetAuraSlots buffer for UpdateBuffs
pfUI.tooltip.buffs = CreateFrame("Frame", "pfTooltipBuffs", GameTooltip) pfUI.tooltip.buffs = CreateFrame("Frame", "pfTooltipBuffs", GameTooltip)
pfUI.tooltip.buffs:SetPoint("BOTTOMLEFT", GameTooltipStatusBar, "TOPLEFT", 0, default_border + 2) pfUI.tooltip.buffs:SetPoint("BOTTOMLEFT", GameTooltipStatusBar, "TOPLEFT", 0, default_border + 2)
pfUI.tooltip.buffs:SetHeight(BUFF_SIZE) pfUI.tooltip.buffs:SetHeight(BUFF_SIZE)
@@ -239,17 +240,20 @@ pfUI:RegisterModule("tooltip", function ()
end end
local prev, count = nil, 0 local prev, count = nil, 0
for i = 1, BUFF_MAX do -- one GetAuraSlots enumeration + positional by-slot reads: no per-aura
local aura = C_UnitAuras.GetBuffDataByIndex(unit, i) -- table and no per-index re-walk of the aura array
if not aura then break end local n = ScanAuraSlots(unit, "HELPFUL", auraSlots, BUFF_MAX)
for i = 1, n do
local name, texture, applications, _, _, expirationTime = C_UnitAuras.UnitAuraBySlot(unit, auraSlots[i])
if not name then break end
count = count + 1 count = count + 1
local icon = pfUI.tooltip.buffpool:Acquire() local icon = pfUI.tooltip.buffpool:Acquire()
icon.texture:SetTexture(aura.icon) icon.texture:SetTexture(texture)
icon.stacks:SetText(aura.applications and aura.applications > 1 and aura.applications or "") icon.stacks:SetText(applications and applications > 1 and applications or "")
icon.expirationTime = aura.expirationTime icon.expirationTime = expirationTime
local timeleft = aura.expirationTime and aura.expirationTime > 0 and (aura.expirationTime - GetTime()) or 0 local timeleft = expirationTime and expirationTime > 0 and (expirationTime - GetTime()) or 0
icon.timer:SetText(timeleft > 0 and GetColoredTimeString(timeleft) or "") icon.timer:SetText(timeleft > 0 and GetColoredTimeString(timeleft) or "")
if prev then if prev then
+1 -1
View File
@@ -113,7 +113,7 @@ pfUI:RegisterModule("totems", function ()
self.bar[i].cdbg = self.bar[i].cdbg or CreateFrame("Frame", nil, self.bar[i]) self.bar[i].cdbg = self.bar[i].cdbg or CreateFrame("Frame", nil, self.bar[i])
self.bar[i].cdbg:SetSize(self.iconsize - 3, self.iconsize - 3) self.bar[i].cdbg:SetSize(self.iconsize - 3, self.iconsize - 3)
self.bar[i].cdbg:SetPoint("CENTER", self.bar[i], "CENTER", 0, 0) self.bar[i].cdbg:SetPoint("CENTER", self.bar[i], "CENTER", 0, 0)
self.bar[i].cd = self.bar[i].cd or CreateFrame(COOLDOWN_FRAME_TYPE, "pfTotemsBar"..i.."Cooldown", self.bar[i].cdbg, "CooldownFrameTemplate") self.bar[i].cd = self.bar[i].cd or CreateFrame("Model", "pfTotemsBar"..i.."Cooldown", self.bar[i].cdbg, "CooldownFrameTemplate")
self.bar[i].cd.pfCooldownStyleAnimation = 1 self.bar[i].cd.pfCooldownStyleAnimation = 1
self.bar[i].cd.pfCooldownType = "ALL" self.bar[i].cd.pfCooldownType = "ALL"
+2 -2
View File
@@ -1,7 +1,7 @@
pfUI:RegisterModule("tracking", function () pfUI:RegisterModule("tracking", function ()
MINIMAP_TRACKING_FRAME:UnregisterAllEvents() _G.MiniMapTrackingFrame:UnregisterAllEvents()
MINIMAP_TRACKING_FRAME:Hide() _G.MiniMapTrackingFrame:Hide()
local rawborder, border = GetBorderSize() local rawborder, border = GetBorderSize()
local size = tonumber(C.appearance.minimap.tracking_size) local size = tonumber(C.appearance.minimap.tracking_size)
+1 -2958
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -363,9 +363,9 @@ end
b:EnableMouse(true) b:EnableMouse(true)
b:RegisterEvent("FACTION_STANDING_CHANGED") b:RegisterEvent("FACTION_STANDING_CHANGED")
b:RegisterEvent("UNIT_PET") b:RegisterUnitEvent("UNIT_PET", "player")
b:RegisterEvent("UNIT_LEVEL") b:RegisterUnitEvent("UNIT_LEVEL", "player")
b:RegisterEvent("UNIT_PET_EXPERIENCE") b:RegisterUnitEvent("UNIT_PET_EXPERIENCE", "player", "pet")
b:RegisterEvent("PLAYER_ENTERING_WORLD") b:RegisterEvent("PLAYER_ENTERING_WORLD")
b:RegisterEvent("UPDATE_EXHAUSTION") b:RegisterEvent("UPDATE_EXHAUSTION")
b:RegisterEvent("PLAYER_XP_UPDATE") b:RegisterEvent("PLAYER_XP_UPDATE")
+30 -94
View File
@@ -3,6 +3,8 @@ function SlashCmdList.RELOAD(msg, editbox)
ReloadUI() ReloadUI()
end end
local addonName = ...
SLASH_PFUI1 = '/pfui' SLASH_PFUI1 = '/pfui'
function SlashCmdList.PFUI(msg, editbox) function SlashCmdList.PFUI(msg, editbox)
pfUI.gui:SetShown(not pfUI.gui:IsShown()) pfUI.gui:SetShown(not pfUI.gui:IsShown())
@@ -13,79 +15,12 @@ function SlashCmdList.GM(msg, editbox)
ToggleHelpFrame(1) ToggleHelpFrame(1)
end end
pfUI = CreateFrame("Frame", nil, UIParent) local pfUI = CreateFrame("Frame", addonName, UIParent)
pfUI:RegisterEvent("ADDON_LOADED") pfUI:RegisterEvent("ADDON_LOADED")
-- setup bootvar -- setup bootvar
pfUI.bootup = true pfUI.bootup = true
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 = 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"
local function FormatVersion(packed)
local x = math.floor(packed / 10000)
local y = math.floor(math.mod(packed, 10000) / 100)
local z = math.mod(packed, 100)
return string.format("v%d.%d.%d", x, y, z)
end
if not CLASSIC_API_VERSION or CLASSIC_API_VERSION < PFUI_CLASSIC_API_MIN then
local minVersion = FormatVersion(PFUI_CLASSIC_API_MIN)
pfUI.disabled = true
local detail
if not CLASSIC_API_VERSION then
detail = "The ClassicAPI DLL isn't loaded. The |cff33ffcc!!!ClassicAPI|r addon ships bundled with it -- delete your |cff33ffcc!!!ClassicAPI|r folder and install the latest release from:"
else
detail = "ClassicAPI " .. minVersion .. " or newer is required. Delete your |cff33ffcc!!!ClassicAPI|r folder and reinstall the latest release from:"
end
local function ShowRequiredPopup()
StaticPopupDialogs["PFUI_CLASSICAPI_REQUIRED"] = {
text = "|cff33ffccpf|cffffffffUI|r has been disabled.\n\n" .. detail,
button1 = OKAY,
hasEditBox = 1,
editBoxWidth = 280,
timeout = 0,
whileDead = 1,
hideOnEscape = 1,
preferredIndex = 3,
OnShow = function()
local editBox = getglobal(this:GetName().."EditBox")
if editBox then
editBox:SetText(PFUI_CLASSIC_API_LATEST_URL)
editBox:HighlightText()
editBox:SetFocus()
end
end,
}
StaticPopup_Show("PFUI_CLASSICAPI_REQUIRED")
DEFAULT_CHAT_FRAME:AddMessage(
"|cff33ffccpf|cffffffffUI|r disabled: " .. detail .. " " .. PFUI_CLASSIC_API_LATEST_URL,
1, 0.3, 0.3
)
end
local loginFrame = CreateFrame("Frame")
loginFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
loginFrame:SetScript("OnEvent", function()
loginFrame:UnregisterEvent("PLAYER_ENTERING_WORLD")
ShowRequiredPopup()
end)
elseif CLASSIC_API_VERSION < PFUI_CLASSIC_API_LATEST then
EventUtil.ContinueOnPlayerLogin(function()
C_Timer.After(8, function()
DEFAULT_CHAT_FRAME:AddMessage(
"|cff33ffccpf|rUI: ClassicAPI " .. FormatVersion(PFUI_CLASSIC_API_LATEST) .. " is available — " .. PFUI_CLASSIC_API_LATEST_URL,
1, 0.85, 0.3
)
end)
end)
end
end
pfUI_playerDB = pfUI_playerDB or {} pfUI_playerDB = pfUI_playerDB or {}
pfUI_config = pfUI_config or {} pfUI_config = pfUI_config or {}
pfUI_init = pfUI_init or {} pfUI_init = pfUI_init or {}
@@ -117,35 +52,24 @@ pfUI.env = {}
-- the flag; one that lacks it should drop the flag and get the safe fallback. -- the flag; one that lacks it should drop the flag and get the safe fallback.
pfUI.handlesHookScript = true pfUI.handlesHookScript = true
if not pfUI.disabled then pfUI.events = Mixin({}, CallbackRegistryMixin)
pfUI.events = Mixin({}, CallbackRegistryMixin) pfUI.events:OnLoad()
pfUI.events:OnLoad() pfUI.events:SetUndefinedEventsAllowed(true)
pfUI.events:SetUndefinedEventsAllowed(true)
end
-- check if macro addons are loaded (disables macrotweak/macroscan) -- check if macro addons are loaded (disables macrotweak/macroscan)
function pfUI:MacroAddonsLoaded() function pfUI:MacroAddonsLoaded()
return IsAddOnLoaded("Supermacro") or IsAddOnLoaded("SuperCleveRoidMacros") or IsAddOnLoaded("UltimaMacros") return IsAddOnLoaded("Supermacro") or IsAddOnLoaded("SuperCleveRoidMacros") or IsAddOnLoaded("UltimaMacros")
end end
-- detect current addon path pfUI.name = addonName
local tocs = { "", "-master", "-tbc", "-wotlk" } pfUI.path = "Interface\\AddOns\\" .. addonName
for _, name in pairs(tocs) do
local current = string.format("pfUI%s", name)
local title = C_AddOns.GetAddOnName(current)
if title then
pfUI.name = current
pfUI.path = "Interface\\AddOns\\" .. current
break
end
end
-- handle/convert media dir paths -- handle/convert media dir paths
pfUI.media = setmetatable({}, { __index = function(tab,key) pfUI.media = setmetatable({}, { __index = function(tab,key)
local value = tostring(key) local value = tostring(key)
if strfind(value, "img:") then if value:find("img:") then
value = string.gsub(value, "img:", pfUI.path .. "\\img\\") value = string.gsub(value, "img:", pfUI.path .. "\\img\\")
elseif strfind(value, "font:") then elseif value:find("font:") then
value = string.gsub(value, "font:", pfUI.path .. "\\fonts\\") value = string.gsub(value, "font:", pfUI.path .. "\\fonts\\")
else else
value = string.gsub(value, "Interface\\AddOns\\pfUI\\", pfUI.path .. "\\") value = string.gsub(value, "Interface\\AddOns\\pfUI\\", pfUI.path .. "\\")
@@ -154,9 +78,7 @@ pfUI.media = setmetatable({}, { __index = function(tab,key)
return value return value
end}) end})
-- cache client version pfUI.client = INTERFACE_VERSION
local _, _, _, client = GetBuildInfo()
pfUI.client = client or 11200
-- setup pfUI namespace -- setup pfUI namespace
setmetatable(pfUI.env, {__index = getfenv(0)}) setmetatable(pfUI.env, {__index = getfenv(0)})
@@ -390,14 +312,13 @@ function pfUI:CheckNewModules()
end end
local function BackwardsCompatRegister(func, arg3) local function BackwardsCompatRegister(func, arg3)
if arg3 and type(func) == "string" and type(arg3) == "function" and string.find(func, "vanilla") then if arg3 and type(func) == "string" and type(arg3) == "function" and func:find("vanilla") then
return arg3 return arg3
end end
return func return func
end end
function pfUI:RegisterModule(name, func, arg3) function pfUI:RegisterModule(name, func, arg3)
if pfUI.disabled then return end
if pfUI.module[name] then return end if pfUI.module[name] then return end
func = BackwardsCompatRegister(func, arg3) func = BackwardsCompatRegister(func, arg3)
pfUI.module[name] = func pfUI.module[name] = func
@@ -408,7 +329,6 @@ function pfUI:RegisterModule(name, func, arg3)
end end
function pfUI:RegisterSkin(name, func, arg3) function pfUI:RegisterSkin(name, func, arg3)
if pfUI.disabled then return end
if pfUI.skin[name] then return end if pfUI.skin[name] then return end
func = BackwardsCompatRegister(func, arg3) func = BackwardsCompatRegister(func, arg3)
pfUI.skin[name] = func pfUI.skin[name] = func
@@ -429,7 +349,6 @@ function pfUI:LoadSkin(s)
end end
pfUI:SetScript("OnEvent", function() pfUI:SetScript("OnEvent", function()
if pfUI.disabled then return end
-- make sure to initialize and set our fonts -- make sure to initialize and set our fonts
-- each time an addon got loaded but only -- each time an addon got loaded but only
@@ -443,7 +362,7 @@ pfUI:SetScript("OnEvent", function()
-- "@project-version@" until release tooling substitutes it — mark those -- "@project-version@" until release tooling substitutes it — mark those
-- explicitly as "dev" instead of pretending they're a real numbered build. -- explicitly as "dev" instead of pretending they're a real numbered build.
local raw = tostring(GetAddOnMetadata(pfUI.name, "Version")) local raw = tostring(GetAddOnMetadata(pfUI.name, "Version"))
if strfind(raw, "@") then if raw:find("@") then
pfUI.version.major, pfUI.version.minor, pfUI.version.fix = 0, 0, 0 pfUI.version.major, pfUI.version.minor, pfUI.version.fix = 0, 0, 0
pfUI.version.string = "dev" pfUI.version.string = "dev"
else else
@@ -577,3 +496,20 @@ function pfUI.SetupCVars()
end end
UIParentLoadAddOn("Blizzard_CombatText") UIParentLoadAddOn("Blizzard_CombatText")
end end
do -- RunMacroText
local obj = setmetatable({ ["GetText"] = function(self) return self.text end }, {
__index = function(tab,key)
local value = function() return end
rawset(tab,key,value)
return value
end
})
function RunMacroText(text)
obj.text = text
ChatEdit_ParseText(obj, 1)
end
end
_G.PLAYER_BUFF_START_ID = -1
+5 -7
View File
@@ -8,11 +8,9 @@
## SavedVariablesPerCharacter: pfUI_config, pfUI_init, pfUI_playerDB ## SavedVariablesPerCharacter: pfUI_config, pfUI_init, pfUI_playerDB
## X-Website: https://github.com/brues-code/pfUI ## X-Website: https://github.com/brues-code/pfUI
pfUI.lua # ClassicAPI-less fallback. ClassicAPI redirects this read to pfUI_Turtle.toc
# (Turtle clients) or pfUI_ClassicAPI.toc (everything else), so the client only
# ever reaches THIS file when the DLL is missing. Load the notice and nothing
# else.
init\env.xml API_Check.lua
init\compat.xml
init\api.xml
init\libs.xml
init\skins.xml
init\modules.xml
+23
View File
@@ -0,0 +1,23 @@
## Interface: 11200
## Title: |cff33ffccpf|cffffffffUI
## Author: Shagu - modified by me0wg4ming & brues
## Notes: A complete user interface replacement.
## Notes-ruRU: Полная замена пользовательского интерфейса.
## Version: @project-version@
## SavedVariables: pfUI_profiles, pfUI_addon_profiles, pfUI_cache, pfUI_throttle
## SavedVariablesPerCharacter: pfUI_config, pfUI_init, pfUI_playerDB
## X-Website: https://github.com/brues-code/pfUI
# Selected by ClassicAPI on every non-Turtle client. Turtle clients get
# pfUI_Turtle.toc instead, which trades init\stock.xml for init\turtle.xml.
API_Check.lua
pfUI.lua
init\env.xml
init\stock.xml
init\api.xml
init\libs.xml
init\skins.xml
init\modules.xml
+24
View File
@@ -0,0 +1,24 @@
## Interface: 11200
## Title: |cff33ffccpf|cffffffffUI
## Author: Shagu - modified by me0wg4ming & brues
## Notes: A complete user interface replacement.
## Notes-ruRU: Полная замена пользовательского интерфейса.
## Version: @project-version@
## SavedVariables: pfUI_profiles, pfUI_addon_profiles, pfUI_cache, pfUI_throttle
## SavedVariablesPerCharacter: pfUI_config, pfUI_init, pfUI_playerDB
## X-Website: https://github.com/brues-code/pfUI
# Selected by ClassicAPI on Turtle WoW, where it wins over pfUI_ClassicAPI.toc.
# Same list with init\stock.xml swapped for init\turtle.xml: the modules, skins
# and vendor prices that only mean anything on a Turtle client.
API_Check.lua
pfUI.lua
init\env.xml
init\api.xml
init\libs.xml
init\skins.xml
init\modules.xml
init\turtle.xml