enhanced merge into master

Final release!
This commit is contained in:
Meow
2026-02-04 22:48:30 +01:00
parent df550cf9e2
commit 54355c9019
22 changed files with 4659 additions and 1237 deletions
+15 -3
View File
@@ -668,7 +668,10 @@ pfUI:RegisterModule("actionbar", "vanilla", function ()
start, duration, enable = GetActionCooldown(button.id)
end
CooldownFrame_SetTimer(button.cd, start, duration, enable)
-- Nil-protect: GetActionCooldown can return nil during macro parsing/indexing
if start and duration then
CooldownFrame_SetTimer(button.cd, start, duration, enable or 1)
end
end
local _, active
@@ -982,7 +985,16 @@ pfUI:RegisterModule("actionbar", "vanilla", function ()
pageswitch:RegisterEvent("PLAYER_AURAS_CHANGED")
pageswitch:RegisterEvent("PLAYER_ENTERING_WORLD")
pageswitch:RegisterEvent("UNIT_CASTEVENT")
pageswitch:RegisterEvent("PLAYER_LOGOUT")
pageswitch:SetScript("OnEvent", function()
-- Handle shutdown to prevent crash 132
if event == "PLAYER_LOGOUT" then
this:UnregisterAllEvents()
this:SetScript("OnEvent", nil)
this:SetScript("OnUpdate", nil)
return
end
if class ~= "DRUID" then return end
-- On login/reload: full scan
@@ -1230,8 +1242,8 @@ pfUI:RegisterModule("actionbar", "vanilla", function ()
f.count:SetJustifyH("RIGHT")
f.count:SetJustifyV("BOTTOM")
-- macro spell scan
if C.bars.macroscan == "0" then
-- macro spell scan (disabled when macro addons are loaded)
if C.bars.macroscan == "0" or pfUI:MacroAddonsLoaded() then
f.scanmacro, f.spellslot, f.booktype = nil, nil, nil
else
f.scanmacro = true
+116 -26
View File
@@ -76,8 +76,55 @@ pfUI:RegisterModule("buff", "vanilla:tbc", function ()
buff.backdrop:SetBackdropBorderColor(br,bg,bb,ba)
end
else
buff:Hide()
return
-- Fallback: try UnitBuff/UnitDebuff API which may be more reliable in some cases
local fallbackTexture, fallbackStacks, fallbackDispelType, fallbackSpellId
local maxSlots = buff.btype == "HELPFUL" and 32 or 16
if buff.id >= 1 and buff.id <= maxSlots then
if buff.btype == "HELPFUL" and C.buffs.buffs == "1" then
for i = 1, maxSlots do
local tex, stacks, dtype, spellId = UnitBuff("player", i)
if tex and i == buff.id then
fallbackTexture, fallbackStacks, fallbackDispelType, fallbackSpellId = tex, stacks, dtype, spellId
break
end
if not tex then break end
end
elseif buff.btype == "HARMFUL" and C.buffs.debuffs == "1" then
for i = 1, maxSlots do
local tex, stacks, dtype, spellId = UnitDebuff("player", i)
if tex and i == buff.id then
fallbackTexture, fallbackStacks, fallbackDispelType, fallbackSpellId = tex, stacks, dtype, spellId
break
end
if not tex then break end
end
end
end
if fallbackTexture then
buff.mode = buff.btype
buff.fallbackSpellId = fallbackSpellId
buff.texture:SetTexture(fallbackTexture)
if buff.btype == "HARMFUL" then
if fallbackDispelType == "Magic" then
buff.backdrop:SetBackdropBorderColor(0,1,1,1)
elseif fallbackDispelType == "Poison" then
buff.backdrop:SetBackdropBorderColor(0,1,0,1)
elseif fallbackDispelType == "Curse" then
buff.backdrop:SetBackdropBorderColor(1,0,1,1)
elseif fallbackDispelType == "Disease" then
buff.backdrop:SetBackdropBorderColor(1,1,0,1)
else
buff.backdrop:SetBackdropBorderColor(1,0,0,1)
end
else
buff.backdrop:SetBackdropBorderColor(br,bg,bb,ba)
end
else
buff:Hide()
return
end
end
buff:Show()
@@ -119,30 +166,8 @@ pfUI:RegisterModule("buff", "vanilla:tbc", function ()
buff.btype = btype
buff.gid = i
buff:SetScript("OnUpdate", function()
if not this.next then this.next = GetTime() + .1 end
if this.next > GetTime() then return end
this.next = GetTime() + .1
local timeleft = 0
local stacks = 0
if this.mode == this.btype then
timeleft = GetPlayerBuffTimeLeft(this.bid, this.btype)
stacks = GetPlayerBuffApplications(this.bid, this.btype)
elseif this.mode == "MAINHAND" then
local _, mhtime, mhcharge = GetWeaponEnchantInfo()
timeleft = mhtime/1000
stacks = mhcharge
elseif this.mode == "OFFHAND" then
local _, _, _, _, ohtime, ohcharge = GetWeaponEnchantInfo()
timeleft = ohtime/1000
stacks = ohcharge
end
this.timer:SetText(timeleft > 0 and GetColoredTimeString(timeleft) or "")
this.stacks:SetText(stacks > 1 and stacks or "")
end)
-- PERF: OnUpdate moved to consolidated parent frame handler (see pfUI.buff:SetScript("OnUpdate"))
-- Individual buff frames no longer have their own OnUpdate
buff:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_BOTTOMRIGHT")
@@ -251,6 +276,71 @@ pfUI:RegisterModule("buff", "vanilla:tbc", function ()
end
end)
-- PERF: Consolidated OnUpdate handler for all buff timers
-- This replaces 50 individual OnUpdate handlers with a single one
pfUI.buff:SetScript("OnUpdate", function()
local now = GetTime()
if not this.nextUpdate then this.nextUpdate = now + 0.1 end
if this.nextUpdate > now then return end
this.nextUpdate = now + 0.1
-- Cache weapon enchant info once per update cycle
local mh, mhtime, mhcharge, oh, ohtime, ohcharge = GetWeaponEnchantInfo()
-- Update all visible buff buttons
local buttons = pfUI.buff.buffs.buttons
for i = 1, 32 do
local buff = buttons[i]
if buff:IsShown() then
local timeleft, stacks = 0, 0
if buff.mode == buff.btype then
timeleft = GetPlayerBuffTimeLeft(buff.bid, buff.btype)
stacks = GetPlayerBuffApplications(buff.bid, buff.btype)
elseif buff.mode == "MAINHAND" then
timeleft = mhtime and mhtime / 1000 or 0
stacks = mhcharge or 0
elseif buff.mode == "OFFHAND" then
timeleft = ohtime and ohtime / 1000 or 0
stacks = ohcharge or 0
end
buff.timer:SetText(timeleft > 0 and GetColoredTimeString(timeleft) or "")
buff.stacks:SetText(stacks > 1 and stacks or "")
end
end
-- Update all visible debuff buttons
buttons = pfUI.buff.debuffs.buttons
for i = 1, 16 do
local buff = buttons[i]
if buff:IsShown() then
local timeleft = GetPlayerBuffTimeLeft(buff.bid, buff.btype)
local stacks = GetPlayerBuffApplications(buff.bid, buff.btype)
buff.timer:SetText(timeleft > 0 and GetColoredTimeString(timeleft) or "")
buff.stacks:SetText(stacks > 1 and stacks or "")
end
end
-- Update weapon buff buttons if separate
if C.buffs.separateweapons == "1" then
buttons = pfUI.buff.wepbuffs.buttons
for i = 1, 2 do
local buff = buttons[i]
if buff:IsShown() then
local timeleft, stacks = 0, 0
if buff.mode == "MAINHAND" then
timeleft = mhtime and mhtime / 1000 or 0
stacks = mhcharge or 0
elseif buff.mode == "OFFHAND" then
timeleft = ohtime and ohtime / 1000 or 0
stacks = ohcharge or 0
end
buff.timer:SetText(timeleft > 0 and GetColoredTimeString(timeleft) or "")
buff.stacks:SetText(stacks > 1 and stacks or "")
end
end
end
end)
-- Weapon Buffs
pfUI.buff.wepbuffs = CreateFrame("Frame", "pfWepBuffFrame", UIParent)
pfUI.buff.wepbuffs.count = 0
+25 -2
View File
@@ -125,7 +125,23 @@ pfUI:RegisterModule("buffwatch", "vanilla:tbc", function ()
if this.unit == "player" then
GameTooltip:SetPlayerBuff(GetPlayerBuff(PLAYER_BUFF_START_ID+this.id,this.type))
elseif this.type == "HARMFUL" then
GameTooltip:SetUnitDebuff(this.unit, this.id)
-- For "only own debuffs" mode: find the REAL slot by matching spell name AND caster
local config = this.parent and this.parent.config
if config and config.selfdebuff == "1" and libdebuff then
local ownDebuffName = libdebuff:UnitOwnDebuff(this.unit, this.id)
if ownDebuffName then
-- Search through all game slots to find OUR debuff with matching name
for gameSlot = 1, 16 do
local gameName, _, _, _, _, _, _, gameCaster = libdebuff:UnitDebuff(this.unit, gameSlot)
if gameName == ownDebuffName and gameCaster == "player" then
GameTooltip:SetUnitDebuff(this.unit, gameSlot)
break
end
end
end
else
GameTooltip:SetUnitDebuff(this.unit, this.id)
end
elseif this.type == "HELPFUL" then
GameTooltip:SetUnitBuff(this.unit, this.id)
end
@@ -261,7 +277,14 @@ pfUI:RegisterModule("buffwatch", "vanilla:tbc", function ()
and data[3] and data[3] ~= "" -- buff has a name
and data[4] and data[4] ~= "" -- buff has a texture
then
local uuid = data[4] .. data[3] -- we use that to cache some values for buffs
-- For player: no slot in uuid (slots shift when other buffs expire)
-- For target: include slot (multiple players can have same debuff, slot identifies who)
local uuid
if frame.unit == "player" then
uuid = data[4] .. data[3] -- texture + name only
else
uuid = data[4] .. data[3] .. data[2] -- texture + name + slot
end
-- update bar data
frame.bars[bar] = frame.bars[bar] or CreateStatusBar(bar, frame)
+14 -3
View File
@@ -6,6 +6,17 @@ pfUI:RegisterModule("castbar", "vanilla", function ()
local rawborder, default_border = GetBorderSize("unitframes")
local cbtexture = pfUI.media[C.appearance.castbar.texture]
-- Helper function for castbar timer formatting
local function FormatCastbarTime(value)
if C.unitframes.castbardecimals == "1" then
-- 1 decimal, always floor
return string.format("%.1f", floor(value * 10) / 10)
else
-- 2 decimals (default)
return string.format("%.2f", value)
end
end
local function CreateCastbar(name, parent, unitstr, unitname)
local cb = CreateFrame("Frame", name, parent or UIParent)
@@ -165,10 +176,10 @@ pfUI:RegisterModule("castbar", "vanilla", function ()
if this.showtimer then
if this.delay and this.delay > 0 then
local delay = "|cffffaaaa" .. (channel and "-" or "+") .. round(this.delay,2) .. " |r "
this.bar.right:SetText(delay .. string.format("%.2f",cur) .. " / " .. round(max,2))
local delay = "|cffffaaaa" .. (channel and "-" or "+") .. FormatCastbarTime(this.delay) .. " |r "
this.bar.right:SetText(delay .. FormatCastbarTime(cur) .. " / " .. FormatCastbarTime(max))
else
this.bar.right:SetText(string.format("%.2f",cur) .. " / " .. round(max,2))
this.bar.right:SetText(FormatCastbarTime(cur) .. " / " .. FormatCastbarTime(max))
end
end
+23 -10
View File
@@ -891,6 +891,15 @@ pfUI:RegisterModule("gui", "vanilla:tbc", function ()
"7:" .. T["Small"],
"8:" .. T["Tiny (PixelPerfect)"],
},
["abbrevnum"] = {
"0:" .. T["Full Numbers (4250)"],
"1:" .. T["Abbreviate 2 Decimals (4.25k)"],
"2:" .. T["Abbreviate 1 Decimal (4.2k)"],
},
["castbardecimals"] = {
"1:" .. T["1 Decimal (2.1)"],
"2:" .. T["2 Decimals (2.14)"],
},
["orientation"] = {
"HORIZONTAL:" .. T["Horizontal"],
"VERTICAL:" .. T["Vertical"],
@@ -1378,7 +1387,7 @@ pfUI:RegisterModule("gui", "vanilla:tbc", function ()
donate:SetHeight(20)
donate:SetText(T["Donate"])
donate:SetScript("OnClick", function()
pfUI.chat.urlcopy.CopyText("https://ko-fi.com/shagu")
pfUI.chat.urlcopy.CopyText("https://github.com/me0wg4ming/pfUI")
end)
SkinButton(donate)
@@ -1388,7 +1397,7 @@ pfUI:RegisterModule("gui", "vanilla:tbc", function ()
github:SetHeight(20)
github:SetText(T["GitHub"])
github:SetScript("OnClick", function()
pfUI.chat.urlcopy.CopyText("https://github.com/shagu/pfUI")
pfUI.chat.urlcopy.CopyText("https://github.com/me0wg4ming/pfUI")
end)
SkinButton(github)
@@ -1398,7 +1407,7 @@ pfUI:RegisterModule("gui", "vanilla:tbc", function ()
website:SetHeight(20)
website:SetText(T["Website"])
website:SetScript("OnClick", function()
pfUI.chat.urlcopy.CopyText("https://shagu.org/pfUI")
pfUI.chat.urlcopy.CopyText("https://github.com/me0wg4ming/pfUI")
end)
SkinButton(website)
end)
@@ -1496,7 +1505,8 @@ pfUI:RegisterModule("gui", "vanilla:tbc", function ()
CreateConfig(nil, T["Disable Errors in UIErrors Frame"], C.global, "errors_hide", "checkbox")
CreateConfig(nil, T["Highlight Settings That Require Reload"], C.gui, "reloadmarker", "checkbox")
CreateConfig(nil, T["Show Incompatible Config Entries"], C.gui, "showdisabled", "checkbox")
CreateConfig(nil, T["Abbreviate Numbers (4200 -> 4.2k)"], C.unitframes, "abbrevnum", "checkbox")
CreateConfig(nil, T["Abbreviate Numbers"], C.unitframes, "abbrevnum", "dropdown", pfUI.gui.dropdowns.abbrevnum)
CreateConfig(nil, T["Castbar Timer Decimals"], C.unitframes, "castbardecimals", "dropdown", pfUI.gui.dropdowns.castbardecimals)
CreateConfig(nil, T["Abbreviate Unit Names"], C.unitframes, "abbrevname", "checkbox")
CreateConfig(nil, T["Health Point Estimation"], nil, nil, "header")
CreateConfig(nil, T["Estimate Enemy Health Points"], C.global, "libhealth", "checkbox")
@@ -1669,7 +1679,6 @@ pfUI:RegisterModule("gui", "vanilla:tbc", function ()
CreateConfig(nil, T["Spell Queue Icon Size"], C.unitframes, "spellqueuesize", nil, nil, nil, nil, nil, "vanilla" )
CreateConfig(nil, T["Show Reactive Spell Indicator"], C.unitframes, "reactive_indicator", "checkbox", nil, nil, nil, nil, "vanilla" )
CreateConfig(nil, T["Reactive Indicator Size"], C.unitframes, "reactive_size", nil, nil, nil, nil, nil, "vanilla" )
CreateConfig(nil, T["Enhanced Buff Tracking"], C.unitframes, "nampower_buffs", "checkbox", nil, nil, nil, nil, "vanilla" )
CreateConfig(nil, T["UnitXP Settings"], nil, nil, "header")
CreateConfig(nil, T["Show Line of Sight Indicator"], C.unitframes, "los_indicator", "checkbox", nil, nil, nil, nil, "vanilla" )
@@ -1831,7 +1840,7 @@ pfUI:RegisterModule("gui", "vanilla:tbc", function ()
CreateConfig(U[c], T["Timer"], nil, nil, "header")
CreateConfig(U[c], T["Show Timer Text"], C.unitframes[c], "cooldown_text", "checkbox")
CreateConfig(U[c], T["Show Timer Animation"], C.unitframes[c], "cooldown_anim", "checkbox")
CreateConfig(Reload, T["Show Timer Animation"], C.unitframes[c], "cooldown_anim", "checkbox")
CreateConfig(U[c], T["Buffs"], nil, nil, "header")
CreateConfig(U[c], T["Buff Position"], C.unitframes[c], "buffs", "dropdown", pfUI.gui.dropdowns.uf_buff_position)
@@ -2032,7 +2041,9 @@ pfUI:RegisterModule("gui", "vanilla:tbc", function ()
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["Show Animation On Hidden Bars"], C.bars, "animalways", "checkbox")
CreateConfig(U["bars"], T["Scan Macros For Spells"], C.bars, "macroscan", "checkbox", nil, nil, nil, nil, "vanilla")
if not pfUI:MacroAddonsLoaded() then
CreateConfig(U["bars"], T["Scan Macros For Spells"], C.bars, "macroscan", "checkbox", nil, nil, nil, nil, "vanilla")
end
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["Equipped Item Color"], C.bars, "eqcolor", "color")
@@ -2357,7 +2368,8 @@ pfUI:RegisterModule("gui", "vanilla:tbc", function ()
CreateConfig(U["nameplates"], T["Show Debuff Stacks"], C.nameplates.debuffs, "showstacks", "checkbox")
CreateConfig(U["nameplates"], T["Enable Debuff Timers"], C.nameplates, "debufftimers", "checkbox")
CreateConfig(U["nameplates"], T["Show Timer Text"], C.nameplates, "debufftext", "checkbox")
CreateConfig(nil, T["Show Timer Animation"], C.nameplates, "debuffanim", "checkbox")
CreateConfig(Reload, T["Show Timer Animation"], C.nameplates, "debuffanim", "checkbox")
CreateConfig(U["nameplates"], T["Only Show Own Debuffs (|cffffaaaaExperimental|r)"], C.nameplates, "selfdebuff", "checkbox")
CreateConfig(U["nameplates"], T["Filter Mode"], C.nameplates.debuffs, "filter", "dropdown", pfUI.gui.dropdowns.buffbarfilter)
CreateConfig(U["nameplates"], T["Blacklist"], C.nameplates.debuffs, "blacklist", "list")
@@ -2448,7 +2460,8 @@ pfUI:RegisterModule("gui", "vanilla:tbc", function ()
CreateGUIEntry(T["Components"], T["Modules"], function()
table.sort(pfUI.modules)
for i,m in pairs(pfUI.modules) do
if m ~= "gui" then
-- skip gui and macrotweak when macro addons are loaded
if m ~= "gui" and not (m == "macrotweak" and pfUI:MacroAddonsLoaded()) then
-- create disabled entry if not existing and display
pfUI:UpdateConfig("disabled", nil, m, "0")
CreateConfig(nil, T["Disable Module"] .. " " .. m, C.disabled, m, "checkbox")
@@ -2465,4 +2478,4 @@ pfUI:RegisterModule("gui", "vanilla:tbc", function ()
end
end)
end
end)
end)
+2 -2
View File
@@ -1,6 +1,6 @@
pfUI:RegisterModule("macrotweak", "vanilla", function ()
-- disable macrotweak when SuperCleveRoidMacros is loaded
if IsAddOnLoaded("SuperCleveRoidMacros") then return end
-- disable macrotweak when macro addons are loaded
if IsAddOnLoaded("Supermacro") or IsAddOnLoaded("SuperCleveRoidMacros") or IsAddOnLoaded("UltimaMacros") then return end
-- do not write macro calls into chat input history
if ChatFrameEditBox._AddHistoryLine then
+469 -240
View File
@@ -4,7 +4,6 @@ pfUI:RegisterModule("nameplates", "vanilla", function ()
-- check for SuperWoW support (use SUPERWOW_VERSION global)
local superwow_active = SUPERWOW_VERSION ~= nil
--print("pfUI Nameplates: SuperWoW " .. (superwow_active and "ACTIVE" or "INACTIVE"))
-- Local function references for performance
local GetTime = GetTime
@@ -19,6 +18,10 @@ pfUI:RegisterModule("nameplates", "vanilla", function ()
local UnitCanAssist = UnitCanAssist
local UnitCastingInfo = UnitCastingInfo
local UnitChannelInfo = UnitChannelInfo
local UnitHealth = UnitHealth
local UnitHealthMax = UnitHealthMax
local UnitMana = UnitMana
local UnitManaMax = UnitManaMax
local pairs = pairs
local tonumber = tonumber
local strlower = strlower
@@ -27,6 +30,7 @@ pfUI:RegisterModule("nameplates", "vanilla", function ()
local floor = floor
local ceil = ceil
local abs = abs
local mathmod = math.mod
local unitcolors = {
["ENEMY_NPC"] = { .9, .2, .3, .8 },
@@ -56,7 +60,8 @@ pfUI:RegisterModule("nameplates", "vanilla", function ()
}
-- catch all nameplates
local childs, regions, plate
local childs = {} -- PERF: Reuse table instead of creating new one each scan
local regions, plate
local initialized = 0
-- Friendly zone nameplate disable state
@@ -66,31 +71,111 @@ pfUI:RegisterModule("nameplates", "vanilla", function ()
local parentcount = 0
local platecount = 0
local registry = {}
-- NEW: Cast event cache like Overhead.lua
local CastEvents = {}
-- ============================================================================
-- OPTIMIZATION: GUID-based registries for O(1) lookups
-- ============================================================================
local guidRegistry = {} -- guid -> plate (for direct event routing)
-- Helper function to safely access libdebuff cast data
local function GetCastInfo(guid)
return pfUI.libdebuff_casts and pfUI.libdebuff_casts[guid]
end
local debuffCache = {} -- guid -> { [spellID] = { start, duration } }
local threatMemory = {} -- guid -> true if mob had player targeted
local debuffSeen = {} -- reusable table for debuff tracking (avoid GC churn)
-- PERF: Track visible plate count for adaptive throttling
local visiblePlateCount = 0
local lastVisibleCheck = 0
-- wipe polyfill
local wipe = wipe or function(t) for k in pairs(t) do t[k] = nil end end
-- Player GUID for filtering
local _, PlayerGUID = UnitExists("player")
-- ============================================================================
-- OPTIMIZATION: Config caching
-- ============================================================================
local cfg = {}
local function CacheConfig()
cfg.showcastbar = C.nameplates["showcastbar"] == "1"
cfg.targetcastbar = C.nameplates["targetcastbar"] == "1"
cfg.notargalpha = tonumber(C.nameplates.notargalpha) or 0.5
if cfg.notargalpha > 1 then cfg.notargalpha = cfg.notargalpha / 100 end
cfg.namefightcolor = C.nameplates.namefightcolor == "1"
cfg.spellname = C.nameplates.spellname == "1"
cfg.showhp = C.nameplates.showhp == "1"
cfg.showdebuffs = C.nameplates["showdebuffs"] == "1"
cfg.targetzoom = C.nameplates.targetzoom == "1"
cfg.zoomval = (tonumber(C.nameplates.targetzoomval) or 0.4) + 1
cfg.width = tonumber(C.nameplates.width) or 120
cfg.heighthealth = tonumber(C.nameplates.heighthealth) or 8
cfg.targetglow = C.nameplates.targetglow == "1"
cfg.targethighlight = C.nameplates.targethighlight == "1"
cfg.outcombatstate = C.nameplates.outcombatstate == "1"
cfg.barcombatstate = C.nameplates.barcombatstate == "1"
cfg.ccombatcasting = C.nameplates.ccombatcasting == "1"
cfg.ccombatthreat = C.nameplates.ccombatthreat == "1"
cfg.ccombatnothreat = C.nameplates.ccombatnothreat == "1"
cfg.ccombatstun = C.nameplates.ccombatstun == "1"
cfg.ccombatofftank = C.nameplates.ccombatofftank == "1"
cfg.use_unitfonts = C.nameplates.use_unitfonts == "1"
cfg.font_size = cfg.use_unitfonts and C.global.font_unit_size or C.global.font_size
cfg.hptextformat = C.nameplates.hptextformat
-- NEW: Cache debuff config
cfg.debufftimers = C.nameplates.debufftimers == "1"
cfg.debuffanim = tonumber(C.nameplates.debuffanim) or 0
cfg.debufftext = tonumber(C.nameplates.debufftext) or 1
end
-- ============================================================================
-- OPTIMIZATION: Frame state cache
-- ============================================================================
local frameState = {
now = 0,
hasTarget = false,
targetGuid = nil,
hasMouseover = false,
}
-- cache default border color
local er, eg, eb, ea = GetStringColor(pfUI_config.appearance.border.color)
local function GetCombatStateColor(guid)
-- PERF: Quick exit if not in combat
if not UnitAffectingCombat("player") then return false end
if not UnitAffectingCombat(guid) then return false end
if UnitCanAssist("player", guid) then return false end
local target = guid.."target"
local color = false
if UnitAffectingCombat("player") and UnitAffectingCombat(guid) and not UnitCanAssist("player", guid) then
if C.nameplates.ccombatcasting == "1" and (UnitCastingInfo(guid) or UnitChannelInfo(guid)) then
color = combatstate.CASTING
elseif C.nameplates.ccombatthreat == "1" and UnitIsUnit(target, "player") then
color = combatstate.THREAT
elseif C.nameplates.ccombatofftank == "1" and UnitName(target) and offtanks[strlower(UnitName(target))] then
color = combatstate.OFFTANK
elseif C.nameplates.ccombatofftank == "1" and pfUI.uf and pfUI.uf.raid and pfUI.uf.raid.tankrole[UnitName(target)] then
color = combatstate.OFFTANK
elseif C.nameplates.ccombatnothreat == "1" and UnitExists(target) then
color = combatstate.NOTHREAT
elseif C.nameplates.ccombatstun == "1" and not UnitExists(target) and not UnitIsPlayer(guid) then
color = combatstate.STUN
end
local castInfo = GetCastInfo(guid)
local isCasting = castInfo and castInfo.endTime and frameState.now < castInfo.endTime
local targetingPlayer = UnitIsUnit(target, "player")
-- Remember if mob targets player, clear only when targeting someone else while NOT casting
if targetingPlayer then
threatMemory[guid] = true
elseif UnitExists(target) and not isCasting then
threatMemory[guid] = nil
end
if cfg.ccombatcasting and isCasting then
color = combatstate.CASTING
elseif cfg.ccombatthreat and (targetingPlayer or threatMemory[guid]) then
color = combatstate.THREAT
elseif cfg.ccombatofftank and UnitName(target) and offtanks[strlower(UnitName(target))] then
color = combatstate.OFFTANK
elseif cfg.ccombatofftank and pfUI.uf and pfUI.uf.raid and pfUI.uf.raid.tankrole[UnitName(target)] then
color = combatstate.OFFTANK
elseif cfg.ccombatnothreat and UnitExists(target) then
color = combatstate.NOTHREAT
elseif cfg.ccombatstun and not UnitExists(target) and not UnitIsPlayer(guid) then
color = combatstate.STUN
end
return color
@@ -298,31 +383,25 @@ pfUI:RegisterModule("nameplates", "vanilla", function ()
plate.debuffs[index].stacks:SetJustifyV("BOTTOM")
plate.debuffs[index].stacks:SetTextColor(1,1,0)
-- Read config for cooldown animation and text
local cooldown_anim = tonumber(C.nameplates.debuffanim) or 0
local cooldown_text = tonumber(C.nameplates.debufftext) or 1
if pfUI.client <= 11200 then
-- Animation enabled: Use Model frame with CooldownFrameTemplate
plate.debuffs[index].cd = CreateFrame(COOLDOWN_FRAME_TYPE, plate.platename.."Debuff"..index.."Cooldown", plate.debuffs[index], "CooldownFrameTemplate")
plate.debuffs[index].cd:SetAllPoints(plate.debuffs[index])
plate.debuffs[index].cd.pfCooldownStyleAnimation = 1
plate.debuffs[index].cd.pfCooldownStyleText = cooldown_text
plate.debuffs[index].cd.pfCooldownType = "ALL"
else
-- Animation disabled: Create fake cooldown frame for performance (like ShaguPlates)
-- PERF: Use lightweight fake cooldown frame when animation disabled
-- The Model-based CooldownFrameTemplate causes major lag with many nameplates
if pfUI.client <= 11200 and cfg.debuffanim ~= 1 then
plate.debuffs[index].cd = CreateFrame("Frame", plate.platename.."Debuff"..index.."Cooldown", plate.debuffs[index])
plate.debuffs[index].cd:SetAllPoints(plate.debuffs[index])
-- Add dummy functions so CooldownFrame_SetTimer doesn't crash
plate.debuffs[index].cd:SetScript("OnUpdate", CooldownFrame_OnUpdateModel)
plate.debuffs[index].cd.AdvanceTime = DoNothing
plate.debuffs[index].cd.SetSequence = DoNothing
plate.debuffs[index].cd.SetSequenceTime = DoNothing
plate.debuffs[index].cd.pfCooldownStyleAnimation = 0
plate.debuffs[index].cd.pfCooldownStyleText = cooldown_text
plate.debuffs[index].cd.pfCooldownType = "ALL"
else
-- Use CooldownFrameTemplate for animation or TBC+
plate.debuffs[index].cd = CreateFrame(COOLDOWN_FRAME_TYPE, plate.platename.."Debuff"..index.."Cooldown", plate.debuffs[index], "CooldownFrameTemplate")
plate.debuffs[index].cd:SetAllPoints(plate.debuffs[index])
end
-- Set initial config flags (will be cached per-cooldown later)
plate.debuffs[index].cd.pfCooldownStyleAnimation = cfg.debuffanim
plate.debuffs[index].cd.pfCooldownStyleText = cfg.debufftext
plate.debuffs[index].cd.pfCooldownType = "ALL"
end
local function UpdateDebuffConfig(nameplate, i)
@@ -356,25 +435,62 @@ pfUI:RegisterModule("nameplates", "vanilla", function ()
nameplate.debuffs[i]:SetWidth(tonumber(C.nameplates.debuffsize))
nameplate.debuffs[i]:SetHeight(tonumber(C.nameplates.debuffsize))
-- Update cooldown display settings
if nameplate.debuffs[i].cd then
local cooldown_text = tonumber(C.nameplates.debufftext) or 1
local cooldown_anim = tonumber(C.nameplates.debuffanim) or 0
nameplate.debuffs[i].cd.pfCooldownStyleText = cooldown_text
nameplate.debuffs[i].cd.pfCooldownStyleAnimation = cooldown_anim
-- Update scale for TBC+
if pfUI.client > 11200 then
local debuffsize = tonumber(C.nameplates.debuffsize)
local cdScale = debuffsize / 32
nameplate.debuffs[i].cd:SetScale(cdScale)
end
end
end
-- create nameplate core
local nameplates = CreateFrame("Frame", "pfNameplates", UIParent)
nameplates:RegisterEvent("PLAYER_ENTERING_WORLD")
nameplates:RegisterEvent("PLAYER_TARGET_CHANGED")
nameplates:RegisterEvent("UNIT_COMBO_POINTS")
nameplates:RegisterEvent("PLAYER_COMBO_POINTS")
nameplates:RegisterEvent("UNIT_AURA")
nameplates:RegisterEvent("ZONE_CHANGED_NEW_AREA")
local nameplates = CreateFrame("Frame", "pfNameplates", UIParent)
nameplates:RegisterEvent("PLAYER_ENTERING_WORLD")
nameplates:RegisterEvent("PLAYER_TARGET_CHANGED")
nameplates:RegisterEvent("PLAYER_LOGOUT")
nameplates:RegisterEvent("UNIT_COMBO_POINTS")
nameplates:RegisterEvent("PLAYER_COMBO_POINTS")
nameplates:RegisterEvent("ZONE_CHANGED_NEW_AREA")
-- NEW: Register cast events like Overhead.lua
if superwow_active then
nameplates:RegisterEvent("UNIT_CASTEVENT")
-- Cast tracking handled by libdebuff (SPELL_START/GO/FAILED events)
-- No local event registration needed
-- Callback from libdebuff when auras change (GUID-based, event-driven)
nameplates.OnAuraUpdate = function(self, guid)
if not guid then return end
-- GUID is actual GUID (0xF13000...) from SuperWoW/Nampower events
local plate = guidRegistry[guid]
if plate and plate.nameplate then
-- Mark nameplate for aura update in next OnUpdate cycle
plate.nameplate.auraUpdate = true
end
end
nameplates:SetScript("OnEvent", function()
if event == "PLAYER_ENTERING_WORLD" or event == "ZONE_CHANGED_NEW_AREA" then
-- Stop event handling during logout to prevent crash 132
if event == "PLAYER_LOGOUT" then
this:UnregisterAllEvents()
this:SetScript("OnEvent", nil)
this:SetScript("OnUpdate", nil)
if nameplates.mouselook then
nameplates.mouselook:SetScript("OnUpdate", nil)
end
return
elseif event == "PLAYER_ENTERING_WORLD" or event == "ZONE_CHANGED_NEW_AREA" then
if event == "PLAYER_ENTERING_WORLD" then
_, PlayerGUID = UnitExists("player")
CacheConfig()
this:SetGameVariables()
end
@@ -419,47 +535,27 @@ pfUI:RegisterModule("nameplates", "vanilla", function ()
savedFriendlyState = nil
end
end
elseif event == "UNIT_CASTEVENT" then
-- NEW: Event-based cast handling from Overhead.lua
local casterGUID = arg1
if casterGUID == PlayerGUID then return end
local eventType = arg3 -- "START", "CAST", "FAIL", "CHANNEL"
local spellID = arg4
local castDuration = arg5
local spellName, _, icon = SpellInfo(spellID)
if eventType == "MAINHAND" or eventType == "OFFHAND" then
return
end
if eventType == "CAST" then
if not CastEvents[casterGUID] or spellID ~= CastEvents[casterGUID].spell then
return
elseif event == "PLAYER_TARGET_CHANGED" then
-- Flag target plate for update via GUID registry
local _, targetGuid = UnitExists("target")
if targetGuid then
local plate = guidRegistry[targetGuid]
if plate and plate.nameplate then
plate.nameplate.targetUpdate = true
end
end
if eventType == "START" or eventType == "CHANNEL" then
if not CastEvents[casterGUID] then
CastEvents[casterGUID] = {}
-- Also propagate to all plates for alpha/strata updates
this.eventcache = true
elseif event == "PLAYER_COMBO_POINTS" or event == "UNIT_COMBO_POINTS" then
-- Only flag the target plate for combo point update
local _, targetGuid = UnitExists("target")
if targetGuid then
local plate = guidRegistry[targetGuid]
if plate and plate.nameplate then
plate.nameplate.comboUpdate = true
end
wipe(CastEvents[casterGUID])
CastEvents[casterGUID].event = eventType
CastEvents[casterGUID].spellID = spellID
CastEvents[casterGUID].spellName = spellName
CastEvents[casterGUID].icon = icon
CastEvents[casterGUID].startTime = GetTime()
CastEvents[casterGUID].endTime = castDuration and GetTime() + castDuration / 1000
CastEvents[casterGUID].duration = castDuration and castDuration / 1000 or nil
elseif eventType == "FAIL" then
if CastEvents[casterGUID] then
wipe(CastEvents[casterGUID])
end
end
-- Propagate cast event to all nameplates
for plate in pairs(registry) do
plate.eventcache = true
end
else
this.eventcache = true
@@ -467,8 +563,10 @@ pfUI:RegisterModule("nameplates", "vanilla", function ()
end)
nameplates:SetScript("OnUpdate", function()
-- Throttle main scanner to reasonable rate
if (this.tick or 0) > GetTime() then return else this.tick = GetTime() + 0.05 end
-- PERF: Cache GetTime() once per frame
frameState.now = GetTime()
frameState.hasTarget, frameState.targetGuid = UnitExists("target")
frameState.hasMouseover = UnitExists("mouseover")
-- propagate events to all nameplates
if this.eventcache then
@@ -478,19 +576,76 @@ pfUI:RegisterModule("nameplates", "vanilla", function ()
end
end
-- detect new nameplates
parentcount = WorldFrame:GetNumChildren()
if initialized < parentcount then
childs = { WorldFrame:GetChildren() }
for i = initialized + 1, parentcount do
plate = childs[i]
if IsNamePlate(plate) and not registry[plate] then
nameplates.OnCreate(plate)
registry[plate] = plate
-- PERF: Update visible plate count periodically for adaptive throttling
if frameState.now - lastVisibleCheck > 0.5 then
lastVisibleCheck = frameState.now
local count = 0
for plate in pairs(registry) do
if plate:IsVisible() then count = count + 1 end
end
visiblePlateCount = count
end
-- Throttle ONLY the nameplate scanner
local scanThrottle = nameplates.combat and nameplates.combat.inCombat and 0.1 or 0.05
local shouldScan = (this.tick or 0) <= frameState.now
if shouldScan then
this.tick = frameState.now + scanThrottle
-- detect new nameplates
parentcount = WorldFrame:GetNumChildren()
if initialized < parentcount then
-- PERF: Reuse childs table instead of creating new one
local newchilds = { WorldFrame:GetChildren() }
for i = 1, parentcount do
childs[i] = newchilds[i]
end
for i = initialized + 1, parentcount do
plate = childs[i]
if IsNamePlate(plate) and not registry[plate] then
nameplates.OnCreate(plate)
registry[plate] = plate
end
end
initialized = parentcount
end
end
-- Central OnUpdate for all visible plates
for plate in pairs(registry) do
if plate:IsVisible() then
nameplates.OnUpdate(plate, frameState)
else
-- PERF: Clean up ALL caches for hidden plates to prevent memory leak
local guid = plate.nameplate and plate.nameplate.cachedGuid
if guid then
-- Remove from guidRegistry
if guidRegistry[guid] == plate then
guidRegistry[guid] = nil
end
-- Clean cast cache ONLY if cast has expired
-- (Don't delete active casts just because plate was hidden briefly)
local castInfo = GetCastInfo(guid)
if castInfo and castInfo.endTime and castInfo.endTime < frameState.now then
if pfUI.libdebuff_casts then
pfUI.libdebuff_casts[guid] = nil
end
end
-- Clean debuffCache
if debuffCache[guid] then
debuffCache[guid] = nil
end
-- Clean threatMemory
if threatMemory[guid] then
threatMemory[guid] = nil
end
end
end
initialized = parentcount
end
end)
@@ -498,13 +653,22 @@ pfUI:RegisterModule("nameplates", "vanilla", function ()
nameplates.combat = CreateFrame("Frame")
nameplates.combat:RegisterEvent("PLAYER_ENTER_COMBAT")
nameplates.combat:RegisterEvent("PLAYER_LEAVE_COMBAT")
nameplates.combat:RegisterEvent("PLAYER_LOGOUT")
nameplates.combat:SetScript("OnEvent", function()
if event == "PLAYER_ENTER_COMBAT" then
if event == "PLAYER_LOGOUT" then
this:UnregisterAllEvents()
this:SetScript("OnEvent", nil)
return
elseif event == "PLAYER_ENTER_COMBAT" then
this.inCombat = 1
if PlayerFrame then PlayerFrame.inCombat = 1 end
elseif event == "PLAYER_LEAVE_COMBAT" then
this.inCombat = nil
if PlayerFrame then PlayerFrame.inCombat = nil end
-- Clear threat memory when leaving combat
for k in pairs(threatMemory) do
threatMemory[k] = nil
end
end
end)
@@ -635,9 +799,13 @@ pfUI:RegisterModule("nameplates", "vanilla", function ()
nameplate.castbar = castbar
end
-- Stagger tick to spread updates across frames (0.05s apart per plate)
nameplate.tick = GetTime() + mathmod(platecount, 10) * 0.05
parent.nameplate = nameplate
HookScript(parent, "OnShow", nameplates.OnShow)
HookScript(parent, "OnUpdate", nameplates.OnUpdate)
-- NOTE: OnUpdate is now handled centrally, not per-plate/
parent:SetScript("OnUpdate", nil) -- Disable Blizzard's OnUpdate
nameplates.OnConfigChange(parent)
nameplates.OnShow(parent)
@@ -808,12 +976,12 @@ pfUI:RegisterModule("nameplates", "vanilla", function ()
-- always make sure to keep plate visible
plate:Show()
if target and C.nameplates.targetglow == "1" then
if target and cfg.targetglow then
plate.glow:Show() else plate.glow:Hide()
end
-- target indicator
if superwow_active and C.nameplates.outcombatstate == "1" then
if superwow_active and cfg.outcombatstate then
local guid = plate.parent:GetName(1) or ""
-- determine color based on combat state
@@ -822,7 +990,7 @@ pfUI:RegisterModule("nameplates", "vanilla", function ()
-- set border color
plate.health.backdrop:SetBackdropBorderColor(color.r, color.g, color.b, color.a)
elseif target and C.nameplates.targethighlight == "1" then
elseif target and cfg.targethighlight then
plate.health.backdrop:SetBackdropBorderColor(plate.health.hlr, plate.health.hlg, plate.health.hlb, plate.health.hla)
elseif C.nameplates.outfriendlynpc == "1" and unittype == "FRIENDLY_NPC" then
plate.health.backdrop:SetBackdropBorderColor(unpack(unitcolors[unittype]))
@@ -899,30 +1067,44 @@ pfUI:RegisterModule("nameplates", "vanilla", function ()
plate.health:SetMinMaxValues(hpmin, hpmax)
plate.health:SetValue(hp)
if C.nameplates.showhp == "1" then
if cfg.showhp then
local rhp, rhpmax, estimated
if hpmax > 100 or (round(hpmax/100*hp) ~= hp) then
rhp, rhpmax = hp, hpmax
elseif pfUI.libhealth and pfUI.libhealth.enabled then
rhp, rhpmax, estimated = pfUI.libhealth:GetUnitHealthByName(name,level,tonumber(hp),tonumber(hpmax))
-- Try Nampower first for real HP values via GUID
local guid = superwow_active and plate.parent:GetName(1) or nil
if guid and GetUnitField then
local npHp = GetUnitField(guid, "health")
local npMaxHp = GetUnitField(guid, "maxHealth")
if npHp and npHp > 0 and npMaxHp and npMaxHp > 0 then
rhp, rhpmax = npHp, npMaxHp
end
end
-- Fallback to existing methods
if not rhp then
if hpmax > 100 or (round(hpmax/100*hp) ~= hp) then
rhp, rhpmax = hp, hpmax
elseif pfUI.libhealth and pfUI.libhealth.enabled then
rhp, rhpmax, estimated = pfUI.libhealth:GetUnitHealthByName(name,level,tonumber(hp),tonumber(hpmax))
end
end
local setting = C.nameplates.hptextformat
local hasdata = ( estimated or hpmax > 100 or (round(hpmax/100*hp) ~= hp) )
local setting = cfg.hptextformat
local hasdata = ( rhp and rhpmax ) or estimated or hpmax > 100 or (round(hpmax/100*hp) ~= hp)
if setting == "curperc" and hasdata then
if setting == "curperc" and hasdata and rhp then
plate.health.text:SetText(string.format("%s | %s%%", Abbreviate(rhp), ceil(hp/hpmax*100)))
elseif setting == "cur" and hasdata then
elseif setting == "cur" and hasdata and rhp then
plate.health.text:SetText(string.format("%s", Abbreviate(rhp)))
elseif setting == "curmax" and hasdata then
elseif setting == "curmax" and hasdata and rhp then
plate.health.text:SetText(string.format("%s - %s", Abbreviate(rhp), Abbreviate(rhpmax)))
elseif setting == "curmaxs" and hasdata then
elseif setting == "curmaxs" and hasdata and rhp then
plate.health.text:SetText(string.format("%s / %s", Abbreviate(rhp), Abbreviate(rhpmax)))
elseif setting == "curmaxperc" and hasdata 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)))
elseif setting == "curmaxpercs" and hasdata 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)))
elseif setting == "deficit" then
elseif setting == "deficit" and rhp then
plate.health.text:SetText(string.format("-%s" .. (hasdata and "" or "%%"), Abbreviate(rhpmax - rhp)))
else -- "percent" as fallback
plate.health.text:SetText(string.format("%s%%", ceil(hp/hpmax*100)))
@@ -943,7 +1125,7 @@ pfUI:RegisterModule("nameplates", "vanilla", function ()
r, g, b, a = .5, .5, .5, .8
end
if superwow_active and C.nameplates.barcombatstate == "1" then
if superwow_active and cfg.barcombatstate then
local guid = plate.parent:GetName(1) or ""
local color = GetCombatStateColor(guid)
@@ -971,7 +1153,7 @@ pfUI:RegisterModule("nameplates", "vanilla", function ()
-- update debuffs
local index = 1
if C.nameplates["showdebuffs"] == "1" then
if cfg.showdebuffs then
local verify = string.format("%s:%s", (name or ""), (level or ""))
-- update cached debuffs
@@ -1008,18 +1190,26 @@ pfUI:RegisterModule("nameplates", "vanilla", function ()
plate.debuffs[index].stacks:Hide()
end
if duration and timeleft and C.nameplates.debufftimers == "1" then
-- Read config values for cooldown display
local cooldown_anim = tonumber(C.nameplates.debuffanim) or 0
local cooldown_text = tonumber(C.nameplates.debufftext) or 1
if duration and timeleft and cfg.debufftimers then
-- PERF: Only update cooldown if start time changed significantly
local cd = plate.debuffs[index].cd
local newStart = GetTime() + timeleft - duration
-- Ensure cooldown flags are set
plate.debuffs[index].cd.pfCooldownStyleText = cooldown_text
plate.debuffs[index].cd.pfCooldownStyleAnimation = cooldown_anim
plate.debuffs[index].cd.pfCooldownType = "ALL"
plate.debuffs[index].cd:Show()
CooldownFrame_SetTimer(plate.debuffs[index].cd, GetTime() + timeleft - duration, duration, 1)
if not cd.cachedStart or abs(cd.cachedStart - newStart) > 0.5 then
-- Update config flags only on first run or config change
if not cd.configCached or cd.cachedAnim ~= cfg.debuffanim or cd.cachedText ~= cfg.debufftext then
cd.pfCooldownStyleAnimation = cfg.debuffanim
cd.pfCooldownStyleText = cfg.debufftext
cd:SetAlpha(cfg.debuffanim == 1 and 1 or 0)
cd.cachedAnim = cfg.debuffanim
cd.cachedText = cfg.debufftext
cd.configCached = true
end
cd:Show()
CooldownFrame_SetTimer(cd, newStart, duration, 1)
cd.cachedStart = newStart
end
end
index = index + 1
@@ -1039,45 +1229,111 @@ pfUI:RegisterModule("nameplates", "vanilla", function ()
local frame = frame or this
local nameplate = frame.nameplate
-- Register GUID when plate becomes visible
if superwow_active then
local guid = frame:GetName(1)
if guid then
nameplate.cachedGuid = guid
guidRegistry[guid] = frame
end
end
nameplates:OnDataChanged(nameplate)
end
nameplates.OnUpdate = function(frame)
local frame = frame or this
nameplates.OnUpdate = function(frame, state)
local nameplate = frame.nameplate
local now = GetTime()
local now = state and state.now or GetTime()
-- Performance: Skip invisible frames immediately
local isVisible = frame:IsVisible()
if not isVisible then return end
-- Update GUID registry (lightweight, needed for event routing)
if superwow_active then
local guid = frame:GetName(1)
if guid and guid ~= nameplate.cachedGuid then
if nameplate.cachedGuid and guidRegistry[nameplate.cachedGuid] == frame then
guidRegistry[nameplate.cachedGuid] = nil
end
nameplate.cachedGuid = guid
guidRegistry[guid] = frame
end
end
-- Intelligent throttling based on target/castbar status
local target = UnitExists("target") and frame:GetAlpha() >= 0.99 or nil
-- PERF: Intelligent throttling based on target/castbar status and plate count
local target = state and state.hasTarget and frame:GetAlpha() >= 0.99 or nil
local isCasting = nameplate.castbar and nameplate.castbar:IsShown()
local throttle
if target or isCasting then
throttle = 0.02 -- 50 FPS for target OR active castbar
elseif visiblePlateCount > 20 then
throttle = 0.15 -- ~7 FPS for mass pulls (20+ plates)
else
throttle = 0.1 -- 10 FPS for others (healthbar updates)
end
if (nameplate.lasttick or 0) + throttle > now then return end
-- Check for pending event updates (these bypass throttle for immediate response)
local hasEventUpdate = nameplate.eventcache or nameplate.auraUpdate or nameplate.castUpdate or nameplate.targetUpdate or nameplate.comboUpdate
-- Event updates bypass throttle
if not hasEventUpdate and (nameplate.lasttick or 0) + throttle > now then return end
nameplate.lasttick = now
-- =========================================================================
-- EVERYTHING BELOW RUNS AT THROTTLED RATE (50 FPS target, 10 FPS others)
-- =========================================================================
local update
local original = nameplate.original
local name = original.name:GetText()
local mouseover = UnitExists("mouseover") and original.glow:IsShown() or nil
local namefightcolor = C.nameplates.namefightcolor == "1"
local mouseover = state and state.hasMouseover and original.glow:IsShown() or nil
-- trigger queued event update
if nameplate.eventcache then
if hasEventUpdate then
nameplates:OnDataChanged(nameplate)
nameplate.eventcache = nil
nameplate.auraUpdate = nil
nameplate.castUpdate = nil
nameplate.targetUpdate = nil
nameplate.comboUpdate = nil
end
-- OPTIMIZED: Cache strata changes
-- =========================================================================
-- VANILLA OVERLAP/CLICKTHROUGH HANDLING
-- =========================================================================
if pfUI.client <= 11200 then
local useOverlap = C.nameplates["overlap"] == "1" or C.nameplates["vertical_offset"] ~= "0"
local clickable = C.nameplates["clickthrough"] ~= "1"
if not clickable then
frame:EnableMouse(false)
nameplate:EnableMouse(false)
else
local plate = useOverlap and nameplate or frame
plate:EnableMouse(clickable)
end
if C.nameplates["overlap"] == "1" then
if frame:GetWidth() > 1 then
frame:SetWidth(1)
frame:SetHeight(1)
end
else
if not nameplate.dwidth then
nameplate.dwidth = floor(nameplate:GetWidth() * UIParent:GetScale())
end
if floor(frame:GetWidth()) ~= nameplate.dwidth then
frame:SetWidth(nameplate:GetWidth() * UIParent:GetScale())
frame:SetHeight(nameplate:GetHeight() * UIParent:GetScale())
end
end
local mouseEnabled = nameplate:IsMouseEnabled()
if C.nameplates["clickthrough"] == "0" and C.nameplates["overlap"] == "1" and SpellIsTargeting() == mouseEnabled then
nameplate:EnableMouse(not mouseEnabled)
end
end
-- Cache strata changes
if nameplate.istarget ~= target then
nameplate.target_strata = nil
end
@@ -1092,21 +1348,11 @@ pfUI:RegisterModule("nameplates", "vanilla", function ()
nameplate.istarget = target
-- set non-target plate alpha (cached to prevent flicker)
local configAlpha = tonumber(C.nameplates.notargalpha)
if not configAlpha or configAlpha < 0 then
configAlpha = 100
end
-- Ensure we're working with 0-1 range
if configAlpha > 1 then
configAlpha = configAlpha / 100
end
local desiredAlpha = (target or not UnitExists("target")) and 1 or configAlpha
-- Set non-target plate alpha
local configAlpha = cfg.notargalpha or 0.5
local desiredAlpha = (target or not state.hasTarget) and 1 or configAlpha
if nameplate.cachedAlpha ~= desiredAlpha then
-- Setze nur die nameplate Alpha, nicht den parent frame
nameplate:SetAlpha(desiredAlpha)
nameplate.cachedAlpha = desiredAlpha
end
@@ -1129,13 +1375,22 @@ pfUI:RegisterModule("nameplates", "vanilla", function ()
update = true
end
-- trigger update when name color changed
-- trigger update when name color changed (includes combat state check)
local r, g, b = original.name:GetTextColor()
if r + g + b ~= nameplate.cache.namecolor then
local inCombatWithPlayer = false
if superwow_active and cfg.namefightcolor then
local guid = nameplate.cachedGuid
if guid then
inCombatWithPlayer = UnitAffectingCombat(guid) and UnitAffectingCombat("player")
end
end
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 namefightcolor then
if r > .9 and g < .2 and b < .2 then
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)
@@ -1155,10 +1410,11 @@ pfUI:RegisterModule("nameplates", "vanilla", function ()
update = true
end
-- scan for debuff timeouts
-- PERF: scan for debuff timeouts using indexed access instead of pairs()
if nameplate.debuffcache then
for id, data in pairs(nameplate.debuffcache) do
if ( not data.stop or data.stop < now ) and not data.empty then
for id = 1, 16 do
local data = nameplate.debuffcache[id]
if data and ( not data.stop or data.stop < now ) and not data.empty then
data.empty = true
update = true
end
@@ -1176,12 +1432,12 @@ pfUI:RegisterModule("nameplates", "vanilla", function ()
nameplate.tick = now + .5
end
-- OPTIMIZED: Cache zoom dimensions
if target and C.nameplates.targetzoom == "1" then
-- Zoom animation
if target and cfg.targetzoom then
if not nameplate.health.zoomed then
local zoomval = tonumber(C.nameplates.targetzoomval)+1
local wc = tonumber(C.nameplates.width)*zoomval
local hc = tonumber(C.nameplates.heighthealth)*(zoomval*.9)
local zoomval = cfg.zoomval
local wc = cfg.width * zoomval
local hc = cfg.heighthealth * (zoomval * .9)
nameplate.health.targetWidth = wc
nameplate.health.targetHeight = hc
end
@@ -1189,27 +1445,27 @@ pfUI:RegisterModule("nameplates", "vanilla", function ()
local w, h = nameplate.health:GetWidth(), nameplate.health:GetHeight()
local wc, hc = nameplate.health.targetWidth, nameplate.health.targetHeight
-- Nutze kleine Toleranz um Fließkomma-Schwankungen zu vermeiden
if wc > w + 0.5 then
nameplate.health:SetWidth(w*1.05)
nameplate.health.zoomTransition = true
elseif hc > h + 0.5 then
nameplate.health:SetHeight(h*1.05)
nameplate.health.zoomTransition = true
else
if nameplate.health.zoomTransition then
nameplate.health:SetWidth(wc)
nameplate.health:SetHeight(hc)
nameplate.health.zoomTransition = nil
if wc and hc then
if wc > w + 0.5 then
nameplate.health:SetWidth(w*1.05)
nameplate.health.zoomTransition = true
elseif hc > h + 0.5 then
nameplate.health:SetHeight(h*1.05)
nameplate.health.zoomTransition = true
else
if nameplate.health.zoomTransition then
nameplate.health:SetWidth(wc)
nameplate.health:SetHeight(hc)
nameplate.health.zoomTransition = nil
end
nameplate.health.zoomed = true
end
nameplate.health.zoomed = true
end
elseif nameplate.health.zoomed or nameplate.health.zoomTransition then
local w, h = nameplate.health:GetWidth(), nameplate.health:GetHeight()
local wc = tonumber(C.nameplates.width)
local hc = tonumber(C.nameplates.heighthealth)
local wc = cfg.width
local hc = cfg.heighthealth
-- Nutze kleine Toleranz um Fließkomma-Schwankungen zu vermeiden
if w > wc + 0.5 then
nameplate.health:SetWidth(w*.95)
elseif h > hc + 0.5 then
@@ -1224,27 +1480,25 @@ pfUI:RegisterModule("nameplates", "vanilla", function ()
end
end
-- OPTIMIZED: UNIT_CASTEVENT implementation
-- OPTIMIZED: 100% Nampower - libdebuff handles all cast events (SPELL_START/GO/FAILED)
-- Use multiple checks for target detection (target variable, istarget flag, or zoomed state)
local isTargetPlate = target or nameplate.istarget or (nameplate.health and nameplate.health.zoomed)
if C.nameplates["showcastbar"] == "1" and ( C.nameplates["targetcastbar"] == "0" or isTargetPlate ) then
if cfg.showcastbar and ( not cfg.targetcastbar or isTargetPlate ) then
local unitstr = nil
local targetGUID = nil
-- Get GUID for CastEvents lookup
if isTargetPlate and superwow_active then
-- Get target's GUID for event cache lookup
local _, guid = UnitExists("target")
targetGUID = guid
-- Get GUID for CastEvents lookup - use cached GUID when available
if isTargetPlate then
targetGUID = state and state.targetGuid
end
-- Use SuperWoW GUID for non-target plates
-- Use cached GUID for non-target plates
if superwow_active and not isTargetPlate then
unitstr = nameplate.parent:GetName(1)
unitstr = nameplate.cachedGuid
end
-- Check event-based cast cache first (use GUID)
local castInfo = (targetGUID and CastEvents[targetGUID]) or (unitstr and CastEvents[unitstr])
local castInfo = GetCastInfo(targetGUID) or (unitstr and GetCastInfo(unitstr))
if castInfo and castInfo.spellID then
-- Check if cast is still valid
@@ -1266,9 +1520,15 @@ pfUI:RegisterModule("nameplates", "vanilla", function ()
end
nameplate.castbar:SetValue(barValue)
nameplate.castbar.text:SetText(round(now - castInfo.startTime, 1))
-- Show remaining time (countdown), not elapsed time
local remaining = castInfo.endTime - now
if C.unitframes.castbardecimals == "1" then
nameplate.castbar.text:SetText(floor(remaining * 10) / 10)
else
nameplate.castbar.text:SetText(string.format("%.2f", remaining))
end
if C.nameplates.spellname == "1" then
if cfg.spellname then
nameplate.castbar.spell:SetText(castInfo.spellName)
else
nameplate.castbar.spell:SetText("")
@@ -1293,6 +1553,7 @@ pfUI:RegisterModule("nameplates", "vanilla", function ()
end
if not cast and not channel then
-- No cast data = hide castbar (fixes stuck castbar on FAIL for non-target plates)
nameplate.castbar:Hide()
else
local effect = cast or channel
@@ -1304,7 +1565,14 @@ pfUI:RegisterModule("nameplates", "vanilla", function ()
nameplate.castbar:SetMinMaxValues(0, duration/1000)
nameplate.castbar:SetValue(cur)
nameplate.castbar.text:SetText(round(cur,1))
-- Show remaining time (countdown), not elapsed time
local remaining = max - cur
if channel then remaining = cur end -- Channel already counts down
if C.unitframes.castbardecimals == "1" then
nameplate.castbar.text:SetText(floor(remaining * 10) / 10)
else
nameplate.castbar.text:SetText(string.format("%.2f", remaining))
end
if C.nameplates.spellname == "1" then
nameplate.castbar.spell:SetText(effect)
@@ -1349,6 +1617,9 @@ pfUI:RegisterModule("nameplates", "vanilla", function ()
nameplates:SetGameVariables()
nameplates.UpdateConfig = function()
-- Refresh config cache for all cfg.* values
CacheConfig()
-- update debuff filters
DebuffFilterPopulate()
@@ -1458,48 +1729,6 @@ pfUI:RegisterModule("nameplates", "vanilla", function ()
end
end
local hookOnUpdate = nameplates.OnUpdate
nameplates.OnUpdate = function(self)
-- initialize shortcut variables
local plate = (C.nameplates["overlap"] == "1" or C.nameplates["vertical_offset"] ~= "0") and this.nameplate or this
local clickable = C.nameplates["clickthrough"] ~= "1" and true or false
-- disable all click events
if not clickable then
this:EnableMouse(false)
this.nameplate:EnableMouse(false)
else
plate:EnableMouse(clickable)
end
if C.nameplates["overlap"] == "1" then
if this:GetWidth() > 1 then
-- set parent to 1 pixel to have them overlap each other
this:SetWidth(1)
this:SetHeight(1)
end
else
if not this.nameplate.dwidth then
-- cache initial sizing value for comparison
this.nameplate.dwidth = floor(this.nameplate:GetWidth() * UIParent:GetScale())
end
if floor(this:GetWidth()) ~= this.nameplate.dwidth then
-- align parent plate to the actual size
this:SetWidth(this.nameplate:GetWidth() * UIParent:GetScale())
this:SetHeight(this.nameplate:GetHeight() * UIParent:GetScale())
end
end
-- disable click events while spell is targeting
local mouseEnabled = this.nameplate:IsMouseEnabled()
if C.nameplates["clickthrough"] == "0" and C.nameplates["overlap"] == "1" and SpellIsTargeting() == mouseEnabled then
this.nameplate:EnableMouse(not mouseEnabled)
end
hookOnUpdate(self)
end
-- enable mouselook on rightbutton down
nameplates.mouselook = CreateFrame("Frame", nil, UIParent)
nameplates.mouselook.time = nil
+13 -291
View File
@@ -54,7 +54,15 @@ pfUI:RegisterModule("nampower", "vanilla", function ()
local queue = CreateFrame("Frame")
queue:RegisterEvent("SPELL_QUEUE_EVENT")
queue:RegisterEvent("PLAYER_LOGOUT")
queue:SetScript("OnEvent", function()
-- Handle shutdown to prevent crash 132
if event == "PLAYER_LOGOUT" then
this:UnregisterAllEvents()
this:SetScript("OnEvent", nil)
return
end
local eventCode = arg1
local spellId = arg2
@@ -76,189 +84,7 @@ pfUI:RegisterModule("nampower", "vanilla", function ()
end)
end
-- Enhanced Debuff Tracking using Nampower events
-- DEBUFF_ADDED_OTHER/DEBUFF_REMOVED_OTHER provide accurate debuff tracking with spellId
if libdebuff then
-- Storage for GUID-based debuff tracking
pfUI.nampower_debuffs = pfUI.nampower_debuffs or {}
local debuffdb = pfUI.nampower_debuffs
-- Get player GUID for tracking own debuffs
local playerGuid
local debuffTracker = CreateFrame("Frame")
debuffTracker:RegisterEvent("PLAYER_ENTERING_WORLD")
debuffTracker:RegisterEvent("DEBUFF_ADDED_OTHER")
debuffTracker:RegisterEvent("DEBUFF_REMOVED_OTHER")
debuffTracker:RegisterEvent("DEBUFF_ADDED_SELF")
debuffTracker:RegisterEvent("DEBUFF_REMOVED_SELF")
debuffTracker:SetScript("OnEvent", function()
if event == "PLAYER_ENTERING_WORLD" then
-- Cache player GUID
if UnitExists then
local _, guid = UnitExists("player")
playerGuid = guid
end
return
end
-- DEBUFF events: arg1=guid, arg2=slot, arg3=spellId, arg4=stackCount, arg5=auraLevel
local guid = arg1
local slot = arg2
local spellId = arg3
local stackCount = arg4
local auraLevel = arg5
if not guid or not spellId then return end
if event == "DEBUFF_ADDED_OTHER" or event == "DEBUFF_ADDED_SELF" then
-- Initialize storage for this GUID
if not debuffdb[guid] then debuffdb[guid] = {} end
-- Get spell info
local spellName, spellRank, texture
if SpellInfo then
spellName, spellRank, texture = SpellInfo(spellId)
end
if not spellName then
spellName, spellRank = SafeGetSpellNameAndRank(spellId)
end
if spellName then
-- Get duration from libdebuff's duration table or GetSpellRec
local duration = 0
if libdebuff.GetDuration then
duration = libdebuff:GetDuration(spellName, spellRank)
end
-- Try GetSpellRec for duration if libdebuff doesn't have it
if duration == 0 and GetSpellRec and spellId then
local success, spellRec = pcall(GetSpellRec, spellId)
if success and spellRec and spellRec.durationIndex and spellRec.durationIndex > 0 then
-- Duration index maps to spell duration - common values:
-- This is a rough approximation since we don't have the duration table
duration = 30 -- Default fallback
end
end
-- Store debuff data
debuffdb[guid][spellId] = {
spellId = spellId,
name = spellName,
rank = spellRank,
texture = texture,
stacks = stackCount or 1,
start = GetTime(),
duration = duration,
slot = slot,
auraLevel = auraLevel,
caster = (event == "DEBUFF_ADDED_SELF" or guid == playerGuid) and "player" or nil
}
-- Also update libdebuff's internal tracking if we have unit info
local unitName = UnitName and guid and UnitName(guid)
local unitLevel = UnitLevel and guid and UnitLevel(guid)
if unitName and duration > 0 then
libdebuff:AddEffect(unitName, unitLevel or 0, spellName, duration, "player")
end
end
elseif event == "DEBUFF_REMOVED_OTHER" or event == "DEBUFF_REMOVED_SELF" then
-- Remove debuff from tracking
if debuffdb[guid] and debuffdb[guid][spellId] then
debuffdb[guid][spellId] = nil
end
end
end)
-- Enhanced UnitDebuff function that uses Nampower data
-- This provides more accurate debuff information when available
local originalUnitDebuff = libdebuff.UnitDebuff
function libdebuff:UnitDebuffNampower(unit, id)
-- First try the original method
local effect, rank, texture, stacks, dtype, duration, timeleft, caster = originalUnitDebuff(self, unit, id)
-- If we have Nampower data for this unit, try to enhance it
if not UnitExists then return effect, rank, texture, stacks, dtype, duration, timeleft, caster end
local exists, guid = UnitExists(unit)
if not exists or not guid or not debuffdb[guid] then
return effect, rank, texture, stacks, dtype, duration, timeleft, caster
end
-- Find the debuff by slot
for spellId, data in pairs(debuffdb[guid]) do
if data.slot == id then
-- Use Nampower data for more accurate timing
if data.duration and data.duration > 0 and data.start then
duration = data.duration
timeleft = data.duration + data.start - GetTime()
if timeleft < 0 then timeleft = 0 end
caster = data.caster
stacks = data.stacks or stacks
end
break
end
end
return effect, rank, texture, stacks, dtype, duration, timeleft, caster
end
-- Expose enhanced function
pfUI.api.libdebuff_nampower = libdebuff.UnitDebuffNampower
end
-- Enhanced buff tracking using BUFF events
if C.unitframes.nampower_buffs == "1" then
pfUI.nampower_buffs = pfUI.nampower_buffs or {}
local buffdb = pfUI.nampower_buffs
local buffTracker = CreateFrame("Frame")
buffTracker:RegisterEvent("BUFF_ADDED_OTHER")
buffTracker:RegisterEvent("BUFF_REMOVED_OTHER")
buffTracker:RegisterEvent("BUFF_ADDED_SELF")
buffTracker:RegisterEvent("BUFF_REMOVED_SELF")
buffTracker:SetScript("OnEvent", function()
local guid = arg1
local slot = arg2
local spellId = arg3
local stackCount = arg4
local auraLevel = arg5
if not guid or not spellId then return end
if event == "BUFF_ADDED_OTHER" or event == "BUFF_ADDED_SELF" then
if not buffdb[guid] then buffdb[guid] = {} end
local spellName, spellRank, texture
if SpellInfo then
spellName, spellRank, texture = SpellInfo(spellId)
end
if not spellName then
spellName, spellRank = SafeGetSpellNameAndRank(spellId)
end
if spellName then
buffdb[guid][spellId] = {
spellId = spellId,
name = spellName,
rank = spellRank,
texture = texture,
stacks = stackCount or 1,
start = GetTime(),
slot = slot,
auraLevel = auraLevel
}
end
elseif event == "BUFF_REMOVED_OTHER" or event == "BUFF_REMOVED_SELF" then
if buffdb[guid] and buffdb[guid][spellId] then
buffdb[guid][spellId] = nil
end
end
end)
end
-- NOTE: Buff tracking removed - was dead code (data collected but never used for display)
-- Direct Aura Access API using GetUnitField
-- Much faster than tooltip scanning - reads aura arrays directly from unit fields
@@ -429,22 +255,8 @@ pfUI:RegisterModule("nampower", "vanilla", function ()
end
end
-- UNIT_DIED event handling
-- Can be used to clear tracking data or trigger effects on unit death
local deathTracker = CreateFrame("Frame")
deathTracker:RegisterEvent("UNIT_DIED")
deathTracker:SetScript("OnEvent", function()
local guid = arg1
if not guid then return end
-- Clean up debuff tracking for dead units
if pfUI.nampower_debuffs and pfUI.nampower_debuffs[guid] then
pfUI.nampower_debuffs[guid] = nil
end
if pfUI.nampower_buffs and pfUI.nampower_buffs[guid] then
pfUI.nampower_buffs[guid] = nil
end
end)
-- UNIT_DIED event handling - placeholder for future use
-- (Debuff/buff cleanup removed as tracking is now handled by libdebuff)
-- Trinket Management API
if GetTrinkets then
@@ -667,96 +479,6 @@ pfUI:RegisterModule("nampower", "vanilla", function ()
end
end
-- Enhanced Heal Prediction with AURA_CAST events
-- This helps libpredict detect HoT applications more accurately
if libpredict then
local auraCastFrame = CreateFrame("Frame")
auraCastFrame:RegisterEvent("AURA_CAST_ON_SELF")
auraCastFrame:RegisterEvent("AURA_CAST_ON_OTHER")
auraCastFrame:SetScript("OnEvent", function()
local casterGuid = arg1
local targetGuid = arg2
local spellId = arg3
if not spellId or not targetGuid then return end
-- Get spell name
local spellName
if SpellInfo then
spellName = SpellInfo(spellId)
end
if not spellName then
spellName = SafeGetSpellNameAndRank(spellId)
end
if not spellName then return end
-- Check if this is a HoT spell we care about
local hotSpells = {
["Rejuvenation"] = true,
["Renew"] = true,
["Regrowth"] = true,
["Verjüngung"] = true, -- German
["Erneuerung"] = true,
["Nachwachsen"] = true,
}
if hotSpells[spellName] then
-- Signal to libpredict that a HoT was applied
-- This can be used to update heal predictions
if pfUI.api.libpredict and pfUI.api.libpredict.OnHotApplied then
pfUI.api.libpredict:OnHotApplied(targetGuid, spellName, spellId)
end
end
end)
end
-- Swing Timer Integration
-- Track auto-attack swing timers for melee classes
local swingFrame = CreateFrame("Frame")
swingFrame.mainHand = { start = 0, speed = 0 }
swingFrame.offHand = { start = 0, speed = 0 }
swingFrame.ranged = { start = 0, speed = 0 }
swingFrame:RegisterEvent("PLAYER_ENTER_COMBAT")
swingFrame:RegisterEvent("PLAYER_LEAVE_COMBAT")
swingFrame:RegisterEvent("CHAT_MSG_COMBAT_SELF_HITS")
swingFrame:RegisterEvent("CHAT_MSG_COMBAT_SELF_MISSES")
swingFrame:SetScript("OnEvent", function()
if event == "PLAYER_ENTER_COMBAT" then
local mainSpeed, offSpeed = UnitAttackSpeed("player")
this.mainHand.speed = mainSpeed or 2
this.offHand.speed = offSpeed or 0
this.mainHand.start = GetTime()
if this.offHand.speed > 0 then
this.offHand.start = GetTime()
end
elseif event == "CHAT_MSG_COMBAT_SELF_HITS" or event == "CHAT_MSG_COMBAT_SELF_MISSES" then
-- Reset swing timer on hit/miss
local mainSpeed, offSpeed = UnitAttackSpeed("player")
this.mainHand.speed = mainSpeed or 2
this.mainHand.start = GetTime()
end
end)
pfUI.api.GetSwingTimers = function()
local now = GetTime()
local mainRemaining = swingFrame.mainHand.speed - (now - swingFrame.mainHand.start)
local offRemaining = swingFrame.offHand.speed > 0 and (swingFrame.offHand.speed - (now - swingFrame.offHand.start)) or 0
return {
mainHand = {
remaining = math.max(0, mainRemaining),
speed = swingFrame.mainHand.speed,
progress = swingFrame.mainHand.speed > 0 and (1 - math.max(0, mainRemaining) / swingFrame.mainHand.speed) or 0,
},
offHand = {
remaining = math.max(0, offRemaining),
speed = swingFrame.offHand.speed,
progress = swingFrame.offHand.speed > 0 and (1 - math.max(0, offRemaining) / swingFrame.offHand.speed) or 0,
},
}
end
-- NOTE: HoT Detection (AURA_CAST events) removed - OnHotApplied callback was never implemented in libpredict
-- NOTE: Swing Timer removed - GetSwingTimers() was never called anywhere
end)
+2 -2
View File
@@ -15,8 +15,8 @@ pfUI:RegisterModule("player", "vanilla:tbc", function ()
if pfUI.uf.player:GetScript("OnUpdate") then
local originalOnUpdate = pfUI.uf.player:GetScript("OnUpdate")
pfUI.uf.player:SetScript("OnUpdate", function()
if (this.tick or 0) > GetTime() then return end
this.tick = GetTime() + 0.1
if (this.throttleTick or 0) > GetTime() then return end
this.throttleTick = GetTime() + 0.1
originalOnUpdate()
end)
end
+21
View File
@@ -113,6 +113,27 @@ pfUI:RegisterModule("raid", "vanilla:tbc", function ()
end
end
-- Smart GUID-based updates: only refresh frames where unit changed
if pfUI.uf.guidTracker then
local tracker = pfUI.uf.guidTracker
for i = 1, maxraid do
local frame = pfUI.uf.raid[i]
if frame and frame.id and frame.id > 0 then
local unit = "raid" .. frame.id
local _, newGuid = UnitExists(unit)
local oldGuid = tracker.frameToGuid[frame]
if newGuid ~= oldGuid then
-- GUID changed = different player = need full update
tracker.frameToGuid[frame] = newGuid
frame.update_full = true
frame.update_aura = true -- Force aura refresh!
end
end
end
end
-- rebuild unitmap after frame IDs are assigned
if pfUI.uf.RebuildUnitmap then
pfUI.uf.RebuildUnitmap()
+319 -66
View File
@@ -161,15 +161,42 @@ pfUI:RegisterModule("superwow", "vanilla", function ()
end
-- Add support for druid mana bars
if SUPERWOW_VERSION and pfUI.uf and pfUI.uf.player and pfUI_config.unitframes.druidmanabar == "1" then
local parent = pfUI.uf.player.power.bar
local config = pfUI.uf.player.config
local mana = config.defcolor == "0" and config.manacolor or pfUI_config.unitframes.manacolor
local r, g, b, a = pfUI.api.strsplit(",", mana)
local rawborder, default_border = GetBorderSize("unitframes")
local _, class = UnitClass("player")
local width = config.pwidth ~= "-1" and config.pwidth or config.width
-- Uses Nampower's GetUnitField to get base mana when in shapeshift form
local hasNampower = (GetUnitField ~= nil)
-- Add support for player secondary power bar (shows base mana when in shapeshift form)
-- Works with SuperWoW OR Nampower - both extend UnitMana() to return base mana as second value
-- Only for Druids (only class that can shapeshift in Vanilla)
-- Controlled by "Show Druid Mana Bar" setting
local _, playerClass = UnitClass("player")
if hasNampower and pfUI.uf and pfUI.uf.player and playerClass == "DRUID" and pfUI_config.unitframes.druidmanabar == "1" then
local rawborder, default_border = GetBorderSize("unitframes")
local config = pfUI.uf.player.config
-- Create secondary mana bar below the power bar
local playerMana = CreateFrame("StatusBar", "pfPlayerSecondaryMana", pfUI.uf.player)
playerMana:SetFrameStrata(pfUI.uf.player:GetFrameStrata())
playerMana:SetFrameLevel(pfUI.uf.player:GetFrameLevel() + 5)
playerMana:SetStatusBarTexture(pfUI.media[config.pbartexture])
-- Mana color
local manacolor = config.defcolor == "0" and config.manacolor or C.unitframes.manacolor
local r, g, b, a = pfUI.api.strsplit(",", manacolor)
playerMana:SetStatusBarColor(r, g, b, a)
-- Use SAME size as normal power bar (pwidth/pheight from config)
local width = config.pwidth ~= "-1" and config.pwidth or config.width
local height = config.pheight
playerMana:SetWidth(width)
playerMana:SetHeight(height)
playerMana:SetPoint("TOPLEFT", pfUI.uf.player.power, "BOTTOMLEFT", 0, -2*default_border - (config.pspace or 0))
playerMana:SetPoint("TOPRIGHT", pfUI.uf.player.power, "BOTTOMRIGHT", 0, -2*default_border - (config.pspace or 0))
playerMana:Hide()
CreateBackdrop(playerMana)
CreateBackdropShadow(playerMana)
-- Text overlay - same font settings as power bar
local fontname = pfUI.font_unit
local fontsize = tonumber(pfUI_config.global.font_unit_size)
local fontstyle = pfUI_config.global.font_unit_style
@@ -180,75 +207,285 @@ pfUI:RegisterModule("superwow", "vanilla", function ()
fontstyle = config.customfont_style
end
local druidmana = CreateFrame("StatusBar", "pfDruidMana", UIParent)
druidmana:SetFrameStrata(parent:GetFrameStrata())
druidmana:SetFrameLevel(parent:GetFrameLevel() + 16)
druidmana:SetStatusBarTexture(pfUI.media[config.pbartexture])
druidmana:SetStatusBarColor(r, g, b, a)
druidmana:SetPoint("TOPLEFT", parent, "BOTTOMLEFT", 0, -2*default_border - config.pspace)
druidmana:SetPoint("TOPRIGHT", parent, "BOTTOMRIGHT", 0, -2*default_border - config.pspace)
druidmana:SetWidth(width)
druidmana:SetHeight(tonumber(pfUI_config.unitframes.druidmanaheight) or 6)
druidmana:EnableMouse(true)
druidmana:Hide()
playerMana.text = playerMana:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
playerMana.text:SetFontObject(GameFontWhite)
playerMana.text:SetFont(fontname, fontsize, fontstyle)
playerMana.text:SetPoint("CENTER", playerMana, "CENTER", 0, 0)
-- Set text color like normal power bar (mana = type 0)
local tr, tg, tb = 1, 1, 1
if config["powercolor"] == "1" then
tr = ManaBarColor[0].r
tg = ManaBarColor[0].g
tb = ManaBarColor[0].b
end
if C.unitframes.pastel == "1" then
tr, tg, tb = (tr + .75) * .5, (tg + .75) * .5, (tb + .75) * .5
end
playerMana.text:SetTextColor(tr, tg, tb, 1)
UpdateMovable(druidmana)
CreateBackdrop(druidmana)
CreateBackdropShadow(druidmana)
druidmana:RegisterEvent("UNIT_MANA")
druidmana:RegisterEvent("UNIT_MAXMANA")
druidmana:RegisterEvent("UNIT_DISPLAYPOWER")
druidmana:SetScript("OnEvent", function()
if UnitPowerType("player") == 0 then
this:Hide()
-- Update function
local function UpdatePlayerSecondaryMana()
local powerType = UnitPowerType("player")
-- Only show when NOT using mana (i.e., in Bear/Cat form)
if powerType == 0 then
playerMana:Hide()
return
end
local _, mana = UnitMana("player")
local _, max = UnitManaMax("player")
local perc = math.ceil(mana / max * 100)
if perc == 100 then
this.text:SetText(string.format("%s", Abbreviate(mana)))
-- Get base mana using Nampower's GetUnitField
local baseMana, baseMaxMana
if GetUnitField then
local _, guid = UnitExists("player")
if guid then
baseMana = GetUnitField(guid, "power1")
baseMaxMana = GetUnitField(guid, "maxPower1")
end
end
-- Round down power values (Nampower can return decimals, especially for rage)
if baseMana then baseMana = math.floor(baseMana) end
if baseMaxMana then baseMaxMana = math.floor(baseMaxMana) end
if type(baseMana) ~= "number" or type(baseMaxMana) ~= "number" or baseMaxMana == 0 then
playerMana:Hide()
return
end
-- Update bar
playerMana:SetMinMaxValues(0, baseMaxMana)
playerMana:SetValue(baseMana)
-- Update text based on power text config (uses same setting as normal power bar)
-- Check txtpowercenter first (centered text), then txtpowerright, then txtpowerleft
local textConfig = config.txtpowercenter or config.txtpowerright or config.txtpowerleft
if not textConfig or textConfig == "" or textConfig == "none" then
-- No text configured - hide text
playerMana.text:SetText("")
elseif textConfig == "power" then
playerMana.text:SetText(Abbreviate(baseMana))
elseif textConfig == "powermax" then
playerMana.text:SetText(Abbreviate(baseMaxMana))
elseif textConfig == "powerperc" then
local perc = math.ceil(baseMana / baseMaxMana * 100)
playerMana.text:SetText(perc)
elseif textConfig == "powermiss" then
local miss = math.ceil(baseMana - baseMaxMana)
playerMana.text:SetText(miss == 0 and "0" or Abbreviate(miss))
elseif textConfig == "powerdyn" then
local perc = math.ceil(baseMana / baseMaxMana * 100)
if perc == 100 then
playerMana.text:SetText(Abbreviate(baseMana))
else
playerMana.text:SetText(string.format("%s - %s%%", Abbreviate(baseMana), perc))
end
elseif textConfig == "powerminmax" then
playerMana.text:SetText(string.format("%s/%s", Abbreviate(baseMana), Abbreviate(baseMaxMana)))
else
this.text:SetText(string.format("%s - %s%%", Abbreviate(mana), perc))
-- Default: show dynamic (value + percentage if not full)
local perc = math.ceil(baseMana / baseMaxMana * 100)
if perc == 100 then
playerMana.text:SetText(Abbreviate(baseMana))
else
playerMana.text:SetText(string.format("%s - %s%%", Abbreviate(baseMana), perc))
end
end
playerMana:Show()
end
-- Register events
playerMana:RegisterEvent("UNIT_MANA")
playerMana:RegisterEvent("UNIT_MAXMANA")
playerMana:RegisterEvent("UNIT_DISPLAYPOWER")
playerMana:RegisterEvent("UPDATE_SHAPESHIFT_FORM")
playerMana:RegisterEvent("PLAYER_LOGOUT")
playerMana:SetScript("OnEvent", function()
-- Handle shutdown to prevent crash 132
if event == "PLAYER_LOGOUT" then
this:UnregisterAllEvents()
this:SetScript("OnEvent", nil)
return
end
if arg1 == nil or arg1 == "player" then
UpdatePlayerSecondaryMana()
end
this:SetMinMaxValues(0, max)
this:SetValue(mana)
this:Show()
end)
druidmana.text = druidmana:CreateFontString("Status", "OVERLAY", "GameFontNormalSmall")
druidmana.text:SetFontObject(GameFontWhite)
druidmana.text:SetFont(fontname, fontsize, fontstyle)
druidmana.text:SetPoint("RIGHT", -2*(default_border + config.txtpowerrightoffx), 0)
druidmana.text:SetPoint("LEFT", 2*(default_border + config.txtpowerrightoffx), 0)
druidmana.text:SetJustifyH("RIGHT")
-- Initial update
UpdatePlayerSecondaryMana()
if config["powercolor"] == "1" then
local r = ManaBarColor[0].r
local g = ManaBarColor[0].g
local b = ManaBarColor[0].b
if pfUI_config.unitframes.pastel == "1" then
druidmana.text:SetTextColor((r+.75)*.5, (g+.75)*.5, (b+.75)*.5, 1)
else
druidmana.text:SetTextColor(r, g, b, a)
end
end
if pfUI_config.unitframes.druidmanatext == "1" then
druidmana.text:Show()
else
druidmana.text:Hide()
end
if class ~= "DRUID" then
druidmana:UnregisterAllEvents()
druidmana:Hide()
end
-- Store reference
pfUI.uf.player.secondaryMana = playerMana
end
-- Add support for target secondary power bar (shows base mana when target is in shapeshift form)
-- Works with SuperWoW OR Nampower - both extend UnitMana() to return base mana as second value
-- Controlled by "Show Druid Mana Bar" setting - available for ALL classes
if hasNampower and pfUI.uf and pfUI.uf.target and pfUI_config.unitframes.druidmanabar == "1" then
local rawborder, default_border = GetBorderSize("unitframes")
local config = pfUI.uf.target.config
-- Create secondary mana bar below the power bar
local targetMana = CreateFrame("StatusBar", "pfTargetSecondaryMana", pfUI.uf.target)
targetMana:SetFrameStrata(pfUI.uf.target:GetFrameStrata())
targetMana:SetFrameLevel(pfUI.uf.target:GetFrameLevel() + 5)
targetMana:SetStatusBarTexture(pfUI.media[config.pbartexture])
-- Mana color
local manacolor = config.defcolor == "0" and config.manacolor or C.unitframes.manacolor
local r, g, b, a = pfUI.api.strsplit(",", manacolor)
targetMana:SetStatusBarColor(r, g, b, a)
-- Use SAME size as normal power bar (pwidth/pheight from config)
local width = config.pwidth ~= "-1" and config.pwidth or config.width
local height = config.pheight -- Same height as power bar!
targetMana:SetWidth(width)
targetMana:SetHeight(height)
targetMana:SetPoint("TOPLEFT", pfUI.uf.target.power, "BOTTOMLEFT", 0, -2*default_border - (config.pspace or 0))
targetMana:SetPoint("TOPRIGHT", pfUI.uf.target.power, "BOTTOMRIGHT", 0, -2*default_border - (config.pspace or 0))
targetMana:Hide()
CreateBackdrop(targetMana)
CreateBackdropShadow(targetMana)
-- Text overlay - same font settings as power bar
local fontname = pfUI.font_unit
local fontsize = tonumber(pfUI_config.global.font_unit_size)
local fontstyle = pfUI_config.global.font_unit_style
if config.customfont == "1" then
fontname = pfUI.media[config.customfont_name]
fontsize = tonumber(config.customfont_size)
fontstyle = config.customfont_style
end
targetMana.text = targetMana:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
targetMana.text:SetFontObject(GameFontWhite)
targetMana.text:SetFont(fontname, fontsize, fontstyle)
targetMana.text:SetPoint("CENTER", targetMana, "CENTER", 0, 0)
-- Set text color like normal power bar (mana = type 0)
-- Uses powercolor setting and pastel effect
local function UpdateTextColor()
local tr, tg, tb = 1, 1, 1
if config["powercolor"] == "1" then
tr = ManaBarColor[0].r
tg = ManaBarColor[0].g
tb = ManaBarColor[0].b
end
if C.unitframes.pastel == "1" then
tr, tg, tb = (tr + .75) * .5, (tg + .75) * .5, (tb + .75) * .5
end
targetMana.text:SetTextColor(tr, tg, tb, 1)
end
UpdateTextColor()
-- Update function
local function UpdateTargetSecondaryMana()
if not UnitExists("target") then
targetMana:Hide()
return
end
local powerType = UnitPowerType("target")
-- Only show if target is NOT using mana (i.e., in shapeshift form with energy/rage)
if powerType == 0 then
-- Target is using mana as primary power - no need for secondary bar
targetMana:Hide()
return
end
-- Get base mana using Nampower's GetUnitField
local baseMana, baseMaxMana
if GetUnitField then
local _, guid = UnitExists("target")
if guid then
baseMana = GetUnitField(guid, "power1")
baseMaxMana = GetUnitField(guid, "maxPower1")
end
end
-- Round down power values (Nampower can return decimals, especially for rage)
if baseMana then baseMana = math.floor(baseMana) end
if baseMaxMana then baseMaxMana = math.floor(baseMaxMana) end
-- Check if we got valid mana values
if type(baseMana) ~= "number" or type(baseMaxMana) ~= "number" or baseMaxMana == 0 then
targetMana:Hide()
return
end
-- Update bar
targetMana:SetMinMaxValues(0, baseMaxMana)
targetMana:SetValue(baseMana)
-- Update text based on power text config (uses same setting as normal power bar)
local textConfig = config.txtpowercenter or config.txtpowerright or config.txtpowerleft
if not textConfig or textConfig == "" or textConfig == "none" then
targetMana.text:SetText("")
elseif textConfig == "power" then
targetMana.text:SetText(Abbreviate(baseMana))
elseif textConfig == "powermax" then
targetMana.text:SetText(Abbreviate(baseMaxMana))
elseif textConfig == "powerperc" then
local perc = math.ceil(baseMana / baseMaxMana * 100)
targetMana.text:SetText(perc)
elseif textConfig == "powermiss" then
local miss = math.ceil(baseMana - baseMaxMana)
targetMana.text:SetText(miss == 0 and "0" or Abbreviate(miss))
elseif textConfig == "powerdyn" then
local perc = math.ceil(baseMana / baseMaxMana * 100)
if perc == 100 then
targetMana.text:SetText(Abbreviate(baseMana))
else
targetMana.text:SetText(string.format("%s - %s%%", Abbreviate(baseMana), perc))
end
elseif textConfig == "powerminmax" then
targetMana.text:SetText(string.format("%s/%s", Abbreviate(baseMana), Abbreviate(baseMaxMana)))
else
local perc = math.ceil(baseMana / baseMaxMana * 100)
if perc == 100 then
targetMana.text:SetText(Abbreviate(baseMana))
else
targetMana.text:SetText(string.format("%s - %s%%", Abbreviate(baseMana), perc))
end
end
targetMana:Show()
end
-- Register events
targetMana:RegisterEvent("PLAYER_TARGET_CHANGED")
targetMana:RegisterEvent("UNIT_MANA")
targetMana:RegisterEvent("UNIT_MAXMANA")
targetMana:RegisterEvent("UNIT_DISPLAYPOWER")
targetMana:RegisterEvent("PLAYER_LOGOUT")
targetMana:SetScript("OnEvent", function()
-- Handle shutdown to prevent crash 132
if event == "PLAYER_LOGOUT" then
this:UnregisterAllEvents()
this:SetScript("OnEvent", nil)
return
end
if event == "PLAYER_TARGET_CHANGED" then
UpdateTargetSecondaryMana()
elseif arg1 == "target" then
UpdateTargetSecondaryMana()
end
end)
-- Store reference
pfUI.uf.target.secondaryMana = targetMana
end
-- Add support for guid based focus frame
if SUPERWOW_VERSION and pfUI.uf and pfUI.uf.focus then
local focus = function(unitstr)
@@ -312,8 +549,16 @@ pfUI:RegisterModule("superwow", "vanilla", function ()
trackFrame:RegisterEvent("PARTY_MEMBERS_CHANGED")
trackFrame:RegisterEvent("RAID_ROSTER_UPDATE")
trackFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
trackFrame:RegisterEvent("PLAYER_LOGOUT")
trackFrame:SetScript("OnEvent", function()
-- Handle shutdown to prevent crash 132
if event == "PLAYER_LOGOUT" then
this:UnregisterAllEvents()
this:SetScript("OnEvent", nil)
return
end
-- Track party members
for i = 1, 4 do
local unit = "party" .. i
@@ -483,7 +728,15 @@ pfUI:RegisterModule("superwow", "vanilla", function ()
supercast:RegisterEvent("PLAYER_ENTERING_WORLD")
supercast:RegisterEvent("UNIT_CASTEVENT")
supercast:RegisterEvent("PLAYER_LOGOUT")
supercast:SetScript("OnEvent", function()
-- Handle shutdown to prevent crash 132
if event == "PLAYER_LOGOUT" then
this:UnregisterAllEvents()
this:SetScript("OnEvent", nil)
return
end
if event == "PLAYER_ENTERING_WORLD" then
-- Cache player GUID
if UnitExists then
+25
View File
@@ -90,7 +90,24 @@ pfUI:RegisterModule("unitxp", "vanilla", function ()
-- Also try on PLAYER_ENTERING_WORLD in case target frame wasn't ready
local initFrame = CreateFrame("Frame")
initFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
initFrame:RegisterEvent("PLAYER_LOGOUT")
initFrame:SetScript("OnEvent", function()
-- Handle shutdown to prevent crash 132
if event == "PLAYER_LOGOUT" then
this:UnregisterAllEvents()
this:SetScript("OnEvent", nil)
-- Stop indicator OnUpdate scripts
if pfUI.uf and pfUI.uf.target then
if pfUI.uf.target.behindIndicator then
pfUI.uf.target.behindIndicator:SetScript("OnUpdate", nil)
end
if pfUI.uf.target.losIndicator then
pfUI.uf.target.losIndicator:SetScript("OnUpdate", nil)
end
end
return
end
CreateTargetIndicators()
this:UnregisterAllEvents()
end)
@@ -102,8 +119,16 @@ pfUI:RegisterModule("unitxp", "vanilla", function ()
notifyFrame:RegisterEvent("CHAT_MSG_BN_WHISPER")
notifyFrame:RegisterEvent("READY_CHECK")
notifyFrame:RegisterEvent("RAID_INSTANCE_WELCOME")
notifyFrame:RegisterEvent("PLAYER_LOGOUT")
notifyFrame:SetScript("OnEvent", function()
-- Handle shutdown to prevent crash 132
if event == "PLAYER_LOGOUT" then
this:UnregisterAllEvents()
this:SetScript("OnEvent", nil)
return
end
pcall(UnitXP, "notify", "taskbarIcon")
pcall(UnitXP, "notify", "systemSound")
end)