110 Commits

Author SHA1 Message Date
Brues cc8b8b476b Removed macrotweak 2026-09-09 23:05:10 -05:00
Brues bd2b6c3105 no need to load macrotweak if ClassicAPI 1.15 is found 2026-09-09 21:48:13 -05:00
Brues 5bede33f00 energytick: sweep the real regen clock, and stop it stuttering
The mana spark modelled a tick clock that does not exist. Spending mana
restarted the sweep with a five-second span, then it cycled at two seconds
until the next spend -- so every cast reset the phase, and the spark drew a
period the server never runs. There is one regen timer for every power,
REGEN_TIME_FULL (2s), re-armed with `+=` in Player::RegenerateAll and never
touched by casting. The five-second rule is a separate countdown that
SetLastManaUse arms on any mana-costing cast; it changes what a tick pays,
never when ticks land. Inside the window the tick still fires and still pays
mp5 plus whatever share of spirit the player keeps.

That share is common and not computable here. The amounts are readable now
(C_Spell.GetSpellEffectInfo), and the talent, racial and buff sources could be
enumerated -- but the Casting Regen item ladder is equip auras that never
appear in the buff list, and m_modManaRegenInterrupt is never sent. A floor
that is wrong whenever the player wears such an item is worse than showing
what happens: the spark starts an FSR window dim and goes solid on the first
tick that lands inside it.

The sweep is a free-running phase lock on that one clock. Every positive mana
delta used to re-anchor it, and most are not the tick -- Illumination refunds,
Judgement of Wisdom, potions, a Mana Spring totem on its own 2s clock at
another phase -- so the spark snapped back to the edge mid-sweep several times
a cycle. A gain re-anchors only if it lands within 250ms of the predicted
boundary. Two rejected gains exactly one period apart are the real clock and
move the lock, which is how a wrong first lock heals. Rollover advances by
whole periods from the anchor instead of restarting from now, so frame timing
no longer accumulates into drift the lock then has to chase.

Even a correct tick used to hitch the spark: re-anchoring to arrival time
moved it by the jitter, 20-80ms, right at the wrap. Inside an 80ms band the
tick now confirms the sweep and leaves the anchor alone; only the excess is
pulled in. With no self-drift left there is nothing systematic in that band.

The rule is armed from UNIT_SPELLCAST_SUCCEEDED filtered through
C_Spell.GetSpellPowerCost, mirroring Spell::TakePower's condition, rather than
from a mana decrease -- Mana Burn lowers mana without arming the rule.
UNIT_SPELLCAST_CHANNEL_STOP re-arms it, since Unit::Update will not expire it
while the spending spell is still channeling.

The energy period no longer names a spell. Player::RegenerateAll sums
SPELL_AURA_MOD_ENERGY_REGEN_TIME and takes amount * agility / 10 off the timer
in milliseconds; everything in that sum is readable here. The player's own
spells carrying aura 217 come from C_SpellBook.GetPlayerSpellsByAura, passives
count while known (current rank only), anything else while it is up in the
buff list, and each amount is basePoints + baseDice from the effect data. So
Blade Rush is found without GetTalentInfo(2, 16) -- an ordinal into the talent
grid that does not fail when the tree changes but reads another talent's rank
(index 16 is Vigor one tab over) -- and a retune or a second source needs no
edit. The sum is cached and dropped on SPELLS_CHANGED and
PLAYER_AURAS_CHANGED, the two events its two halves move on, so a tick costs
no spellbook walk; agility stays live, it is one call.

The CHAT_MSG_SPELL_SELF_BUFF "You gain ... Energy from" filter is gone. The
phase lock rejects Relentless Strikes and Thistle Tea the same way it rejects
a paladin's refunds, it was English-only, and its ignore-the-next-gain logic
could eat a real tick if events reordered.
2026-09-09 13:24:17 -05:00
Brues a286843b18 nameplates: gate the per-plate update before classifying it
The central loop calls the per-plate update for every visible plate ~100 times
a second, and the plate's own throttle sat at the bottom of the function. Above
it ran the whole classification: a GetAlpha call, a castbar IsShown, a cast
lookup, and one or two libthrottle:Get resolutions -- all to work out which
throttle applied, on plates throttled to 10fps that were going to return
anyway. Forty plates makes that four thousand times a second.

Add a cheap gate first, against the floor across all four throttle categories.
Nothing that would have updated can be turned away by it -- a plate past its
real throttle is necessarily past the minimum -- and the category-specific gate
still runs after the classification, unchanged. Event flags are read before
both and bypass both, as they did before.

libthrottle:Get is not a lookup: it walks the saved variables, a defaults
fallback and a preset table, and can build a "<category>_custom" key. Resolve
the four categories in CacheConfig instead, where config changes already land,
and read them from cfg on the hot path. That covers the two calls in the
castbar branch as well.

The castbar hide moves below the cheap gate, which is safe rather than merely
tolerable: a plate showing a castbar sets isCastingNonTarget, which selects the
castbar throttle -- one of the four the floor is derived from -- so it always
clears the first gate and reaches the hide at its normal rate.
2026-09-09 13:24:17 -05:00
Brues 39840c10fd unitframes: let UpdateVisibility own the event subscriptions
Every unit frame registered fifteen unit events globally and then sorted them
out per event:

  arg1 == this.label .. this.id or (UnitGUID and arg1 == UnitGUID(this.label .. this.id))

A string concat -- and on a miss a second concat plus a UnitGUID call -- for
every frame, for every unit event fired by anything in the world. Forty-five
frames in a raid, on every health tick of every raider, to conclude "not mine".

UpdateVisibility is the single place a frame's label and id are ever assigned,
and it already pushed the result to SetAttribute("unit", ...). It now caches
the unitstr and points the subscriptions at it, so there is one hook rather
than a set of transitions to catch, and visibilityscan re-running it five
times a second makes it self-healing. A frame that is not in use drops its
unit events entirely.

EnableEvents no longer registers them: a registration keeps its kind, so
plain-registering first would make every later RegisterUnitEvent a no-op.

UNIT_PET and UNIT_HAPPINESS stay plain deliberately. 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.

The 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 clause was only ever a duplicate wake. It dates from b7a0912e, before
ClassicAPI's TokenObserver made synthetic tokens first-class event sources.

Two teardown paths needed help. UnregisterAllEvents drops the filters with the
registrations, so the cached unit has to be cleared or the next
UpdateVisibility believes they are still in place. And PLAYER_LOGOUT silences
the frame while visibilityscan keeps ticking, which would re-register
everything it just dropped -- the crash 132 that branch exists to prevent -- so
the frame now takes itself off the scan list.
2026-09-09 13:24:17 -05:00
Brues ab98bb7ab5 nameplates: let each plate watch its own unit
The central handler registered UNIT_AURA, UNIT_FLAGS and the four
UNIT_SPELLCAST_* globally, so every unit event in the world woke it for a
"^nameplate" prefix test, and each of the four branches that survived the test
then resolved the plate back out of arg1 -- UnitGUID + plateByGuid, or
GetNamePlateForUnit.

Register them per plate instead, on the plate's own frame against its own
token. The token is the subscription, so the event only reaches the plate it
concerns and `this` is already that plate: the aura branch is now one
assignment where it used to be a prefix test, an API call and a table lookup.

This is also what makes the approach viable at all. A central listener would
have had to name every token it might care about, and nameplate slots have no
cap -- Events.cpp keeps them in a vector that grows by push_back, with only a
"<80 even in AV-scale scenes" comment for sizing. Any nameplate1..N list would
have been a guess that fails silently in exactly the crowded scenes where
plates matter.

Lifecycle: NAME_PLATE_UNIT_ADDED points the registrations at the new token,
replacing the previous unit rather than stacking, since RegisterUnitEvent over
an already-filtered registration swaps the units. NAME_PLATE_UNIT_REMOVED
unregisters, which is required -- freed slots are reused, and a stale token
would feed the frame another unit's events.

PLAYER_LOGOUT now tears down the plates too. It silenced only the central
frame before; leaving forty plate frames dispatching through logout is the
crash 132 that branch exists to prevent.
2026-09-09 13:24:17 -05:00
Brues 73f4999004 marktracking: filter to the mark tokens and idle when solo
UNIT_HEALTH/UNIT_MAXHEALTH register through RegisterUnitEvent for mark1
through mark8, so the scanner stops waking for every unit in the world. Eight
tokens is past the four other clients accept; ClassicAPI takes as many as it
is given, so this file is fork-only.

That makes arg1 always one of eight known strings, so the handler looks the
row index up in a table built alongside markerTokens, instead of running
string.match + tonumber -- which allocated a capture and parsed it on every
health tick of every marked unit.

The fallback poll was the larger cost and the filtering did not touch it: a
full eight-row rebuild once a second for the whole session, whether or not a
marker existed anywhere. Raid markers are a group feature, so the scanner now
watches PARTY_MEMBERS_CHANGED / RAID_ROSTER_UPDATE and creates or cancels the
ticker with group membership -- outside a group no timer is queued at all,
rather than one waking every second to return early. It is 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 precisely the case the poll exists to catch.

The mark-token capability probe at the top goes too. ClassicAPI is a hard
dependency, so the tokens are always there to resolve.
2026-09-09 13:24:17 -05:00
Brues b7c3fe5333 Filter unit events with RegisterUnitEvent
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. Convert the 26 registrations whose unit set
is fixed and known at registration time.

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.
castbar is the case that matters -- the player's own casts only ever fire
arg1=="player", never "target"/"focus", so the target and focus bars must
register "player" too, and their UnitIsUnit test is what still rejects a
"player" event while you are targeting a mob.

Two that are not one-line swaps:

  actionbar's two unit events are keys in tables that also drive dispatch, so
  they cannot leave the tables -- and they cannot be plain-registered first,
  because a registration keeps its kind and RegisterUnitEvent over a plain
  one stays plain. They route through a small event_units map instead.

  swingtimer's UNIT_DIED carries a GUID, not a token. The filter is a plain
  case-insensitive string compare with no GUID resolution, so the player's
  own GUID -- fixed for the session -- filters it exactly.

Guards that are now unreachable stay put: the filter applies only when arg1
is a string, so an event that ever fires with a number or no argument is
delivered as if plainly registered, and the handler's own test is what still
rejects it.

Left alone: api/unitframes.lua (the frame's unit changes at runtime and the
handler also matches on GUID), nameplates' six nameplateN events, libdebuff
and libpredict's UNIT_HEALTH (genuinely any unit), and raid.lua's UNIT_PET
(any raid member can own the pet).
2026-09-09 13:24:17 -05:00
Brues 8e109a585a eqcompare: place the compare after the tooltip settles when Guda is loaded
Guda anchors its item tooltips with ANCHOR_NONE and its own SetPoint, and
the tooltip module then relocates every ANCHOR_NONE tooltip to its
configured spot. eqcompare picked left or right from GetLeft/GetRight
inside the Set* call -- against the position the tooltip was about to
leave -- so for an item in the left half of the screen the shopping
tooltips anchored to the right edge and rode the relocation off-screen
(vatichild/guda#41). Guda's own inventory block also widens the tooltip
after our hook has run.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* CAPI min bumped to 1.13.1

* Gracefully disable pfUI when ClassicAPI is missing

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

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

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

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

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

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

* Show Faction/Race icons in chat

* Load pfUI through ClassicAPI's flavor TOCs

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

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

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

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

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

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

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

* Split the vendor price tables into their own manifests

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

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

* Drop the vanilla compat layer

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

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

NAMEPLATE_FRAMETYPE and PLAYER_BUFF_START_ID had no readers left.

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

* bump CAPI min to 11303

* auras: uncap the self-debuff tooltip lookup

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

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

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

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

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

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

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

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

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

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

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

    bump CAPI min to 1.12.4

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

    tracking: use native ClassicAPI tracking API

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

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

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

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

Populate spell_queue from the cast's own rank-specific spellId (via
C_Spell.GetSpellName/GetSpellSubtext) right before the cache lookup, so
prediction works regardless of how the cast was initiated. Deriving the
rank from the actual spellId also fixes stale-rank lookups when a prior
keybind cast left a different rank of the same spell queued.
2026-08-24 21:37:41 -05:00
brues-code 7ca0e40d8b CAPI needs 1.12.1 for UnitAura changes 2026-08-24 12:05:18 -05:00
brues-code 2f93b246a1 Accidentally bumped ClassicAPI to the wrong version 2026-08-24 11:48:54 -05:00
Brues c647698f8f bump CAPI min 2026-08-24 11:07:38 -05:00
Brues 48026008b6 buffwatch: use official dispel-type colours for debuff bars
The debuff-bar auto colour read the vanilla DebuffTypeColor global,
diverging from unitframes/buff which use C_UnitAuras.GetAuraDispelTypeColor
(the BC GlobalColor.dbc palette ClassicAPI ships). Switch buffwatch to the
same accessor so all aura displays share one palette; nil/unknown types
fall back to DEBUFF_TYPE_NONE instead of the old ad-hoc (1, .2, .2).
2026-08-24 11:07:38 -05:00
Brues c1d96bae58 buff/buffwatch: positional aura reads in hot refresh loops
buff.lua RefreshBuffButton runs 50x per PLAYER_AURAS_CHANGED and
buffwatch.lua GetBuffData runs 32x per RefreshBuffBarFrame; both now use
the zero-allocation positional C_UnitAuras.UnitAura instead of the
table-allocating GetAuraDataByIndex.

buffwatch also threads the aura's dispelType out of GetBuffData onto the
frame.buffs row, so the debuff-bar colour reads it from the row instead
of re-fetching the aura with GetDebuffDataByIndex on every name change.

Cold single-lookups (hover/click tooltips) keep the readable table API.
2026-08-24 11:07:38 -05:00
Brues 1d44c5ec6f unitframes: scan debuff slots once for dispel indicators
The dispellable-indicator block re-scanned all 16 harmful slots for
every dispellable type (O(types x 16) UnitDebuff calls per refresh).
Scan the 16 slots once into a reusable set, then have the per-type loop
check the set. Zero-allocation (the set is cleared and reused within the
single RefreshUnit call).
2026-08-24 11:07:38 -05:00
Brues 8f88dfd852 Use positional C_UnitAuras accessors in hot aura scans
Swap the per-frame aura-refresh loops from table-allocating
GetAuraDataByIndex/GetBuffDataByIndex/GetDebuffDataByIndex/GetUnitAuras
to the zero-allocation positional C_UnitAuras.UnitAura/UnitBuff/UnitDebuff.

unitframes: buff/debuff icon loops, the dispellable-indicator 16-slot
scan, the two GetUnitAuras indicator passes, and the custom-debuff scan.
nameplates: the per-plate debuff scan now writes positional results
straight into the reusable display buffer, dropping the per-aura tables
and the result array.

Filters change from HARMFUL|PLAYER/HARMFUL to PLAYER/nil since UnitBuff/
UnitDebuff lock the range and still honor the PLAYER predicate. Cold
single-lookups (hover/click, tooltip, totem icon, libdebuff) keep the
readable table API.
2026-08-24 11:07:37 -05:00
Brues a57214aeb0 Bump ClassicAPI minimum version 2026-08-24 11:07:37 -05:00
Brues 9bde843e33 utlize inline icons and CreateGoldString in repair module 2026-08-24 11:07:37 -05:00
Brues ad13e6ace3 Utilize SetFormattedText
This change updates several text setters to use `SetFormattedText` instead of passing `string.format(...)` into `SetText`. It also switches a few regex captures from `string.find` to `string.match` for cleaner capture handling, and replaces explicit width/height setters with `SetSize` in the first-run dialog for consistency.
2026-08-24 11:07:37 -05:00
Brues 82c74c2db5 Skip declaring pfSellData on turtle client
This giant table is just going to be immediately replaced so there's no point in declaring it.
2026-08-21 14:59:19 -05:00
roby-brok a3cfd711fe nameplates: read friendly name colours from the class table
The class-colour write sourced r, g, b from the health bar rather than
from PFUI_CLASS_COLORS[class], which the condition tested for but never
read. So "class colours on friendly names" only produced a class colour
when friendclassc - a bar option - happened to be on as well; otherwise
the name took the generic friendly colour. In every case it also
inherited the tapped-grey and barcombatstate overrides meant for the bar,
neither of which belongs on the name.

The name now reads the class table directly.

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

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

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

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

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

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

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

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

Reported by Iden via Discord.

(cherry picked from commit eaa9beac51953d2a959c67d5ff5bc50273a919c3)
(cherry picked from commit 3fdb3b23136b260d111509e24e6d7b0d0b9b1b33)
2026-08-21 13:31:07 -05:00
Brues 9c29f0c7d4 Add handlesHookScript capability flag
Introduce pfUI.handlesHookScript boolean in pfUI.lua to signal that this fork's actionbar buttons correctly support the modern HookScript widget method. This prevents ClassicAPI's AddOnCompat shim from shadowing HookScript during actionbar load. The flag documents behavior (not identity): forks that maintain HookScript-correct actionbar code should keep the flag; forks that do not should clear it to opt into the safe compatibility fallback.
2026-08-21 13:26:09 -05:00
Brues 685ecb4a3e Removed !!!ClassicAPI from toc dependencies
Addon is now baked into the DLL and !!!ClassicAPI will always load before pfUI so this only served to confuse users
2026-08-18 10:35:31 -05:00
Brues 8cdaf1fc00 Prefer GetCoinTextureString in CreateGoldString
If the global GetCoinTextureString exists, use it to format money (wrapped in white color codes) for CreateGoldString. Keeps the existing numeric fallback formatting for environments without GetCoinTextureString.
2026-08-18 04:38:09 -05:00
Brues bb39c9d94e Add localized race info to pfUI environment
Initialize pfUI.env.L["race"] by iterating C_CreatureInfo.GetRaceInfo and C_CreatureInfo.GetFactionInfo. Builds a table keyed by clientFileString containing raceName, raceID and faction (groupTag).
2026-08-18 04:34:34 -05:00
Brues 18059ffbb7 Class-color aura caster name in tooltips 2026-08-18 04:29:33 -05:00
Brues d75e280238 Show party pets in raidpet frames under raidforgroup
When "Use Raid Frames To Display Group Members" is on and in a party,
raid pet frames tried to show raidpet1..40 (nonexistent in a party)
instead of the player pet and partypet1..4.

Remap raidpet grid slots to pet / partypet<N> in UpdateVisibility,
keyed on a stable pfRaidPet<N> slot, mirroring the raid player frames.
LayoutPets now mirror-places the party pet slots, and the raid updater
runs LayoutPets when in a party (not just a raid).

Also exclude raid pet frames (label "partypet", cache_raidpet set) from
the hide_in_raid group-hiding clause so they are not hidden.
2026-08-13 18:03:56 -05:00
Brues 9eb34b8f0a Revert "40-y rangecheck on by default"
This reverts commit af6893c10c.
2026-08-13 17:43:13 -05:00
Brues f8dde49f09 Add IsPlayerGuid API wrapper
Expose the built-in IsPlayerGuid helper through pfUI.api so callers can check whether a GUID or unit token matches the local player. This keeps the pfUI API surface consistent with the underlying WoW API while making the check available through the addon namespace.
2026-08-13 17:32:47 -05:00
Brues 74fbbb6154 Use PFUI_CLASS_COLORS in loothistory 2026-08-12 14:33:05 -05:00
Brues af6893c10c 40-y rangecheck on by default 2026-08-12 14:25:37 -05:00
Brues be6b2ea036 Support binding a click to a named macro
Accept "macro:<name>" in a click-cast action to run a saved macro by
name via the secure `macro` attribute (leading space after the colon is
trimmed). ClassicAPI dispatches it through RunMacro, so a conditional
addon still evaluates the macro body.
2026-08-12 00:33:49 -05:00
roby-brok afdb6c3862 unitframes: stop UnitHasAggro rescanning and reallocating on every call
Two costs on the hot path. It concatenated '<u>target' and '<u>targettarget'
per call per unit, though pfValidUnits never changes after load -- now built
once into a static triple list. And only positive results were cached, so the
common case (nothing has aggro) rescanned the entire unit table on every call;
negative results now cache for 0.3s, short enough that aggro still appears
promptly.
2026-08-11 02:06:28 -05:00
Brues b230a45b92 Show spell and unit IDs on tooltips
Add "Show Spell IDs" and "Show Unit IDs" options alongside the existing
item-ID line. Spell IDs come from GameTooltip:GetSpell() on spell
tooltips and from the aura's spellId on unit-aura tooltips; unit IDs
come from C_CreatureInfo.GetCreatureID on the mouseover unit's GUID
(skipped for players and guarded when the GUID carries no entry).
2026-08-11 00:38:28 -05:00
Brues ef1fa47539 Dock GreedMeter into the chat panel
Register GreedMeter's primary window on the meter dock's damage slot,
mirroring the ShaguDPS integration. GreedMeter builds its windows lazily
and supports several, so frames are resolved at call time.

A second GreedMeter window docks into the panel's other half: pfUI's
dock decides fill-vs-split by whether the threat slot is filled, so hook
GreedMeter's window add/remove to reconcile that slot and re-run the
resize. Only the first two windows fit the panel; any beyond that float.
2026-08-10 23:52:52 -05:00
Brues ab0ad7dcf9 Group tooltip settings into sections 2026-08-10 15:07:01 -05:00
Brues c82813df5b Use GetRGB() for NORMAL_FONT_COLOR 2026-08-10 15:05:48 -05:00
Brues 6e1f8095a2 Allow whitespace-only macro names
strtrim collapsed a name like " " to "" and the empty guard then
rejected the save, but a whitespace-only name is valid in vanilla and a
common way to make a name-less macro. Save the raw text and only reject
a truly empty box.
2026-08-10 09:53:34 -05:00
Brues 9cabdf2974 Revert the pixel-perfect scale change
GetPerfectPixel switched to UIParent:GetEffectiveScale(), which reports
the true unclamped scale (e.g. 1.42 on the larger pixelperfect presets,
since the module calls UIParent:SetScale past the uiScale cvar's 1.0
cap). At that scale 768/screenheight/scale drops below half a pixel and
the border edgeSize rounds to zero, so button borders disappeared.

The uiScale cvar's <=1.0 cap was load-bearing, not a bug: it keeps the
1px borders from vanishing. Restore the cvar-based measurement and move
pixelperfect back to its original load position.
2026-08-10 01:27:27 -05:00
Brues b6931359b0 Use the real UKNOWNBEING global in libpredict guards
The 1.12 client defines Blizzard's own misspelling, UKNOWNBEING
("Unknown Being"); UNKNOWNBEING does not exist and resolves to nil, so
both guards never matched. Point them at the real global.
2026-08-10 01:13:47 -05:00
roby-brok e017dbf515 Fix guards, cache keys and comparisons that never match (#40)
* chat: detect whispers before the timestamp is prepended

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

(cherry picked from commit 4b69d597631c422d9360a94ff129323288a28cc2)

* macrotweak + libpredict: inverted install guard, misspelled globals

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

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

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

(cherry picked from commit 4068110dc4134823f1c09f60ca5f8959302cd14f)

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

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

(cherry picked from commit 5e6fe969a03a88c68e28614548b9d44db562ab9a)

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

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

(cherry picked from commit ad927806c46042163f84bde4db1082726bedf28b)

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

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

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

(cherry picked from commit 9ab7f5f7224d12361ca6cd5a49181cbb4bebd387)

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

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

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

(cherry picked from commit 82a37752a5782479849ce7e1304c24733c2ff97a)

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

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

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

(cherry picked from commit dac2d3416aef85a4c4b71c0239d93d309e76e30b)

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

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

(cherry picked from commit dd89a210330583f890a51a82b29e968f3ad36b34)

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

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

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

(cherry picked from commit 617c8320aca2465af62cf0ff5be7dceaed29a3b2)

* bags: remove the duplicate search OnHide handler

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

(cherry picked from commit e77650448b92e5d7f18ae58632e4531cb2813ea6)

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

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

* roll: actually capture the everyone-passed subject

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

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

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

(cherry picked from commit b338b4a16d0def4ae89fde5c6026e58a796b8c00)

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

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

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

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

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

(cherry picked from commit bf055d87fc0a04ac912c13fa874a4331f47bdfa8)

* firstrun: return after bailing on a disabled chat module

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

(cherry picked from commit 1af427e3b38bb7c13f645ae7a48b8ade3e7455a9)

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

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

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

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

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

* cooldown: return after hiding on a nil parent

Without the return it falls straight through to parent:GetName() on the nil it
just tested for.
2026-08-10 01:00:47 -05:00
Brues 75a25cf614 Show a mouseover unit's buffs on the tooltip
Add a "Show Unit Buffs" option that draws a row of buff icons above the
tooltip for mouseover units. Icons come from a ClassicAPI object pool
and are re-anchored left-to-right on each refresh; a single throttled
ticker counts down the remaining time on the visible icons.
2026-08-09 23:12:20 -05:00
Brues 9daf6165fd Show a note-only menu for offline friends
The friend right-click menu only opens for online players, so notes
could not be edited on offline friends. The full menu is useless
offline anyway (Whisper/Invite/Target need an online player), so open
a menu of our own with just the note editor for offline friends.
2026-08-09 22:47:28 -05:00
Brues 06768c985a Merge classicapi-next: friend list and spell fields on ClassicAPI
Squashed from the classicapi-next branch:
- Read spell fields through ClassicAPI instead of nampower (libdebuff, swingtimer)
- Read friend and who-list class tokens from C_FriendList (socialmod, libunitscan)
- Count online friends with C_FriendList.GetNumOnlineFriends
- Add a friend notes module (modules/friendnotes.lua)
- Bump ClassicAPI minimum version to 10906
2026-08-09 19:14:40 -05:00
Brues 881a17e508 Build the class name lookup from FillLocalizedClassList
Replace the hardcoded per-locale ["class"] reverse-lookup tables (localized
class name -> token) with a runtime build in GetEnvironment:

  L["class"] = tInvert(FillLocalizedClassList({}))

The client's own class list supplies the localized names for whatever locale is
running, so the tables were pure duplication -- and this also covers locales the
hardcoded tables never listed. Consumers (libunitscan, panel, socialmod) are
unchanged. Drop ["class"] from every locale file.
2026-08-09 19:03:29 -05:00
Brues 4e461d766e Read debuff durations from C_UnitAuras instead of a static table
Rewrite libdebuff's duration lookups on ClassicAPI's C_UnitAuras, whose
expirationTime carries the real, talent-modified remaining time:

- GetDuration reads the live duration of a matching aura on the player
  (C_UnitAuras.GetAuraDataBySpellName) instead of L["debuffs"][effect][rank].
- GetBestAuraCast reads the active aura's expirationTime directly rather than
  scanning the ownDebuffs/allAuraCasts cast tables -- a unit only ever holds one
  instance of a given spell, so that is inherently the effective one.
- GetMaxRank deleted (it only fed the old GetDuration); AddPending's
  known-debuff guard removed.

The public tracking tables (pfUI.libdebuff_*) are untouched. Drop the now-unused
L["debuffs"] and L["dyndebuffs"] tables from every locale.
2026-08-09 19:02:35 -05:00
Brues 2dfaa65168 Gate combopoint updates and fold player class-color branches
- combopoints: only ever show on the target's plate, so re-issue Show/Hide
  only when the target's combo count changes, and skip non-target plates once
  cleared (they start hidden) -- instead of hiding all five every tick on every
  plate. plate.cpShown tracks the shown count and stays in sync with the frames.
- player class color: merge the identical ENEMY_PLAYER / FRIENDLY_PLAYER
  branches into one condition.
2026-08-09 13:01:29 -05:00
Brues 97ef039d3a Fetch the totem buff icon via GetBuffDataByIndex
Use C_UnitAuras.GetBuffDataByIndex(guid, 1) instead of GetUnitAuras(guid)[1]:
a passive totem's provided buff is its single helpful aura, so the buff-
filtered single-slot fetch is both more precise and cheaper than building the
full aura list to take the first entry.
2026-08-09 03:39:11 -05:00
Brues 25ef02705c Remove dead totems and critters locale tables
Nameplate totem/critter detection now uses UnitCreatureTypeID instead of
name-matching, so the L["totems"] and L["critters"] tables have no remaining
readers. Drop them from every locale file.
2026-08-09 03:17:00 -05:00
Brues 48d1da2015 Localize hardcoded user-facing nameplate/bag/frame strings
Route the remaining hardcoded English labels through the T translation
table and register the keys in enUS:

- bags: "Sort Bags" / "Sort Bank" tooltips
- farmmode: "FARM MODE" overlay
- unitxp: "BEHIND" / "NO LOS" target indicators
- unitframes: raid group header "Group" (reuses the existing key)
- bgscore: "Battleground Frames" mover title
2026-08-09 03:15:01 -05:00
Brues ff29e9d3c2 Detect nameplate totems and critters by creature type
Replace the localized L["totems"] / L["critters"] name-substring matching
with ClassicAPI's UnitCreatureTypeID (CreatureType.dbc: 8 = Critter,
11 = Totem), cached per plate and reset on reuse. Locale-independent,
covers custom/Turtle units, rejects mobs merely named "...Totem...", and
cheaper than the per-plate name loop.

Totem icons now come straight from the game instead of a hand-picked table:
passive totems self-cast their provided buff, so their single aura's icon
is read via C_UnitAuras.GetUnitAuras; active totems (Searing/Magma/Fire Nova)
carry no self-aura, so their icon is captured from UNIT_SPELLCAST_SUCCEEDED
(C_Spell.GetSpellTexture) and cached. Detection and icons read through the
plate's ClassicAPI-provided GUID (cachedGuid).
2026-08-09 03:15:01 -05:00
Brues f0f1c80e24 autoshift: stub shapeshifts table for pfUI-turtle compatibility
Closes #38
2026-08-08 19:59:36 -05:00
Brues 5b4cb2bc9f Hide redundant Total line in item count tooltip and localize labels
Only show "Total" when the item is split across more than one location;
a single-location item made "Total" duplicate the one breakdown line
(e.g. "Equipped: 1 / Total: 1").

Route the Bags/Bank/Equipped/Total labels through the T translation table
and add the keys to every locale file, using the Blizzard GlobalStrings as
the reference for each localized term.
2026-08-08 19:24:42 -05:00
Brues 5a3a962c90 Remove dead unitframes showtooltip setting
The mouseover-scripts migration dropped the unit frame OnEnter/OnLeave
handlers that read C.unitframes[unit].showtooltip, so nothing consulted the
setting anymore. The engine mouseover (driven by the frame's unit attribute)
now shows the native tooltip unconditionally, so the option could not gate it
even if a reader remained.

Drop the config default, the "Enable Mouseover Tooltip" GUI checkbox, and the
now-orphaned locale string from all translation files.
2026-08-08 16:16:33 -05:00
Brues cd08db75d2 Color button highlights from PFUI_CLASS_COLORS
Read the player class color from PFUI_CLASS_COLORS instead of
GetClassColor in SkinButton, SetHighlight, and SkinDropDown.
GetClassColor returns the stock pink for Shaman, but pfUI remaps it to
blue, so the button highlights and locked borders now match.
2026-08-07 23:52:22 -05:00
Brues 6365f0cdb1 Read the innervate cooldown through ClassicAPI
Replace nampower's GetSpellIdCooldown with C_Spell.GetSpellCooldown, and
compute the remaining time from startTime plus duration (both seconds).
Drop the presence guard, since ClassicAPI is a hard dependency. The
module still needs nampower for the AURA_CAST events.
2026-08-07 18:29:37 -05:00
Brues faa801e27d Read spell range through ClassicAPI in the hunter bar
Replace nampower's IsSpellInRange with C_Spell.IsSpellInRange. This drops
the last unguarded nampower call. The ClassicAPI function returns true,
false, or nil, so test == true and == false and leave the bar unchanged
on nil.
2026-08-07 17:39:10 -05:00
Brues 421a103f22 Show action button spell tooltips by spell ID
libspell.GetSpellInfo now returns the spell ID at position 9 and caches
it with SafePack and unpack.

The action bar tracks self.spellID for macro-cast spells and shows the
tooltip with GameTooltip:SetSpellByID. Gather the slot, book type, and
spell ID from one GetSpellInfo call instead of a separate GetSpellIndex
lookup.
2026-08-06 23:08:32 -05:00
Brues ecb28fb194 Strip leading 'v' from version tags before parsing
Release tags often include a leading 'v' (e.g. "v9.0.18"). Previously the code split the raw tag directly which could yield a nil major version and fall back to 0.
2026-08-06 22:10:21 -05:00
Brues 94268b1162 Add option to always show equipment slot flyouts
The per-slot popout arrows previously appeared only while the equipment
manager sidecar was open. Add a "Always Show Equipment Slot Flyouts"
option under Character -> Inventory (character.inventory.equipflyout,
off by default) that instead ties them to the paperdoll, so gear can be
swapped without opening the equipment manager.

Centralize the show/hide in a single UpdatePopouts(): by default the
arrows follow the sidecar, with the option on they follow PaperDollFrame.
Both the sidecar OnShow/OnHide and new PaperDollFrame OnShow/OnHide hooks
route through it, and the flyout hides whenever the arrows go inactive.
2026-08-06 20:28:32 -05:00
Brues 1c7c91fdf7 Fix power bar rendering black behind its backdrop
The power bar's frame level was pinned to f.power's live level
(f.power:GetFrameLevel() + 1). CreateBackdrop caches the backdrop at
f.power's level when it first builds it, but f.power's level can shift
afterward as strata changes propagate across UpdateConfig re-runs. When
it shifts below the cached backdrop, the fill drops behind the dark
backdrop and the bar renders black -- which reproduced on some clients
but not others depending on how the levels resolved.

Anchor the fill to the backdrop's own level instead
(f.power.backdrop:GetFrameLevel() + 1). The backdrop's level is fixed
once created, so the fill is always exactly one level above it
regardless of f.power's value, and the bar can never fall behind it.
2026-08-06 19:16:49 -05:00
brues-code 7f5d18bc30 Update README to simplify project description 2026-08-06 13:29:49 -05:00
Brues 7efc3334ae Add an aura caster line to tooltips
Hook GameTooltip:SetUnitAura to append the caster's name; every pfUI aura
tooltip (player buffs, buffwatch, unitframes) routes through it, so one hook
covers them all. Resolve the name from the live sourceUnit token, falling
back to the sourceGUID name cache (players only) when the token is gone.

Gated behind a new tooltip.aurasource option (off by default) with a matching
"Show Aura Caster" checkbox. The toggle reloads the UI, so the hook is
installed conditionally at load rather than re-checking the flag per tooltip.
2026-08-06 02:51:08 -05:00
Brues f322fbd6c6 CancelItemTempEnchantment can't exist in vanilla without server change 2026-08-06 02:03:21 -05:00
brues-code 41b77a5728 Add the macro icon picker and improve the equipment manager (#35)
* Add pfUI-native macro icon picker

Replace Blizzard's MacroPopupFrame (the name + icon dialog) with a
custom pfUI picker driven by IconDataProviderMixin, so the full spell +
item + loose icon set is available instead of the stock spell-only list.
It has a searchable, filterable icon grid and saves through
C_Macro.CreateMacro / C_Macro.EditMacro, which take the icon as a texture
string -- so arbitrary icons (including index-less INV_* item icons)
persist. The macro-frame API is identical on the modern (Turtle/Octo)
and stock-vanilla macro UIs, so the picker works on both with no client
detection.

The new macroicons module takes over the New / Change-Icon buttons and
retires the stock popup; the blizzard macro skin now only skins
MacroPopupFrame when that module is disabled.

* Gate the macro icon hook behind a config flag

Run the Blizzard_MacroUI hook only when C.disabled["macroicons"] is
"1".

* Add search to the icon pickers and fix set editing

Equipment manager:
- Move the OK button next to Cancel at the bottom-right.
- Add an icon search box in the freed bottom-left space.
- Shift the popup contents up and shrink the window after the title
  was removed.
- Show the current icon when you edit a set. Keep a full icon path as
  is instead of adding a second prefix, and match grid icons by name.
- Close the name/icon popup when the manager frame closes. Remove the
  duplicate OnHide hook.

Both pickers now use Blizzard global strings for the labels and the
icon filters.

* Replace the row hover poll with OnLeave handlers

The gear button sits inside the set row's rectangle, so MouseIsOver(row)
is true for both. Show the gear on row enter. Hide it from a shared
OnLeave on the row and the gear when the cursor leaves the row. This
removes the per-frame OnUpdate poll.

* Handle missing C_Macro.CreateMacro API

Add guards to detect if C_Macro.CreateMacro is unavailable. The macroicons module now returns early if the API doesn't exist, and turtle-wow.lua uses the alternative Blizzard_MacroUI fallback path in such cases.

* Remove the macro picker title and shrink the window

Delete the title font string and its SetText calls. Move every
top-anchored element up 14px and shrink the window height from 498 to
484. The OK, Cancel, and search controls are bottom-anchored, so they
keep their spacing to the grid.

* bump ClassicAPI min version to 1.9.3

* Extract a shared icon picker widget

The macro icon picker and the equipment manager's name/icon popup carried
near-identical copies of the icon grid, All/Spells/Items filter, name search,
and the IconDataProvider state machine. Pull that into a single
pfUI.api.CreateIconPicker helper; each module keeps only its own frame shell,
buttons, and save flow.

Also fix the grid tooltip: pass white to GameTooltip:SetText (it defaults to
red) and derive the icon name with the case-insensitive basename key so
mixed-case provider paths show a clean name instead of the full path.
2026-08-05 22:33:37 -05:00
Brues 06fea6c889 Cache the player guild name for nameplate coloring
Compare each plate's guild name against a cached myGuild value instead
of calling UnitIsInMyGuild for every plate. Refresh the cache on
PLAYER_GUILD_UPDATE and PLAYER_ENTERING_WORLD. This replaces a per-plate
C call with a Lua string compare.
2026-08-05 21:43:08 -05:00
Brues 940a1adae0 Restore Clique support on unit frames
Fixes #33.

Clique's pfUI plugin replaces pfUI.uf.ClickAction and calls it from an
OnClick script. The move to secure attribute clicks removed that
function, so Clique stopped working.

Add the legacy Lua click path back for Clique. When Clique is loaded,
EnableScripts sets an OnClick script that runs ClickAction. When Clique
is not loaded, the frames keep the secure attribute path.

Skip the secure attributes in Clique mode. The type1 target attribute
switches the target before Clique can cast. Right-click opens the unit
menu through ClassicAPI_ToggleUnitMenu.
2026-08-05 21:43:08 -05:00
Brues d13710c5a4 Rebuild macro icon list from the icon data provider
Hook Blizzard_MacroUI and replace UpdateMacroIconFilenames. The new
function reads each icon from IconDataProviderMixin, strips the path
prefix, and adds the name to MACRO_ICON_FILENAMES. A seen table skips
duplicate names.
2026-08-05 03:02:43 -05:00
Brues c3dd7cb601 Refactor player info: simplify haste and SP display
Minor readability and style cleanup.
2026-08-05 00:28:55 -05:00
Brues 9190b88974 Cache player color codes and use full ARGB hex
Replace the cfgColorToHex function with a memoized table. The table
caches the color code for each config string on first use.

Build the code with C_ColorUtil.GenerateTextColorCode instead of
string.format. Change the spell-school colors and the format strings
to 8-character ARGB hex with the |c prefix.
2026-08-04 23:00:51 -05:00
Brues fe0ec82ede Route config color strings through GetStringColor cache
Replace raw strsplit(",", ...) color parsing across action bars, chat,
nameplates, third-party skins, player, and roll with the cached
GetStringColor / GetStringColorObject helpers, dropping the per-build
string-table allocations they created.
2026-08-04 22:27:43 -05:00
Brues 350b1d63ae Revert breaking change for WorldMapFrame 2026-08-04 21:29:03 -05:00
Brues c7f42491ef Update README.md 2026-08-04 16:28:08 -05:00
brues-code 6b458c9e27 Modernize cast bars, unit-frame clicks, and mark tracking with ClassicAPI (#32)
* Migrate player castbar to ClassicAPI UNIT_SPELLCAST_* events

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

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

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

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

* Route target/focus castbars through remote UNIT_SPELLCAST_*

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

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

* Drive nameplate castbars off UNIT_SPELLCAST_*

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

* We no longer use these features from SuperWoW/Nampower

* Use secure unit attributes; remove mouseover scripts

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

* Use SetShown and simplify component default

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

* Drive unit-frame clicks via secure attributes

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

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

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

* Simplify marktracking colors and refresh per mark

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

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

* not true anymore

* Poll marktracking fallback via C_Timer instead of OnUpdate

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

* SuperWoW is now optional

* Update README feature list

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

* Replace fixed-interval OnUpdate polls with C_Timer/RunNextFrame

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

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

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

* update pfQuest link

* Use SetShown/SetSize in panel module

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

* Bump ClassicAPI min version to 1.9.0

* Drive reagent counter from events instead of a polling OnUpdate

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

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

UpdateSlot now seeds a new reagent's real count via GetItemCount so a freshly
placed reagent spell shows the correct number immediately. The updatecache ->
BarsUpdate render path and the IsReagentAction/GetReagentCount accessors are
unchanged.
2026-08-04 14:27:15 -05:00
Brues f338deebe7 UNITFRAME_SECURE_TEMPLATE is always nil in vanilla 2026-08-01 13:12:14 -05:00
Brues c994d00596 Toggle bagslots visibility via SetShown
Replace explicit if/else calling :Hide()/:Show() with :SetShown(not IsShown())
2026-07-31 13:22:17 -05:00
Brues 4be680183a Use GetSize/SetSize for size operations
Replace separate GetWidth/GetHeight and SetWidth/SetHeight calls with GetSize/SetSize for consistency and brevity. Updated modules: addonbuttons (store size via GetSize), chatcopy (use SetSize), easteregg (use SetSize for explosions), farmmode (use GetSize/SetSize when adjusting map size), and game_menu (use GetSize/SetSize when resizing frame).
2026-07-30 14:29:30 -05:00
Brues 2352f1c6ed Use GetSize/SetSize for map frame and buttons
Replace separate GetWidth/GetHeight and SetWidth/SetHeight calls with GetSize and SetSize in modules/map.lua.
2026-07-30 14:11:34 -05:00
Brues 305ff6c86e Collapse frameState.hasTarget into targetGuid
hasTarget was always snapshotted alongside targetGuid (UnitExists("target")
next to UnitGUID("target")), so it was just targetGuid ~= nil. Drop the
field and derive it from targetGuid at both read sites; remove the now-unused
UnitExists perf-cache local.
2026-07-30 13:45:53 -05:00
Brues a8b131898b Event-drive nameplate target state; drop per-tick poll and targetPlateGuid
frameState.hasTarget/targetGuid were polled via UnitExists("target") every
central-loop tick, while targetPlateGuid held the same guid but was already
event-driven -- redundant. Set frameState target fields in PLAYER_TARGET_CHANGED
(seeded on PLAYER_ENTERING_WORLD for a target held across reload), remove the
poll, and collapse targetPlateGuid into frameState.targetGuid (the castbar
frame now reads that). The central loop's only remaining per-tick poll is
GetTime().
2026-07-30 11:59:56 -05:00
Brues 1f9be33772 Use GetStringColorObject for buffbar colors; numeric border check
Store the buffbar color/bordercolor/textcolor as cached read-only ColorMixins
via GetStringColorObject instead of building per-bar {r,g,b,a} tables from
GetStringColor. These color tables are only ever read (SetStatusBarColor /
SetTextColor / the border check), so sharing the cached objects is safe and
drops the intermediate locals and per-bar allocations.

GetStringColorObject's components are numbers, so fix CreateBuffBarFrame's
border check from ~= "0" (string) to ~= 0.
2026-07-30 02:44:56 -05:00
Brues a798e67b7f Use GetStringColor for buffbar color parsing
Route the three buffbar color config splits through GetStringColor (cached)
instead of bare strsplit. GetStringColor returns strings, so this is
behavior-preserving -- including the bordercolor ~= "0" check in
CreateBuffBarFrame -- and just adds the shared color cache and consistency
with the rest of the codebase.
2026-07-30 02:39:56 -05:00
Brues 8b87e30266 Revert GetStringColor to strings; keep object numeric; tidy cmatch
- GetStringColor returns strings again (its lifelong contract). The earlier
  numeric conversion was a return-type change that could break any caller
  doing string comparisons on components (e.g. buffwatch's `~= "0"`); every
  caller was written against strings and color setters coerce, so reverting
  is the safe fix without a codebase-wide audit.
- GetStringColorObject now tonumbers its own inputs, so its ColorMixin stays
  numerically correct (GenerateHexColor/IsEqualTo) independent of the above.
- cmatch: move its a-e / va-ve / ra-re scratch out of module scope into clear
  function-locals (idx*/val*/out*). Strict 1:1 rename, logic unchanged; also
  drops the shared-upvalue reentrancy concern.
2026-07-30 02:39:16 -05:00
Brues f68b14e53b Use SetSize instead of SetWidth/SetHeight
Replaced numerous SetWidth/SetHeight calls with SetSize for consistency and brevity across UI code. Touched api/ui-widgets.lua, api/unitframes.lua and multiple modules (actionbar, addonbuttons, addons, afkcam, autovendor, bags, bgscore, buff, buffwatch, nameplates). Also simplified some sizing math in buff module. No functional behavior intended to change — code modernization only.
2026-07-30 02:27:03 -05:00
Brues 8ab6fab04f Event-drive nameplate mouseover via UPDATE_MOUSEOVER_UNIT
ClassicAPI polyfills UPDATE_MOUSEOVER_UNIT to fire on mouseover loss as well
as gain (src/unit/Mouseover.cpp), so the per-tick UnitGUID("mouseover") poll
in the central loop is no longer needed. Update frameState.mouseoverGuid on
the event and flag just the plates losing/gaining hover (via plateByGuid,
which holds overlays) for an immediate refresh.
2026-07-30 02:01:10 -05:00
Brues 6d1bfe805e Use UnitClassBase for class detection 2026-07-30 01:26:29 -05:00
Brues 838f7f5d25 guard against nil units
UPDATE_MOUSEOVER_UNIT can fire when unit is dropped now so guard against that
2026-07-30 01:20:50 -05:00
Brues fad078fa87 Drop dead GUID-label branch in unit frame OnEnter
No unit frame ever has a GUID label -- CreateUnitFrame is only ever called
with fixed type names (Player/Target/Focus/Party/Raid/...), and .label is only
ever assigned those. The string.find(this.label, "^0x") branch was a leftover
from an older focus implementation; focus now uses the real "focus" unit
token, so OnEnter always resolves the mouseover via the normal unitstr path.
2026-07-30 01:09:53 -05:00
Brues 48d76138ed Iterate active nameplates only; fix global eventcache propagation
- The central OnUpdate looped the entire plate pool (registry) every tick with
  an IsVisible guard. Maintain a visiblePlates set via NAME_PLATE_UNIT_ADDED/
  _REMOVED and iterate that instead, so hidden pool slots aren't touched. The
  IsVisible guard stays as a safety net.
- Fix the global refresh propagation: it set .eventcache on the base frame,
  but OnUpdate reads/clears it on the overlay (nameplate) -- so the propagation
  was a no-op and global refreshes (e.g. target-change alpha/strata) fell
  through to the 0.5s catch-all timer. Set plate.nameplate.eventcache and scope
  to visiblePlates, so those refreshes land on the next tick as intended.

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