32 Commits

Author SHA1 Message Date
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
29 changed files with 495 additions and 308 deletions
+22
View File
@@ -0,0 +1,22 @@
{
"runtime": {
"version": "Lua 5.1"
},
"diagnostics": {
"disable": ["deprecated"],
"globals": [
"this",
"event",
"arg",
"arg1",
"arg2",
"arg3",
"arg4",
"arg5",
"arg6",
"arg7",
"arg8",
"arg9"
]
}
}
+4
View File
@@ -0,0 +1,4 @@
package-as: pfUI
ignore:
- .luarc.json
+10
View File
@@ -204,6 +204,12 @@ function pfUI.api.UnitHasBuff(unit, name)
return C_UnitAuras.GetAuraDataBySpellName(unit, name, "HELPFUL") ~= nil or nil
end
-- [ IsPlayerGuid ]
-- Returns whether a GUID or unit token refers to the local player.
-- guid [string] A unit GUID (or unitID) to test.
-- return: [bool] true if it is the player otherwise "false"
pfUI.api.IsPlayerGuid = _G.IsPlayerGuid
-- [ GetUnbuffedRoster ]
-- Returns a comma-joined, colored list of group members missing the named aura.
-- name [string] the localized aura name to check for
@@ -665,6 +671,10 @@ end
function pfUI.api.CreateGoldString(money)
if type(money) ~= "number" then return "-" end
if _G.GetCoinTextureString then
return "|cffffffff" .. _G.GetCoinTextureString(money) .. "|r"
end
local gold = floor(money/ 100 / 100)
local silver = floor(mod((money/100),100))
local copper = floor(mod(money,100))
+1 -1
View File
@@ -223,7 +223,7 @@ function pfUI:LoadConfig()
pfUI:UpdateConfig("unitframes", nil, "always2dportrait", "0")
pfUI:UpdateConfig("unitframes", nil, "portraittexture", "1")
pfUI:UpdateConfig("unitframes", nil, "layout", "default")
pfUI:UpdateConfig("unitframes", nil, "rangecheck", "1")
pfUI:UpdateConfig("unitframes", nil, "rangecheck", "0")
pfUI:UpdateConfig("unitframes", nil, "rangecheck_mode", "vanilla")
pfUI:UpdateConfig("unitframes", nil, "rangecheck_distance", "40")
pfUI:UpdateConfig("unitframes", nil, "buffdetect", "0")
+97 -58
View File
@@ -205,8 +205,13 @@ function pfUI.uf:UpdateVisibility()
-- cache result of strsub to avoid repeating calls
if not self.cache_raid then
if strsub(self:GetName(),0,6) == "pfRaid" then
self.cache_raid = tonumber(strsub(self:GetName(),7,8)) or 0
local name = self:GetName()
if strsub(name,0,9) == "pfRaidPet" then
-- pet grid slot (pfRaidPet1..40); used to mirror party pets below
self.cache_raid = 0
self.cache_raidpet = tonumber(strsub(name,10)) or 0
elseif strsub(name,0,6) == "pfRaid" then
self.cache_raid = tonumber(strsub(name,7,8)) or 0
else
self.cache_raid = 0
end
@@ -238,6 +243,26 @@ function pfUI.uf:UpdateVisibility()
end
end
-- show raidpet frames as party pets when a party is shown as a raid grid
if self.cache_raidpet then
if not IsInRaid() and IsInGroup() and C.unitframes.raidforgroup == "1" then
local id = self.cache_raidpet
if id == 1 then
-- grid slot 1 mirrors the player, so its pet is the player's pet
self.id = ""
self.label = "pet"
elseif id <= 5 then
self.id = id - 1
self.label = "partypet"
end
-- reset to regular raidpet unitstrings after leaving party mode
elseif self.label == "pet" or self.label == "partypet" then
self.id = self.cache_raidpet
self.label = "raidpet"
end
end
-- display every unit as player while pfUI.uf.showall is set
if pfUI.uf.showall then
self._label = self._label or self.label
@@ -266,9 +291,10 @@ function pfUI.uf:UpdateVisibility()
-- frame shall not be visible
visibility = "hide"
self.visible = nil
elseif hide_group and self.cache_raid == 0 and self.label and strsub(self.label,0,5) == "party" then
-- hide group while shown as a raid grid and option is set (raid frames
-- carry label "party" under raidforgroup, so exclude them by cache_raid)
elseif hide_group and self.cache_raid == 0 and not self.cache_raidpet and self.label and strsub(self.label,0,5) == "party" then
-- hide group while shown as a raid grid and option is set (raid player
-- frames carry label "party" and raid pet frames carry label "partypet"
-- under raidforgroup, so exclude them by cache_raid / cache_raidpet)
visibility = "hide"
self.visible = nil
elseif ( self.fname == "Group0" or self.fname == "PartyPet0" or self.fname == "Party0Target" )
@@ -1612,39 +1638,40 @@ function pfUI.uf:RefreshUnit(unit, component)
for i=1, unit.config.bufflimit do
if not unit.buffs[i] then break end
local aura = C_UnitAuras.GetBuffDataByIndex(unitstr, i)
-- positional UnitBuff allocates nothing (vs a table per icon per refresh)
local name, icon, count, _, duration, expirationTime, _, _, _, spellId = C_UnitAuras.UnitBuff(unitstr, i)
if aura then
unit.buffs[i].texture:SetTexture(aura.icon)
if name then
unit.buffs[i].texture:SetTexture(icon)
unit.buffs[i]:Show()
if aura.applications > 1 then
unit.buffs[i].stacks:SetText(aura.applications)
if count > 1 then
unit.buffs[i].stacks:SetText(count)
else
unit.buffs[i].stacks:SetText("")
end
if aura.expirationTime > 0 then
if expirationTime > 0 then
-- pfUI's cooldown text falls into a 2^32 wraparound branch when start > GetTime(),
-- which happens for talent-extended buffs where the real duration exceeds aura.duration
-- (the Spell.dbc base). Anchor start to now in that case to keep the remaining math sane.
-- which happens for talent-extended buffs where the real duration exceeds the
-- Spell.dbc base. Anchor start to now in that case to keep the remaining math sane.
local now = GetTime()
local start = aura.expirationTime - aura.duration
local duration = aura.duration
if start > now or duration <= 0 then
start, duration = now, aura.expirationTime - now
local start = expirationTime - duration
local dur = duration
if start > now or dur <= 0 then
start, dur = now, expirationTime - now
end
if duration > 0 then
CooldownFrame_SetTimer(unit.buffs[i].cd, start, duration, 1)
if dur > 0 then
CooldownFrame_SetTimer(unit.buffs[i].cd, start, dur, 1)
else
CooldownFrame_SetTimer(unit.buffs[i].cd, 0, 0, 0)
end
elseif aura.duration > 0 then
elseif duration > 0 then
local guid = UnitGUID(unitstr)
local guidStarts = guid and pfUI.uf.aura_starts[guid]
local start = guidStarts and guidStarts[aura.spellId]
local start = guidStarts and guidStarts[spellId]
if start then
CooldownFrame_SetTimer(unit.buffs[i].cd, start, aura.duration, 1)
CooldownFrame_SetTimer(unit.buffs[i].cd, start, duration, 1)
else
CooldownFrame_SetTimer(unit.buffs[i].cd, 0, 0, 0)
end
@@ -1715,10 +1742,11 @@ function pfUI.uf:RefreshUnit(unit, component)
-- selfdebuff narrows to player-cast harmful auras via the PLAYER filter.
-- Player-frame debuffs aren't gated on it (it'd hide most party-applied
-- effects on you).
local filter = (unit.label ~= "player" and selfdebuff == "1") and "HARMFUL|PLAYER" or "HARMFUL"
local aura = C_UnitAuras.GetAuraDataByIndex(unitstr, i, filter)
if aura then
texture, stacks, dtype = aura.icon, aura.applications, aura.dispelName
-- positional UnitDebuff allocates nothing; PLAYER predicate honored for selfdebuff
local filter = (unit.label ~= "player" and selfdebuff == "1") and "PLAYER" or nil
local name, icon, count, dispelType, duration, expirationTime = C_UnitAuras.UnitDebuff(unitstr, i, filter)
if name then
texture, stacks, dtype = icon, count, dispelType
else
texture, stacks, dtype = nil, 0, nil
end
@@ -1731,18 +1759,18 @@ function pfUI.uf:RefreshUnit(unit, component)
if texture then
unit.debuffs[i]:Show()
if aura and aura.expirationTime > 0 then
if expirationTime > 0 then
-- Cap start to now so talent-extended debuffs (expirationTime past
-- the dbc base duration) don't push start into the future and trip
-- CooldownFrame_SetTimer's 2^32-wraparound branch.
local now = GetTime()
local start = aura.expirationTime - aura.duration
local duration = aura.duration
if start > now or duration <= 0 then
start, duration = now, aura.expirationTime - now
local start = expirationTime - duration
local dur = duration
if start > now or dur <= 0 then
start, dur = now, expirationTime - now
end
if duration > 0 then
CooldownFrame_SetTimer(unit.debuffs[i].cd, start, duration, 1)
if dur > 0 then
CooldownFrame_SetTimer(unit.debuffs[i].cd, start, dur, 1)
else
CooldownFrame_SetTimer(unit.debuffs[i].cd, 0, 0, 0)
end
@@ -1800,6 +1828,17 @@ function pfUI.uf:RefreshUnit(unit, component)
end
end
-- Scan the 16 harmful slots once into a reusable set, instead of
-- re-scanning all 16 for every dispellable type below. Reused across
-- frames (populated and consumed within this single RefreshUnit call).
local present = pfUI.uf.dispelPresent or {}
pfUI.uf.dispelPresent = present
for k in pairs(present) do present[k] = nil end
for i=1,16 do
local name, _, _, dispelType = C_UnitAuras.UnitDebuff(unitstr, i)
if name and dispelType and dispelType ~= "" then present[dispelType] = true end
end
for _, debuff in pairs(unit.dispellable) do
indicator[debuff] = indicator[debuff] or CreateFrame("Frame", nil, indicator)
indicator[debuff]:SetParent(indicator)
@@ -1839,15 +1878,7 @@ function pfUI.uf:RefreshUnit(unit, component)
indicator[debuff].disp = indicator.disp
end
indicator[debuff].visible = nil
for i=1,16 do
local a = C_UnitAuras.GetDebuffDataByIndex(unitstr, i)
local dtype = a and a.dispelName
if dtype == debuff then
indicator[debuff].visible = true
end
end
indicator[debuff].visible = present[debuff]
if indicator[debuff].visible then
indicator[debuff]:Show()
@@ -1894,50 +1925,58 @@ function pfUI.uf:RefreshUnit(unit, component)
local pos = 1
if table.getn(unit.indicators) > 0 then
for _, aura in ipairs(C_UnitAuras.GetUnitAuras(unitstr, "HELPFUL")) do
local texLower = string.lower(aura.icon)
local timeleft = aura.expirationTime > 0 and (aura.expirationTime - GetTime()) or nil
local i = 1
while true do
local name, icon, count, _, _, expirationTime = C_UnitAuras.UnitBuff(unitstr, i)
if not name then break end
local texLower = string.lower(icon)
local timeleft = expirationTime > 0 and (expirationTime - GetTime()) or nil
for _, filter in pairs(unit.indicators) do
if filter == texLower then
local hot = HOT_INDICATORS[texLower]
if hot and string.lower(aura.name) ~= hot.name then
if hot and string.lower(name) ~= hot.name then
break -- texture matches but name disambiguates (e.g. shared icon)
end
if hot then
local start, duration, prediction = libpredict:GetHotDuration(unitstr, hot.predict)
pfUI.uf:AddIcon(unit, pos, aura.icon, timeleft or prediction, aura.applications, tonumber(start), tonumber(duration))
pfUI.uf:AddIcon(unit, pos, icon, timeleft or prediction, count, tonumber(start), tonumber(duration))
else
pfUI.uf:AddIcon(unit, pos, aura.icon, timeleft, aura.applications)
pfUI.uf:AddIcon(unit, pos, icon, timeleft, count)
end
pos = pos + 1
break
end
end
i = i + 1
end
end
if table.getn(unit.indicator_custom) > 0 then
for _, aura in ipairs(C_UnitAuras.GetUnitAuras(unitstr, "HELPFUL")) do
local timeleft = aura.expirationTime > 0 and (aura.expirationTime - GetTime()) or nil
local lowerName = string.lower(aura.name)
local ai = 1
while true do
local name, icon, count, _, _, expirationTime = C_UnitAuras.UnitBuff(unitstr, ai)
if not name then break end
local timeleft = expirationTime > 0 and (expirationTime - GetTime()) or nil
local lowerName = string.lower(name)
for _, filter in pairs(unit.indicator_custom) do
if filter == lowerName then
pfUI.uf:AddIcon(unit, pos, aura.icon, timeleft, aura.applications)
pfUI.uf:AddIcon(unit, pos, icon, timeleft, count)
pos = pos + 1
break
end
end
ai = ai + 1
end
local debuffFilter = unit.config.selfdebuff == "1" and "HARMFUL|PLAYER" or "HARMFUL"
local debuffFilter = unit.config.selfdebuff == "1" and "PLAYER" or nil
for i=1,16 do -- scan for custom debuffs
local aura = C_UnitAuras.GetAuraDataByIndex(unitstr, i, debuffFilter)
if aura then
local timeleft = aura.expirationTime > 0 and (aura.expirationTime - GetTime()) or nil
local name, icon, count, _, _, expirationTime = C_UnitAuras.UnitDebuff(unitstr, i, debuffFilter)
if name then
local timeleft = expirationTime > 0 and (expirationTime - GetTime()) or nil
for _, filter in pairs(unit.indicator_custom) do
if filter == string.lower(aura.name) then
pfUI.uf:AddIcon(unit, pos, aura.icon, timeleft, aura.applications)
if filter == string.lower(name) then
pfUI.uf:AddIcon(unit, pos, icon, timeleft, count)
pos = pos + 1
break
end
+6
View File
@@ -63,6 +63,12 @@ for i=1,40 do
pfValidUnits["nameplate" .. i .. "target"] = true
end
if TURTLE_WOW_VERSION then -- We already know this table is going to be replaced by turtle-wow.lua, so there's no point in declaring it.
pfSellData = {} -- create empty just incase user has turtle-wow module disabled.
return
end
-- This table makes me sick.
-- itemid = buyPrice (copper). Sell price comes from C_Item.GetItemSellPriceByID;
-- this table only carries the curated vendor-buy price (which isn't a static
-- engine field — it's collected from real vendor encounters).
+3
View File
@@ -168,6 +168,9 @@ pfUI.libdebuff_spell_start_self_hooks["libpredict"] = function(spellId, casterGu
return
end
spell_queue[1] = spellName
spell_queue[2] = spellName .. (C_Spell.GetSpellSubtext(spellId) or "")
if spell_queue[1] == spellName and cache[spell_queue[2]] then
local amount = cache[spell_queue[2]][1]
local casttime = castTime
+7 -7
View File
@@ -23,7 +23,7 @@ pfUI:RegisterModule("buff", function ()
CreateBackdropShadow(buff)
end
local aura = C_UnitAuras.GetAuraDataByIndex("player", buff.id, buff.btype)
local name, icon, count, dispelType, _, expirationTime, _, _, _, spellId = C_UnitAuras.UnitAura("player", buff.id, buff.btype)
--detect weapon buffs
if buff.btype == "HELPFUL" and ((C.buffs.separateweapons == "0" and buff.gid <= pfUI.buff.wepbuffs.count) or (pfUI.buff.wepbuffs.count > 0 and buff.weapon ~= nil)) then
@@ -55,16 +55,16 @@ pfUI:RegisterModule("buff", function ()
buff.texture:SetTexture(GetInventoryItemTexture("player", 17))
buff.backdrop:SetBackdropBorderColor(GetItemQualityColor(GetInventoryItemQuality("player", 17) or 1))
end
elseif aura and (( buff.btype == "HARMFUL" and C.buffs.debuffs == "1" ) or ( buff.btype == "HELPFUL" and C.buffs.buffs == "1" )) then
elseif name and (( buff.btype == "HARMFUL" and C.buffs.debuffs == "1" ) or ( buff.btype == "HELPFUL" and C.buffs.buffs == "1" )) then
-- Set Buff Texture and Border
buff.mode = buff.btype
buff.expirationTime = aura.expirationTime
buff.stackCount = aura.applications
buff.spellId = aura.spellId
buff.texture:SetTexture(aura.icon)
buff.expirationTime = expirationTime
buff.stackCount = count
buff.spellId = spellId
buff.texture:SetTexture(icon)
if buff.btype == "HARMFUL" then
local dispelColor = C_UnitAuras.GetAuraDispelTypeColor(aura.dispelName)
local dispelColor = C_UnitAuras.GetAuraDispelTypeColor(dispelType)
buff.backdrop:SetBackdropBorderColor(dispelColor:GetRGBA())
else
buff.backdrop:SetBackdropBorderColor(br,bg,bb,ba)
+10 -11
View File
@@ -75,10 +75,10 @@ pfUI:RegisterModule("buffwatch", function ()
local function GetBuffData(unit, id, type, selfdebuff)
local filter = (selfdebuff and type == "HARMFUL") and "HARMFUL|PLAYER" or type
local aura = C_UnitAuras.GetAuraDataByIndex(unit, id, filter)
if not aura then return end
local remaining = aura.expirationTime > 0 and (aura.expirationTime - GetTime()) or 0
return remaining, aura.icon, aura.name, aura.applications
local name, icon, count, dispelType, _, expirationTime = C_UnitAuras.UnitAura(unit, id, filter)
if not name then return end
local remaining = expirationTime > 0 and (expirationTime - GetTime()) or 0
return remaining, icon, name, count, dispelType
end
local function StatusBarOnClick()
@@ -236,7 +236,7 @@ pfUI:RegisterModule("buffwatch", function ()
local selfdebuff = frame.config.selfdebuff == "1"
for i=1,32 do
local timeleft, texture, name, stacks = GetBuffData(frame.unit, i, frame.type, selfdebuff)
local timeleft, texture, name, stacks, dtype = GetBuffData(frame.unit, i, frame.type, selfdebuff)
timeleft = timeleft or 0
if texture and name and name ~= "" and BuffIsVisible(frame.config, name) then
@@ -245,12 +245,14 @@ pfUI:RegisterModule("buffwatch", function ()
frame.buffs[i][3] = name
frame.buffs[i][4] = texture
frame.buffs[i][5] = stacks
frame.buffs[i][6] = dtype
else
frame.buffs[i][1] = 0
frame.buffs[i][2] = nil
frame.buffs[i][3] = nil
frame.buffs[i][4] = nil
frame.buffs[i][5] = 0
frame.buffs[i][6] = nil
end
end
@@ -294,12 +296,9 @@ pfUI:RegisterModule("buffwatch", function ()
-- calculate dynamic auto color
local r, g, b
if frame.type == "HARMFUL" then
r, g, b = 1, .2, .2
local a = C_UnitAuras.GetDebuffDataByIndex(frame.unit, data[2])
local dtype = a and a.dispelName
if dtype and DebuffTypeColor[dtype] then
r,g,b = DebuffTypeColor[dtype].r,DebuffTypeColor[dtype].g,DebuffTypeColor[dtype].b
end
-- official Blizzard dispel-type colors (matches unitframes/buff);
-- nil/unknown type falls back to the DEBUFF_TYPE_NONE colour
r, g, b = C_UnitAuras.GetAuraDispelTypeColor(data[6] or ""):GetRGBA()
else
r,g,b = str2rgb(data[3])
end
+1 -1
View File
@@ -45,7 +45,7 @@ pfUI:RegisterModule("castbar", function ()
if not cb.tradeskillTotal or not cb.activeName or not cb.showname then return end
local remaining = cb.tradeskillTotal - (cb.tradeskillCompleted or 0)
if remaining > 1 then
cb.bar.left:SetText(string.format("%s (%d)", cb.activeName, remaining))
cb.bar.left:SetFormattedText("%s (%d)", cb.activeName, remaining)
else
cb.bar.left:SetText(cb.activeName)
end
+91 -21
View File
@@ -63,6 +63,23 @@ pfUI:RegisterModule("chat", function ()
return pfUI_cache["chathistory"][realm][player][id]
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.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:SetScript("OnShow", function() pfUI.chat:RefreshChat() end)
UpdateMovable(pfUI.chat.left)
CreateBackdrop(pfUI.chat.left, default_border, nil, .8)
ApplyPanelColors(pfUI.chat.left)
if C.chat.global.frameshadow == "1" then
CreateBackdropShadow(pfUI.chat.left)
end
if C.chat.global.custombg == "1" then
local r, g, b, a = GetStringColor(C.chat.global.background)
pfUI.chat.left.backdrop:SetBackdropColor(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
local r, g, b, a = GetStringColor(C.chat.global.border)
pfUI.chat.left.backdrop:SetBackdropBorderColor(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
end
pfUI.chat.left.panelTop = CreateFrame("Frame", "leftChatPanelTop", pfUI.chat.left)
pfUI.chat.left.panelTop:ClearAllPoints()
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:SetScript("OnShow", function() pfUI.chat:RefreshChat() end)
UpdateMovable(pfUI.chat.right)
CreateBackdrop(pfUI.chat.right, default_border, nil, .8)
ApplyPanelColors(pfUI.chat.right)
if C.chat.global.frameshadow == "1" then
CreateBackdropShadow(pfUI.chat.right)
end
if C.chat.global.custombg == "1" then
local r, g, b, a = GetStringColor(C.chat.global.background)
pfUI.chat.right.backdrop:SetBackdropColor(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
local r, g, b, a = GetStringColor(C.chat.global.border)
pfUI.chat.right.backdrop:SetBackdropBorderColor(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
end
pfUI.chat.right.panelTop = CreateFrame("Frame", "rightChatPanelTop", pfUI.chat.right)
pfUI.chat.right.panelTop:ClearAllPoints()
pfUI.chat.right.panelTop:SetHeight(C.global.font_size+default_border*2)
@@ -296,6 +297,68 @@ pfUI:RegisterModule("chat", function ()
end
end
-- [ Chat Background Alpha ]
-- Blizzard's per-window transparency slider drives FCF_SetWindowAlpha, which only
-- touches the ChatFrame*Background textures that pfUI hides on docked frames. Mirror
-- the window's stored alpha onto the visible pfUI backdrop so the native slider
-- controls it directly.
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()
local panelheight = C.global.font_size*1.5 + default_border*2 + 2
@@ -447,9 +510,13 @@ pfUI:RegisterModule("chat", function ()
for index, value in pairs(DOCKED_CHAT_FRAMES) do
FCF_UpdateButtonSide(value)
end
pfUI.chat:RefreshBackgroundAlpha()
end
hooksecurefunc("FCF_SaveDock", pfUI.chat.RefreshChat)
hooksecurefunc("FCF_SetWindowAlpha", function() pfUI.chat:RefreshBackgroundAlpha() end)
hooksecurefunc("FCF_SelectDockFrame", function() pfUI.chat:RefreshBackgroundAlpha() end)
if C.chat.global.tabmouse == "1" then
pfUI.chat.mouseovertab = CreateFrame("Frame")
@@ -500,7 +567,7 @@ pfUI:RegisterModule("chat", function ()
FCF_SetLocked(ChatFrame1, 1)
FCF_SetWindowName(ChatFrame1, GENERAL)
FCF_SetWindowColor(ChatFrame1, 0, 0, 0)
FCF_SetWindowAlpha(ChatFrame1, 0)
FCF_SetWindowAlpha(ChatFrame1, 0.8)
FCF_SetChatWindowFontSize(ChatFrame1, 12)
ChatFrame1:SetUserPlaced(1)
@@ -508,7 +575,7 @@ pfUI:RegisterModule("chat", function ()
FCF_SetLocked(ChatFrame2, 1)
FCF_SetWindowName(ChatFrame2, COMBAT_LOG)
FCF_SetWindowColor(ChatFrame2, 0, 0, 0)
FCF_SetWindowAlpha(ChatFrame2, 0)
FCF_SetWindowAlpha(ChatFrame2, 0.8)
FCF_SetChatWindowFontSize(ChatFrame2, 12)
ChatFrame2:SetUserPlaced(1)
@@ -518,7 +585,7 @@ pfUI:RegisterModule("chat", function ()
FCF_SetLocked(ChatFrame3, 1)
FCF_SetWindowName(ChatFrame3, T["Loot & Spam"])
FCF_SetWindowColor(ChatFrame3, 0, 0, 0)
FCF_SetWindowAlpha(ChatFrame3, 0)
FCF_SetWindowAlpha(ChatFrame3, 0.8)
FCF_SetChatWindowFontSize(ChatFrame3, 12)
FCF_UnDockFrame(ChatFrame3)
FCF_SetTabPosition(ChatFrame3, 0)
@@ -566,6 +633,9 @@ pfUI:RegisterModule("chat", function ()
end
pfUI.chat:SetScript("OnEvent", function()
-- restore legacy chat windows stuck at 0 alpha before anything reads it
pfUI.chat:MigrateBackgroundAlpha()
-- set the default chat
FCF_SelectDockFrame(SELECTED_CHAT_FRAME)
+6 -11
View File
@@ -127,7 +127,7 @@ pfUI:RegisterModule("firstrun", function ()
-- welcome dialog
pfUI.firstrun:AddStep("init", function()
local f = CreateFirstRunPage()
f.text:SetText(string.format(T["Welcome to |cff33ffccpf|cffffffffUI|r!\n\nI'm the first run wizard that will guide you through some basic configuration. If you're lazy, feel free to hit the \"Defaults\" button. If you wish to run this dialog again, go to the settings and hit the \"Reset Firstrun\" button.\n\nVisit |cff33ffcc%s|r to check for the latest version."], GetAddOnMetadata(pfUI.name, "X-Website")))
f.text:SetFormattedText(T["Welcome to |cff33ffccpf|cffffffffUI|r!\n\nI'm the first run wizard that will guide you through some basic configuration. If you're lazy, feel free to hit the \"Defaults\" button. If you wish to run this dialog again, go to the settings and hit the \"Reset Firstrun\" button.\n\nVisit |cff33ffcc%s|r to check for the latest version."], GetAddOnMetadata(pfUI.name, "X-Website"))
return f
end)
@@ -137,8 +137,7 @@ pfUI:RegisterModule("firstrun", function ()
f.text:SetText(T["A new installation of |cff33ffccpf|rUI ships with 4 prebuilt design profiles. Click below if you wish to load one of these profiles."])
f.Modern = CreateFrame("Button", nil, f, "UIPanelButtonTemplate")
f.Modern:SetWidth(120)
f.Modern:SetHeight(20)
f.Modern:SetSize(120, 20)
f.Modern:SetPoint("BOTTOM", -65, 100)
f.Modern:SetTextColor(1,1,1)
f.Modern:SetText("Modern")
@@ -151,8 +150,7 @@ pfUI:RegisterModule("firstrun", function ()
SkinButton(f.Modern)
f.Nostalgia = CreateFrame("Button", nil, f, "UIPanelButtonTemplate")
f.Nostalgia:SetWidth(120)
f.Nostalgia:SetHeight(20)
f.Nostalgia:SetSize(120, 20)
f.Nostalgia:SetPoint("BOTTOM", 65, 100)
f.Nostalgia:SetTextColor(1,1,1)
f.Nostalgia:SetText("Nostalgia")
@@ -165,8 +163,7 @@ pfUI:RegisterModule("firstrun", function ()
SkinButton(f.Nostalgia)
f.Legacy = CreateFrame("Button", nil, f, "UIPanelButtonTemplate")
f.Legacy:SetWidth(120)
f.Legacy:SetHeight(20)
f.Legacy:SetSize(120, 20)
f.Legacy:SetPoint("BOTTOM", 65, 75)
f.Legacy:SetTextColor(1,1,1)
f.Legacy:SetText("Legacy")
@@ -179,8 +176,7 @@ pfUI:RegisterModule("firstrun", function ()
SkinButton(f.Legacy)
f.Slim = CreateFrame("Button", nil, f, "UIPanelButtonTemplate")
f.Slim:SetWidth(120)
f.Slim:SetHeight(20)
f.Slim:SetSize(120, 20)
f.Slim:SetPoint("BOTTOM", -65, 75)
f.Slim:SetTextColor(1,1,1)
f.Slim:SetText("Slim")
@@ -197,8 +193,7 @@ pfUI:RegisterModule("firstrun", function ()
f.Slider.text:SetPoint("TOP", f.Slider, "BOTTOM", 0, 2)
f.Slider.text:SetText(T["Scale"])
f.Slider:SetWidth(240)
f.Slider:SetHeight(20)
f.Slider:SetSize(240, 20)
f.Slider:SetPoint("BOTTOM", 0, 50)
f.Slider:SetOrientation('HORIZONTAL')
f.Slider:SetMinMaxValues(0.5, 2.0)
+3 -3
View File
@@ -2880,9 +2880,9 @@ pfUI:RegisterModule("gui", function ()
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 Frame Shadow"], C.chat.global, "frameshadow", "checkbox")
CreateConfig(nil, T["Enable Custom Colors"], C.chat.global, "custombg", "checkbox")
CreateConfig(nil, T["Chat Background Color"], C.chat.global, "background", "color")
CreateConfig(nil, T["Chat Border Color"], C.chat.global, "border", "color")
CreateConfig(U["chat"], T["Enable Custom Colors"], C.chat.global, "custombg", "checkbox")
CreateConfig(U["chat"], T["Chat Background Color"], C.chat.global, "background", "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["Incoming Whispers Color"], C.chat.global, "whisper", "color")
CreateConfig(nil, T["Enable Sticky Chat"], C.chat.global, "sticky", "checkbox")
+1 -1
View File
@@ -181,7 +181,7 @@ pfUI:RegisterModule("map", function ()
end
if mx and my and MouseIsOver(WorldMapButton) then
WorldMapButton.coords.text:SetText(string.format('%.1f / %.1f', mx, my))
WorldMapButton.coords.text:SetFormattedText('%.1f / %.1f', mx, my)
else
WorldMapButton.coords.text:SetText("")
end
+1 -1
View File
@@ -138,7 +138,7 @@ pfUI:RegisterModule("minimap", function ()
local coord = pfUI.minimapCoordinates
coord.posX, coord.posY = GetPlayerMapPosition("player")
if coord.posX ~= 0 and coord.posY ~= 0 then
coord.text:SetText(string.format("%.1f, %.1f", round(coord.posX * 100, 1), round(coord.posY * 100, 1)))
coord.text:SetFormattedText("%.1f, %.1f", round(coord.posX * 100, 1), round(coord.posY * 100, 1))
else
coord.text:SetText("|cffffaaaaN/A")
end
+108 -56
View File
@@ -1039,13 +1039,29 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
local mouseover = plate.cachedGuid and plate.cachedGuid == frameState.mouseoverGuid or nil
local unitstr = target and "target" or mouseover and "mouseover" or plate.cachedGuid or nil
-- target event sometimes fires too quickly, where nameplate identifiers are not
-- yet updated. So while being inside this event, we cannot trust the unitstr.
if event == "PLAYER_TARGET_CHANGED" then unitstr = nil end
-- remove unitstr when it doesn't resolve to this plate's unit (stale istarget,
-- stale mouseover guid or a despawned unit). Must run before the cache fills
-- below -- an unrelated unitstr would poison cache.player/minion with the
-- *other* unit's answer, and the nil-gate would then never recompute it.
if unitstr and UnitName(unitstr) ~= name then unitstr = nil end
-- resolve player vs npc from plate's own unit so libunitscan can't return
-- a player record for an NPC sharing the same name (e.g. Chromie). Stored
-- as true/false/nil so it doubles as the GetUnitInfo hint.
if plate.cache.player == nil and unitstr then
plate.cache.player = UnitIsPlayer(unitstr) and true or false
end
local class, ulevel, elite, player, guild = GetUnitInfo(name, true, plate.cache.player)
if plate.cache.minion == nil and unitstr then
plate.cache.minion = UnitIsMinion(unitstr) and true or false
end
local class, ulevel, elite, player, guild
if not plate.cache.minion then
class, ulevel, elite, player, guild = GetUnitInfo(name, true, plate.cache.player)
end
if plate.cache.player ~= nil then player = plate.cache.player or nil end
-- Use database level ONLY if current level is ?? (fixes ?? after reload, but doesn't override visible levels)
@@ -1065,18 +1081,11 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
if player and unittype == "ENEMY_NPC" then unittype = "ENEMY_PLAYER" end
if player and unittype == "FRIENDLY_NPC" then unittype = "FRIENDLY_PLAYER" end
elite = plate.original.levelicon:IsShown() and not player and "boss" or elite
if not class then plate.wait_for_scan = true end
if not class and not plate.cache.minion then plate.wait_for_scan = true end
-- skip data updates on invisible frames
if not visible then return end
-- target event sometimes fires too quickly, where nameplate identifiers are not
-- yet updated. So while being inside this event, we cannot trust the unitstr.
if event == "PLAYER_TARGET_CHANGED" then unitstr = nil end
-- remove unitstr on unit name mismatch
if unitstr and UnitName(unitstr) ~= name then unitstr = nil end
-- always make sure to keep plate visible
plate:Show()
@@ -1151,15 +1160,24 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
if plate.cache.level ~= level or plate.cache.elite ~= elite then
plate.cache.level = level
plate.cache.elite = elite
plate.level:SetText(string.format("%s%s", level, (elitestrings[elite] or "")))
plate.level:SetFormattedText("%s%s", level, (elitestrings[elite] or ""))
end
-- Set level color from GetDifficultyColor when using DB level
-- Set level color from GetDifficultyColor when using DB level.
-- No brightening here: adding a flat offset to all three channels
-- desaturates every tier toward white and collapses the boundaries
-- (orange reads as yellow, red reads as orange). Use Blizzard's values.
-- Clearing cache.levelcolor forces the sync block in OnUpdate to
-- re-colour once the ?? resolves and it takes ownership again.
if levelFromDB and type(level) == "number" then
local color = GetDifficultyColor(level)
plate.level:SetTextColor(color.r + 0.3, color.g + 0.3, color.b + 0.3, 1)
plate.level:SetTextColor(color.r, color.g, color.b, 1)
plate.cache.levelcolor = nil
end
-- remember who owns the level colour so the two writers cannot fight
plate.cache.levelfromdb = levelFromDB or nil
if guild and C.nameplates.showguildname == "1" then
plate.guild:SetText(guild)
if guild == myGuild then
@@ -1203,21 +1221,21 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
local hasdata = ( rhp and rhpmax ) or estimated or hpmax > 100 or (round(hpmax/100*hp) ~= hp)
if setting == "curperc" and hasdata and rhp then
plate.health.text:SetText(string.format("%s | %s%%", Abbreviate(rhp), ceil(hp/hpmax*100)))
plate.health.text:SetFormattedText("%s | %s%%", Abbreviate(rhp), ceil(hp/hpmax*100))
elseif setting == "cur" and hasdata and rhp then
plate.health.text:SetText(string.format("%s", Abbreviate(rhp)))
plate.health.text:SetFormattedText("%s", Abbreviate(rhp))
elseif setting == "curmax" and hasdata and rhp then
plate.health.text:SetText(string.format("%s - %s", Abbreviate(rhp), Abbreviate(rhpmax)))
plate.health.text:SetFormattedText("%s - %s", Abbreviate(rhp), Abbreviate(rhpmax))
elseif setting == "curmaxs" and hasdata and rhp then
plate.health.text:SetText(string.format("%s / %s", Abbreviate(rhp), Abbreviate(rhpmax)))
plate.health.text:SetFormattedText("%s / %s", Abbreviate(rhp), Abbreviate(rhpmax))
elseif setting == "curmaxperc" and hasdata and rhp then
plate.health.text:SetText(string.format("%s - %s | %s%%", Abbreviate(rhp), Abbreviate(rhpmax), ceil(hp/hpmax*100)))
plate.health.text:SetFormattedText("%s - %s | %s%%", Abbreviate(rhp), Abbreviate(rhpmax), ceil(hp/hpmax*100))
elseif setting == "curmaxpercs" and hasdata and rhp then
plate.health.text:SetText(string.format("%s / %s | %s%%", Abbreviate(rhp), Abbreviate(rhpmax), ceil(hp/hpmax*100)))
plate.health.text:SetFormattedText("%s / %s | %s%%", Abbreviate(rhp), Abbreviate(rhpmax), ceil(hp/hpmax*100))
elseif setting == "deficit" and rhp then
plate.health.text:SetText(string.format("-%s" .. (hasdata and "" or "%%"), Abbreviate(rhpmax - rhp)))
plate.health.text:SetFormattedText("-%s" .. (hasdata and "" or "%%"), Abbreviate(rhpmax - rhp))
else -- "percent" as fallback
plate.health.text:SetText(string.format("%s%%", ceil(hp/hpmax*100)))
plate.health.text:SetFormattedText("%s%%", ceil(hp/hpmax*100))
end
else
plate.health.text:SetText()
@@ -1251,9 +1269,32 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
plate.cache.r, plate.cache.g, plate.cache.b = r, g, b
end
if r + g + b ~= plate.cache.namecolor and unittype == "FRIENDLY_PLAYER" and C.nameplates["friendclassnamec"] == "1" and class and PFUI_CLASS_COLORS[class] then
plate.name:SetTextColor(r, g, b, a)
plate.cache.namecolor = r + g + b
-- Friendly player names take this colour when friendclassnamec is on.
-- Ownership is recorded so the OnUpdate sync below stands down rather than
-- racing us: both writers used to share cache.namecolor despite storing
-- unrelated quantities (this colour vs Blizzard's name FontString), so
-- either could suppress the other -- and since nameplate.cache survives
-- pool reuse, a recycled plate could keep the previous unit's name colour.
local ownname = unittype == "FRIENDLY_PLAYER" and C.nameplates["friendclassnamec"] == "1"
and class and true or nil
if plate.cache.ownname ~= ownname then
plate.cache.ownname = ownname
-- ownership flipped: whichever writer is now in charge must re-assert
plate.cache.namecolor = nil
plate.cache.ownnamecolor = nil
end
-- read the class colour directly rather than reusing the bar's r,g,b: the
-- bar only carries a class colour when friendclassc happens to be on, and
-- it also picks up the tapped-grey and barcombatstate overrides, neither of
-- which belongs on the name.
if ownname then
local cr, cg, cb, ca = PFUI_CLASS_COLORS[class]:GetRGBA()
if cr + cg + cb ~= plate.cache.ownnamecolor then
plate.cache.ownnamecolor = cr + cg + cb
plate.name:SetTextColor(cr, cg, cb, ca)
end
end
if target and C.nameplates.cpdisplay == "1" then
@@ -1281,19 +1322,22 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
for i = 1, 16 do debuffDisplayBuf[i].effect = nil end
if unitstr then
local filter = cfg.owndebuffs and "HARMFUL|PLAYER" or "HARMFUL"
local auras = C_UnitAuras.GetUnitAuras(unitstr, filter)
local now = GetTime()
for _, aura in ipairs(auras) do
if debuffCount >= 16 then break end
-- positional UnitAura writes straight into the reusable buffer, so the
-- per-plate scan allocates nothing (no per-aura table, no result array)
local i = 1
while debuffCount < 16 do
local aname, icon, count, dispelType, duration, expirationTime = C_UnitAuras.UnitAura(unitstr, i, filter)
if not aname then break end
debuffCount = debuffCount + 1
local timeleft = (aura.expirationTime and aura.expirationTime > 0) and (aura.expirationTime - now) or nil
local b = debuffDisplayBuf[debuffCount]
b.effect = aura.name
b.texture = aura.icon
b.stacks = aura.applications
b.dtype = aura.dispelName
b.duration = aura.duration
b.timeleft = timeleft
b.effect = aname
b.texture = icon
b.stacks = count
b.dtype = dispelType
b.duration = duration
b.timeleft = (expirationTime and expirationTime > 0) and (expirationTime - now) or nil
i = i + 1
end
end
for i = 1, 16 do
@@ -1516,38 +1560,46 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
-- otherwise an NPC sharing a name with a known player flips wait_for_scan
-- off here, then OnDataChanged re-sets it, every frame until the mob scan
-- lands.
if nameplate.wait_for_scan and GetUnitInfo(name, true, nameplate.cache.player) then
if nameplate.wait_for_scan and not nameplate.cache.minion and GetUnitInfo(name, true, nameplate.cache.player) then
nameplate.wait_for_scan = nil
update = true
end
-- trigger update when name color changed (includes combat state check)
local r, g, b = original.name:GetTextColor()
local inCombatWithPlayer = cfg.namefightcolor and UnitAffectingCombat(nameplate.unit) and UnitAffectingCombat("player")
if r + g + b ~= nameplate.cache.namecolor or (cfg.namefightcolor and nameplate.cache.inCombat ~= inCombatWithPlayer) then
nameplate.cache.namecolor = r + g + b
nameplate.cache.inCombat = inCombatWithPlayer
-- trigger update when name color changed (includes combat state check).
-- Skipped once OnDataChanged owns the name colour (class-coloured friendly
-- players), so the two writers cannot overwrite each other.
if not nameplate.cache.ownname then
local r, g, b = original.name:GetTextColor()
local inCombatWithPlayer = cfg.namefightcolor and UnitAffectingCombat(nameplate.unit) and UnitAffectingCombat("player")
if cfg.namefightcolor then
if (r > .9 and g < .2 and b < .2) or inCombatWithPlayer then
nameplate.name:SetTextColor(1,0.4,0.2,1)
if r + g + b ~= nameplate.cache.namecolor or (cfg.namefightcolor and nameplate.cache.inCombat ~= inCombatWithPlayer) then
nameplate.cache.namecolor = r + g + b
nameplate.cache.inCombat = inCombatWithPlayer
if cfg.namefightcolor then
if (r > .9 and g < .2 and b < .2) or inCombatWithPlayer then
nameplate.name:SetTextColor(1,0.4,0.2,1)
else
nameplate.name:SetTextColor(r,g,b,1)
end
else
nameplate.name:SetTextColor(r,g,b,1)
nameplate.name:SetTextColor(1,1,1,1)
end
else
nameplate.name:SetTextColor(1,1,1,1)
update = true
end
update = true
end
-- trigger update when level color changed
local r, g, b = original.level:GetTextColor()
r, g, b = r + .3, g + .3, b + .3
if r + g + b ~= nameplate.cache.levelcolor then
nameplate.cache.levelcolor = r + g + b
nameplate.level:SetTextColor(r,g,b,1)
update = true
-- trigger update when level color changed. Skipped while the level came
-- from the database (?? plates), where OnDataChanged owns the colour --
-- otherwise both writers race and cache.levelcolor goes stale, which
-- leaves a recycled plate wearing the previous unit's colour.
if not nameplate.cache.levelfromdb then
local r, g, b = original.level:GetTextColor()
if r + g + b ~= nameplate.cache.levelcolor then
nameplate.cache.levelcolor = r + g + b
nameplate.level:SetTextColor(r,g,b,1)
update = true
end
end
-- use timer based updates
@@ -1657,7 +1709,7 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
rounded = floor(remaining * 100)
if castbar.lastTextTick ~= rounded then
castbar.lastTextTick = rounded
castbar.text:SetText(string.format("%.2f", remaining))
castbar.text:SetFormattedText("%.2f", remaining)
end
end
end
+7 -3
View File
@@ -418,8 +418,13 @@ pfUI:RegisterModule("panel", function()
local repPercent = floor(cur / max * 100)
if repPercent < 100 then
local _, _, _, hex = GetColorGradient(repPercent/100)
local link = GetInventoryItemLink("player", id)
local texture = GetInventoryItemTexture("player", id)
if texture then
link = CreateSimpleTextureMarkup(texture, 24) .. " " .. link
end
itemLines[table.getn(itemLines)+1] = {
GetInventoryItemLink("player", id),
link,
string.format("%s%s%%|r", hex, repPercent)
}
end
@@ -428,8 +433,7 @@ pfUI:RegisterModule("panel", function()
if totalRep > 0 then
GameTooltip:ClearLines()
GameTooltip_SetDefaultAnchor(GameTooltip, this)
GameTooltip:SetText("|cff555555"..(string.gsub(REPAIR_COST,":","")).."|r")
SetTooltipMoney(GameTooltip, totalRep)
GameTooltip:AddLine(REPAIR_COST.." " .. CreateGoldString(totalRep), 0.3333, 0.3333, 0.3333)
for _,line in ipairs(itemLines) do
GameTooltip:AddDoubleLine(line[1],line[2])
end
+24 -2
View File
@@ -125,6 +125,7 @@ pfUI:RegisterModule("raid", function ()
local grid = self.petgrid
local function place(pet, cell, id)
pet.label = "raidpet"
pet.id = id
local r, g = SlotToCoord(cell, grid.fill, grid.x, grid.y)
pet:ClearAllPoints()
@@ -140,6 +141,22 @@ pfUI:RegisterModule("raid", function ()
return
end
-- Party shown as a raid grid: mirror slots 1..5 (player + party members)
-- into fixed cells. UpdateVisibility maps each slot to pet / partypet<N>.
if not IsInRaid() and IsInGroup() and C.unitframes.raidforgroup == "1" then
for id = 1, maxraid do
if self.pets[id] then
if id <= 5 then
place(self.pets[id], id, id)
else
self.pets[id].id = 0
self.pets[id]:Hide()
end
end
end
return
end
if C.unitframes.raidpet.collapse == "1" then
-- Pack the pets that exist into the leading cells, no gaps.
local k = 0
@@ -213,8 +230,13 @@ pfUI:RegisterModule("raid", function ()
this.tick = GetTime() + 1.0
this.pendingUpdate = nil
-- don't proceed without raid
if not IsInRaid() then return end
-- Without a raid there is nothing to sort, but a party shown as a raid
-- grid still needs its pet frames placed and mapped to the party pets.
if not IsInRaid() then
this:LayoutPets()
this:Hide()
return
end
-- clear all existing frames
for i=1, maxraid do SetRaidIndex(pfUI.uf.raid[i], 0) end
+4 -4
View File
@@ -119,7 +119,7 @@ pfUI:RegisterModule("screenshot", function ()
end
function pfUI.screenshot:CHAT_MSG_SYSTEM()
local _,_, standing, rep = string.find(arg1, FACTION_STANDING_CHANGEDregex)
local standing, rep = string.match(arg1, FACTION_STANDING_CHANGEDregex)
if standing and rep then
local dt = date("%a, %b %d, %Y %X")
local loc = string.format("%s - %s",GetRealZoneText(),GetSubZoneText())
@@ -150,13 +150,13 @@ pfUI:RegisterModule("screenshot", function ()
end
function pfUI.screenshot:CHAT_MSG_LOOT()
local _,_, item, amount = string.find(arg1, LOOT_ITEM_SELF_MULTIPLEregex)
local item, amount = string.match(arg1, LOOT_ITEM_SELF_MULTIPLEregex)
if amount then -- ignore stacks
return
else
_,_, item = string.find(arg1, LOOT_ITEM_SELFregex)
item = string.match(arg1, LOOT_ITEM_SELFregex)
if item then
local _, _, itemColor, itemString, itemName = string.find(item, "^(|c%x+)|H(.+)|h(%[.+%])")
local itemColor, itemString, itemName = string.match(item, "^(|c%x+)|H(.+)|h(%[.+%])")
local quality = color2quality[itemColor]
if quality and quality >= tonumber(C.screenshot.loot) then
local dt = date("%a, %b %d, %Y %X")
+8 -8
View File
@@ -97,20 +97,20 @@ pfUI:RegisterModule("socialmod", function ()
if friendName then
friendName:SetText(cname)
friendLoc:SetText(format(TEXT(FRIENDS_LIST_TEMPLATE), zone, status))
friendLoc:SetFormattedText(TEXT(FRIENDS_LIST_TEMPLATE), zone, status)
else
friendLoc:SetText(format(TEXT(FRIENDS_LIST_TEMPLATE), cname, zone, status))
friendLoc:SetFormattedText(TEXT(FRIENDS_LIST_TEMPLATE), cname, zone, status)
end
friendInfo:SetText(format(TEXT(FRIENDS_LEVEL_TEMPLATE), info.level, info.className))
friendInfo:SetFormattedText(TEXT(FRIENDS_LEVEL_TEMPLATE), info.level, info.className)
caption:SetVertexColor(1,1,1,.9)
friendInfo:SetVertexColor(1,1,1,.9)
else
if playerdb[name] and playerdb[name].cname and playerdb[name].level and playerdb[name].lastseen then
caption:SetText(format(TEXT(FRIENDS_LIST_OFFLINE_TEMPLATE), playerdb[name].cname))
friendInfo:SetText(format(TEXT(FRIENDS_LEVEL_TEMPLATE), playerdb[name].level, playerdb[name].lastseen))
caption:SetFormattedText(TEXT(FRIENDS_LIST_OFFLINE_TEMPLATE), playerdb[name].cname)
friendInfo:SetFormattedText(TEXT(FRIENDS_LEVEL_TEMPLATE), playerdb[name].level, playerdb[name].lastseen)
else
caption:SetText(format(TEXT(FRIENDS_LIST_OFFLINE_TEMPLATE), name.."|r"))
caption:SetFormattedText(TEXT(FRIENDS_LIST_OFFLINE_TEMPLATE), name.."|r")
friendInfo:SetText(TEXT(UNKNOWN))
end
@@ -131,9 +131,9 @@ pfUI:RegisterModule("socialmod", function ()
local playerguild = GetGuildInfo("player")
if num + 1 >= MAX_WHOS_FROM_SERVER then
WhoFrameTotals:SetText("|cffffffff" .. format(GetText("WHO_FRAME_TOTAL_TEMPLATE", nil, num), max).." |cffaaaaaa"..format(WHO_FRAME_SHOWN_TEMPLATE, MAX_WHOS_FROM_SERVER))
WhoFrameTotals:SetFormattedText("|cffffffff" .. GetText("WHO_FRAME_TOTAL_TEMPLATE", nil, num) .. " |cffaaaaaa" .. WHO_FRAME_SHOWN_TEMPLATE, max, MAX_WHOS_FROM_SERVER)
else
WhoFrameTotals:SetText("|cffffffff" .. format(GetText("WHO_FRAME_TOTAL_TEMPLATE", nil, num), num).." |cffaaaaaa"..format(WHO_FRAME_SHOWN_TEMPLATE, num))
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
+8 -8
View File
@@ -592,10 +592,10 @@ pfUI:RegisterModule("swingtimer", function ()
end
pfUI.swingtimer.mainhand:SetStatusBarColor(curR, curG, curB, mhA)
if sw_showtext then
pfUI.swingtimer.mainhand.text:SetText(string.format("%.1f", math.floor(S.mhTimer * 10) / 10))
pfUI.swingtimer.mainhand.text:SetFormattedText("%.1f", math.floor(S.mhTimer * 10) / 10)
end
if sw_showspeed and S.mhSpeed > 0 then
pfUI.swingtimer.mainhand.speed:SetText(string.format("%.2f", S.mhSpeed))
pfUI.swingtimer.mainhand.speed:SetFormattedText("%.2f", S.mhSpeed)
end
anyActive = true
end
@@ -626,10 +626,10 @@ pfUI:RegisterModule("swingtimer", function ()
end
end
if sw_showtext then
pfUI.swingtimer.offhand.text:SetText(string.format("%.1f", math.floor(S.ohTimer * 10) / 10))
pfUI.swingtimer.offhand.text:SetFormattedText("%.1f", math.floor(S.ohTimer * 10) / 10)
end
if sw_showspeed and S.ohSpeed > 0 then
pfUI.swingtimer.offhand.speed:SetText(string.format("%.2f", S.ohSpeed))
pfUI.swingtimer.offhand.speed:SetFormattedText("%.2f", S.ohSpeed)
end
anyActive = true
elseif not sw_showoh then
@@ -668,9 +668,9 @@ pfUI:RegisterModule("swingtimer", function ()
end
if sw_showtext then
if remaining <= 0.5 then
pfUI.swingtimer.ranged.text:SetText(string.format("%.1f", math.floor(remaining * 10) / 10))
pfUI.swingtimer.ranged.text:SetFormattedText("%.1f", math.floor(remaining * 10) / 10)
else
pfUI.swingtimer.ranged.text:SetText(string.format("%.1f", math.floor((remaining - 0.5) * 10) / 10))
pfUI.swingtimer.ranged.text:SetFormattedText("%.1f", math.floor((remaining - 0.5) * 10) / 10)
end
end
else
@@ -683,11 +683,11 @@ pfUI:RegisterModule("swingtimer", function ()
pfUI.swingtimer.ranged.right:Hide()
pfUI.swingtimer.ranged.warn:Hide()
if sw_showtext then
pfUI.swingtimer.ranged.text:SetText(string.format("%.1f", math.floor(remaining * 10) / 10))
pfUI.swingtimer.ranged.text:SetFormattedText("%.1f", math.floor(remaining * 10) / 10)
end
end
if sw_showspeed and S.raSpeed > 0 then
pfUI.swingtimer.ranged.speed:SetText(string.format("%.2f", S.raSpeed))
pfUI.swingtimer.ranged.speed:SetFormattedText("%.2f", S.raSpeed)
end
local raProgress = 1 - (S.raTimer / S.raTimerMax)
local raMarkerX = raProgress * sw_width
+11 -2
View File
@@ -166,9 +166,9 @@ pfUI:RegisterModule("tooltip", function ()
end
if C.tooltip.alwaysperc == "0" and ( estimated or hpmax > 100 or round(hpmax/100*hp) ~= hp ) then
pfUI.tooltipStatusBar.HP:SetText(string.format("%s / %s", Abbreviate(rhp), Abbreviate(rhpmax)))
pfUI.tooltipStatusBar.HP:SetFormattedText("%s / %s", Abbreviate(rhp), Abbreviate(rhpmax))
elseif hpmax > 0 then
pfUI.tooltipStatusBar.HP:SetText(string.format("%s%%", ceil(hp/hpmax*100)))
pfUI.tooltipStatusBar.HP:SetFormattedText("%s%%", ceil(hp/hpmax*100))
else
pfUI.tooltipStatusBar.HP:SetText("")
end
@@ -372,6 +372,15 @@ pfUI:RegisterModule("tooltip", function ()
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
+23 -90
View File
@@ -3,56 +3,15 @@ pfUI:RegisterModule("tracking", function ()
MINIMAP_TRACKING_FRAME:UnregisterAllEvents()
MINIMAP_TRACKING_FRAME:Hide()
local function HasEntries(tbl)
for _ in pairs(tbl) do
return true
end
return nil
end
local rawborder, border = GetBorderSize()
local size = tonumber(C.appearance.minimap.tracking_size)
local pulse = C.appearance.minimap.tracking_pulse == "1"
-- Tracking spells identified by SpellID + expected icon path.
-- SpellID is the primary identifier (stable, locale-independent).
-- Icon path is used as secondary confirmation when scanning the spellbook.
local knownTrackingSpells = {
any = {
{ id = 2481, icon = "Racial_Dwarf_FindTreasure" }, -- Find Treasure
{ id = 2580, icon = "Spell_Nature_Earthquake" }, -- Find Minerals
{ id = 2383, icon = "INV_Misc_Flower_02" }, -- Find Herbs (Rank 1)
{ id = 8387, icon = "INV_Misc_Flower_02" }, -- Find Herbs (Rank 2)
{ id = 52917, icon = "INV_TradeSkillItem_03" }, -- Find Trees (TurtleWow)
},
HUNTER = {
{ id = 1494, icon = "Ability_Tracking" }, -- Track Beasts
{ id = 19883, icon = "Spell_Holy_PrayerOfHealing" }, -- Track Humanoids
{ id = 19884, icon = "Spell_Shadow_DarkSummoning" }, -- Track Undead
{ id = 19885, icon = "Ability_Stealth" }, -- Track Hidden
{ id = 19880, icon = "Spell_Frost_SummonWaterElemental" }, -- Track Elementals
{ id = 19878, icon = "Spell_Shadow_SummonFelHunter" }, -- Track Demons
{ id = 19882, icon = "Ability_Racial_Avatar" }, -- Track Giants
{ id = 19879, icon = "INV_Misc_Head_Dragon_01" }, -- Track Dragonkin
},
PALADIN = {
{ id = 5502, icon = "Spell_Holy_SenseUndead" }, -- Sense Undead
},
WARLOCK = {
{ id = 5500, icon = "Spell_Shadow_Metamorphosis" }, -- Sense Demons
},
DRUID = {
{ id = 5225, icon = "Ability_Tracking" }, -- Track Humanoids (Cat Form only)
},
}
-- Build a flat lookup: spellId -> entry, for fast spellbook matching
local spellIdLookup = {}
for _, entries in pairs(knownTrackingSpells) do
for _, entry in ipairs(entries) do
spellIdLookup[entry.id] = entry
end
end
-- Tracking spells come from ClassicAPI's native GetNumTrackingTypes /
-- GetTrackingInfo (see the DLL's docs/API.md "Tracking"). The DLL
-- enumerates them straight from the spellbook by tracking-aura effect,
-- so there is no spell table to maintain here and server-custom trackers
-- (e.g. Turtle's Find Trees) are picked up automatically.
local state = {
texture = nil,
@@ -128,50 +87,24 @@ pfUI:RegisterModule("tracking", function ()
end)
function pfUI.tracking:RefreshSpells()
local playerClass = UnitClassBase("player")
local isCatForm = pfUI.tracking:PlayerIsDruidInCatForm(playerClass)
state.spells = {}
if not GetNumTrackingTypes then return end
-- Build set of valid SpellIDs for this class
local validIds = {}
for _, entry in ipairs(knownTrackingSpells.any) do
validIds[entry.id] = true
end
if knownTrackingSpells[playerClass] then
for _, entry in ipairs(knownTrackingSpells[playerClass]) do
validIds[entry.id] = true
end
end
-- Druid Track Humanoids (5225) only casts in Cat Form. Hide it out of
-- form so the menu never offers a tracker that would fail to cast.
local isCatForm = pfUI.tracking:PlayerIsDruidInCatForm(UnitClassBase("player"))
-- Druids only get Track Humanoids in Cat Form
if playerClass == "DRUID" and not isCatForm then
validIds[5225] = nil
end
-- Scan spellbook: match by icon path, confirm SpellID is valid for this class
for tabIndex = 1, GetNumSpellTabs() do
local _, _, offset, numSpells = GetSpellTabInfo(tabIndex)
for spellIndex = offset + 1, offset + numSpells do
local spellTexture = GetSpellTexture(spellIndex, BOOKTYPE_SPELL)
local spellName = GetSpellName(spellIndex, BOOKTYPE_SPELL)
if pfUI.tracking.invalidSpells[spellName] then
spellTexture = nil
end
if spellTexture then
local lowerTexture = string.lower(spellTexture)
for spellId, entry in pairs(spellIdLookup) do
if validIds[spellId] and not state.spells[spellId]
and strfind(lowerTexture, string.lower(entry.icon)) then
state.spells[spellId] = {
index = spellIndex,
name = spellName,
texture = spellTexture,
spellId = spellId,
}
end
end
end
for i = 1, GetNumTrackingTypes() do
local name, texture, _, _, spellId = GetTrackingInfo(i)
local castable = not (spellId == 5225 and not isCatForm)
if name and texture and castable
and not pfUI.tracking.invalidSpells[name] then
state.spells[i] = {
index = i, -- tracking index, passed to SetTracking()
name = name,
texture = texture,
spellId = spellId,
}
end
end
end
@@ -187,7 +120,7 @@ pfUI:RegisterModule("tracking", function ()
elseif not texture then
state.texture = nil
if pulse and HasEntries(state.spells) then
if pulse and next(state.spells) ~= nil then
pfUI.tracking.pulse = true
pfUI.tracking.icon:SetTexture("Interface\\Icons\\INV_Misc_QuestionMark")
pfUI.tracking.icon:SetVertexColor(1,1,1,1)
@@ -217,7 +150,7 @@ pfUI:RegisterModule("tracking", function ()
checked = spell.texture == state.texture,
arg1 = spell,
func = function (arg1)
CastSpell(arg1.index, BOOKTYPE_SPELL)
SetTracking(arg1.index)
CloseDropDownMenus()
end
})
+2 -2
View File
@@ -134,7 +134,7 @@ pfUI:RegisterModule("unitxp", function ()
end
this.text:SetTextColor(r, g, b, 1)
this.text:SetText(string.format("%.1f%s", distance, suffix))
this.text:SetFormattedText("%.1f%s", distance, suffix)
this.text:Show()
end)
@@ -206,7 +206,7 @@ pfUI:RegisterModule("unitxp", function ()
end
f.text:SetTextColor(r, g, b, 1)
f.text:SetText(string.format("%.1f%s", distance, suffix))
f.text:SetFormattedText("%.1f%s", distance, suffix)
end)
end
pfUI.uf.target.distanceIndicator = pfRangeDisplay
+3 -3
View File
@@ -236,7 +236,7 @@ end
local xpperc = round(xp / xpmax * 100)
local experc = ex and round(ex / xpmax * 100) or 0
if ex then text = "%s: %s%% (%s%% %s)" end
self.bar.text:SetText(string.format(text, T["Experience"], xpperc, experc, T["Rested"]))
self.bar.text:SetFormattedText(text, T["Experience"], xpperc, experc, T["Rested"])
self.tick = GetTime() + self.timeout
if event == "UPDATE_EXHAUSTION" and GameTooltip:IsOwned(self) then
@@ -254,7 +254,7 @@ end
local text = "%s: %s%%"
local xpperc = nextXP and nextXP ~= 0 and round(currXP / nextXP * 100) or 0
self.bar.text:SetText(string.format(text, T["Pet Experience"], xpperc))
self.bar.text:SetFormattedText(text, T["Pet Experience"], xpperc)
self.tick = GetTime() + self.timeout
return
@@ -282,7 +282,7 @@ end
local text = "%s: %s%% (%s)"
local perc = round(barValue / barMax * 100)
local standing = GetText("FACTION_STANDING_LABEL"..standingID, gender)
self.bar.text:SetText(string.format(text, name, perc, standing))
self.bar.text:SetFormattedText(text, name, perc, standing)
self.tick = GetTime() + self.timeout
return
+31 -10
View File
@@ -23,7 +23,7 @@ do
-- ClassicAPI dependency check.
-- pfUI relies pervasively on the modern C_* / SuperWoW / nameplate / focus
-- API surface that ClassicAPI polyfills, so presence is required.
local PFUI_CLASSIC_API_MIN = 10906 -- (X*10000 + Y*100 + Z)
local PFUI_CLASSIC_API_MIN = 11204 -- (X*10000 + Y*100 + Z)
local PFUI_CLASSIC_API_LATEST = PFUI_CLASSIC_API_MIN
local PFUI_CLASSIC_API_WEBSITE = "https://github.com/brues-code/ClassicAPI"
local PFUI_CLASSIC_API_LATEST_URL = PFUI_CLASSIC_API_WEBSITE .. "/releases/latest"
@@ -86,14 +86,13 @@ do
end
end
-- initialize saved variables
pfUI_playerDB = {}
pfUI_config = {}
pfUI_init = {}
pfUI_profiles = {}
pfUI_addon_profiles = {}
pfUI_cache = {}
pfUI_throttle = {}
pfUI_playerDB = pfUI_playerDB or {}
pfUI_config = pfUI_config or {}
pfUI_init = pfUI_init or {}
pfUI_profiles = pfUI_profiles or {}
pfUI_addon_profiles = pfUI_addon_profiles or {}
pfUI_cache = pfUI_cache or {}
pfUI_throttle = pfUI_throttle or {}
-- localization
pfUI_locale = {}
@@ -110,6 +109,14 @@ pfUI.movables = {}
pfUI.version = {}
pfUI.env = {}
-- Capability flag: this fork's pfActionBar buttons correctly handle the modern
-- HookScript widget method, so ClassicAPI's AddOnCompat shim must NOT shadow
-- HookScript during actionbar load (older forks branch on `if button.HookScript`
-- and take a modern path they were never written for). This describes behavior,
-- not identity: a fork that keeps the HookScript-correct actionbar code keeps
-- the flag; one that lacks it should drop the flag and get the safe fallback.
pfUI.handlesHookScript = true
if not pfUI.disabled then
pfUI.events = Mixin({}, CallbackRegistryMixin)
pfUI.events:OnLoad()
@@ -293,6 +300,20 @@ function pfUI:GetEnvironment()
pfUI.env.pfUI_throttle = _G.pfUI_throttle
pfUI.env.L = (pfUI_locale[GetLocale()] or pfUI_locale["enUS"])
pfUI.env.L["class"] = pfUI.env.L["class"] or tInvert(LOCALIZED_CLASS_NAMES_MALE)
if not pfUI.env.L["race"] then
pfUI.env.L["race"] = {}
local i = 1
local raceInfo = C_CreatureInfo.GetRaceInfo(i)
while raceInfo ~= nil do
pfUI.env.L["race"][raceInfo.clientFileString] = {
raceName = raceInfo.raceName,
raceID = raceInfo.raceID,
faction = C_CreatureInfo.GetFactionInfo(i).groupTag,
}
i = i + 1
raceInfo = C_CreatureInfo.GetRaceInfo(i)
end
end
return pfUI.env
end
@@ -555,4 +576,4 @@ function pfUI.SetupCVars()
COMBAT_TEXT_SHOW_HONOR_GAINED = "1"
end
UIParentLoadAddOn("Blizzard_CombatText")
end
end
-1
View File
@@ -6,7 +6,6 @@
## Version: @project-version@
## SavedVariables: pfUI_profiles, pfUI_addon_profiles, pfUI_cache, pfUI_throttle
## SavedVariablesPerCharacter: pfUI_config, pfUI_init, pfUI_playerDB
## Dependencies: !!!ClassicAPI
## X-Website: https://github.com/brues-code/pfUI
pfUI.lua
+2 -3
View File
@@ -295,9 +295,8 @@ pfUI:RegisterSkin("Character", function ()
if faction and faction.reaction and faction.reaction < 8 then
local repLeft = faction.nextReactionThreshold - faction.currentStanding
if repLeft > 1 then
local text = standing:GetText() .. string.format(" (%d)", repLeft)
standing:SetText(text)
standing:GetParent().standingText = text
standing:SetFormattedText("%s (%d)", standing:GetText(), repLeft)
standing:GetParent().standingText = standing:GetText()
end
end
end
+1 -1
View File
@@ -125,7 +125,7 @@ pfUI:RegisterSkin("Inspect", function ()
local guild, title = GetGuildInfo(InspectFrame.unit)
if guild then
InspectGuildText:SetPoint("TOP", InspectLevelText, "BOTTOM", 0, -1)
InspectGuildText:SetText(format(TEXT(GUILD_TITLE_TEMPLATE), title, guild))
InspectGuildText:SetFormattedText(TEXT(GUILD_TITLE_TEMPLATE), title, guild)
InspectGuildText:Show()
else
InspectGuildText:SetText("")