diff --git a/api/config.lua b/api/config.lua index fc8a492d..c570593a 100644 --- a/api/config.lua +++ b/api/config.lua @@ -817,6 +817,7 @@ function pfUI:LoadConfig() pfUI:UpdateConfig("nameplates", nil, "selfdebuff", "0") pfUI:UpdateConfig("nameplates", nil, "showdebuffs_hostile", "1") pfUI:UpdateConfig("nameplates", nil, "showdebuffs_friendly", "0") + pfUI:UpdateConfig("nameplates", nil, "owndebuffs", "0") pfUI:UpdateConfig("nameplates", nil, "guessdebuffs", "1") pfUI:UpdateConfig("nameplates", nil, "clickthrough", "0") pfUI:UpdateConfig("nameplates", nil, "rightclick", "1") diff --git a/libs/libdebuff.lua b/libs/libdebuff.lua index 5428e12c..576b50a0 100644 --- a/libs/libdebuff.lua +++ b/libs/libdebuff.lua @@ -1144,12 +1144,8 @@ if hasNampower then pfTarget.update_aura = true end end - - if pfUI.nameplates and pfUI.nameplates.OnAuraUpdate then - pfUI.nameplates:OnAuraUpdate(guid) - end end - + carnageState = nil this:Hide() end) @@ -1635,11 +1631,6 @@ if hasNampower then end - -- Notify nameplates - if pfUI.nameplates and pfUI.nameplates.OnAuraUpdate then - pfUI.nameplates:OnAuraUpdate(targetGuid) - end - -- Notify unitframes of debuff updates (UNIT_AURA doesn't fire on refreshes!) -- Check player if UnitGUID("player") then @@ -1857,11 +1848,7 @@ if hasNampower then -- Cleanup expired timers CleanupExpiredTimers(guid) - - -- Notify nameplates - if pfUI.nameplates and pfUI.nameplates.OnAuraUpdate then - pfUI.nameplates:OnAuraUpdate(guid) - end + if pfUI.libdebuff_debuff_added_other_hooks then for _, fn in pairs(pfUI.libdebuff_debuff_added_other_hooks) do fn(arg1, arg2, arg3, arg4) @@ -1950,11 +1937,7 @@ if hasNampower then -- Cleanup expired timers CleanupExpiredTimers(guid) - - -- Notify nameplates - if pfUI.nameplates and pfUI.nameplates.OnAuraUpdate then - pfUI.nameplates:OnAuraUpdate(guid) - end + if pfUI.libdebuff_debuff_removed_other_hooks then for _, fn in pairs(pfUI.libdebuff_debuff_removed_other_hooks) do fn(arg1, arg2, arg3, arg4) diff --git a/modules/gui.lua b/modules/gui.lua index f722e2e3..a8021d4d 100644 --- a/modules/gui.lua +++ b/modules/gui.lua @@ -2895,6 +2895,7 @@ pfUI:RegisterModule("gui", function () CreateConfig(U["nameplates"], T["Enable Debuffs"], C.nameplates, "showdebuffs", "checkbox") CreateConfig(U["nameplates"], T["Show Debuffs on Hostile"], C.nameplates, "showdebuffs_hostile", "checkbox") CreateConfig(U["nameplates"], T["Show Debuffs on Friendly"], C.nameplates, "showdebuffs_friendly", "checkbox") + CreateConfig(U["nameplates"], T["Only Show Your Debuffs"], C.nameplates, "owndebuffs", "checkbox") CreateConfig(U["nameplates"], T["Debuff Position"], C.nameplates.debuffs, "position", "dropdown", pfUI.gui.dropdowns.debuffposition) CreateConfig(U["nameplates"], T["Debuff Icon Offset"], C.nameplates, "debuffoffset") CreateConfig(U["nameplates"], T["Debuff Icon Size"], C.nameplates, "debuffsize") diff --git a/modules/nameplates.lua b/modules/nameplates.lua index 31c40554..d292b4c2 100644 --- a/modules/nameplates.lua +++ b/modules/nameplates.lua @@ -78,36 +78,6 @@ pfUI:RegisterModule("nameplates", function () local threatMemory = {} -- guid -> true if mob had player targeted local debuffSeen = {} -- reusable table for debuff tracking (avoid GC churn) - -- PERF: Module-level IterDebuffs callback to avoid closure allocation per call - local _iterDebuffCount = 0 - local function iterDebuffCallback(auraSlot, spellId, effect, texture, stacks, dtype, duration, timeleft) - if not texture or string.find(texture, "QuestionMark") then return end - _iterDebuffCount = _iterDebuffCount + 1 - if _iterDebuffCount > 16 then return end - local b = debuffDisplayBuf[_iterDebuffCount] - b.effect, b.texture, b.stacks, b.dtype, b.duration, b.timeleft = effect, texture, stacks, dtype, duration, timeleft - end - - -- PERF: Module-level IterDebuffs callback for PlateCacheDebuffs — same - -- rationale; PlateCacheDebuffs runs per visible plate per throttled tick. - local _pcdSelf, _pcdNow, _pcdId - local function iterPlateCacheDebuffsCallback(auraSlot, spellId, effect, texture, stacks, dtype, duration, timeleft) - if not effect or not texture then return end - _pcdId = _pcdId + 1 - if _pcdId > 16 then return end - local stop = (timeleft and timeleft > 0) and (_pcdNow + timeleft) or nil - local start = stop and (stop - (duration or 0)) or _pcdNow - local cache = _pcdSelf.debuffcache[_pcdId] or {} - cache.effect = effect - cache.texture = texture - cache.stacks = stacks - cache.duration = duration or 0 - cache.start = start - cache.stop = stop - cache.empty = nil - _pcdSelf.debuffcache[_pcdId] = cache - end - -- PERF: visiblePlateCount maintained event-driven (NAME_PLATE_UNIT_ADDED/_REMOVED) local visiblePlateCount = 0 @@ -134,6 +104,7 @@ pfUI:RegisterModule("nameplates", function () cfg.showdebuffs = C.nameplates["showdebuffs"] == "1" cfg.showdebuffs_hostile = C.nameplates["showdebuffs_hostile"] == "1" cfg.showdebuffs_friendly = C.nameplates["showdebuffs_friendly"] == "1" + cfg.owndebuffs = C.nameplates["owndebuffs"] == "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 @@ -413,27 +384,29 @@ pfUI:RegisterModule("nameplates", function () end end - -- Use IterDebuffs if Nampower available, else fall back to slot loop - if unitstr and libdebuff.IterDebuffs and UnitGUID then - _pcdSelf, _pcdNow, _pcdId = self, now, 0 - libdebuff:IterDebuffs(unitstr, iterPlateCacheDebuffsCallback) - else - for id = 1, 16 do - local effect, _, texture, stacks, _, duration, timeleft - effect, _, texture, stacks, _, duration, timeleft = libdebuff:UnitDebuff(unitstr, id) - if effect and timeleft and timeleft > 0 then - local start = now - ( (duration or 0) - ( timeleft or 0) ) - local stop = now + timeleft - self.debuffcache[id] = self.debuffcache[id] or {} - self.debuffcache[id].effect = effect - self.debuffcache[id].texture = texture - self.debuffcache[id].stacks = stacks - self.debuffcache[id].duration = duration or 0 - self.debuffcache[id].start = start - self.debuffcache[id].stop = stop - self.debuffcache[id].empty = nil - end - end + -- Pull debuffs straight from C_UnitAuras. The HARMFUL filter restricts to + -- the debuff range; PLAYER (added when cfg.owndebuffs is on) further + -- restricts to auras whose cached caster GUID matches the local player. + -- expirationTime comes from ClassicAPI's Aura::Source cache (SMSG_SPELL_GO + -- observation) — best-effort, so auras applied before login won't carry + -- timing and we treat them as durationless. + local filter = cfg.owndebuffs and "HARMFUL|PLAYER" or "HARMFUL" + local auras = unitstr and C_UnitAuras.GetUnitAuras(unitstr, filter) or {} + for id, aura in ipairs(auras) do + if id > 16 then break end + local duration = aura.duration or 0 + local stop = (aura.expirationTime and aura.expirationTime > 0) and aura.expirationTime or nil + local start = stop and (stop - duration) or now + local cache = self.debuffcache[id] or {} + cache.effect = aura.name + cache.texture = aura.icon + cache.stacks = aura.applications + cache.dtype = aura.dispelName + cache.duration = duration + cache.start = start + cache.stop = stop + cache.empty = nil + self.debuffcache[id] = cache end self.verify = verify @@ -549,32 +522,9 @@ nameplates:RegisterEvent("NAME_PLATE_UNIT_ADDED") nameplates:RegisterEvent("NAME_PLATE_UNIT_REMOVED") if GetUnitField then nameplates:RegisterEvent("UNIT_FLAGS_GUID") + nameplates:RegisterEvent("UNIT_AURA_GUID") end - -- 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 Nampower events - local plate = C_NamePlate.GetNamePlateForGUID(guid) - if plate and plate.nameplate then - -- Mark nameplate for aura update in next OnUpdate cycle - plate.nameplate.auraUpdate = true - end - end - - -- Hook into libdebuff timer signal (fires when slotTimers written or cleared) - pfUI.libdebuff_on_unit_updated = pfUI.libdebuff_on_unit_updated or {} - table.insert(pfUI.libdebuff_on_unit_updated, function(guid) - local plate = C_NamePlate.GetNamePlateForGUID(guid) - if plate and plate.nameplate then - plate.nameplate.auraUpdate = true - end - end) - nameplates:SetScript("OnEvent", function() -- Stop event handling during logout to prevent crash 132 if event == "PLAYER_LOGOUT" then @@ -683,6 +633,17 @@ end plate.nameplate.eventcache = true end + elseif event == "UNIT_AURA_GUID" then + -- Nampower: fires when a unit's aura set changes (add/remove/modify). + -- arg1 = guid. Flag the matching plate so OnUpdate triggers a fresh + -- C_UnitAuras read on the next tick instead of waiting on the 0.5s + -- throttle — covers expirations, dispels, refreshes, and stack changes + -- in one event. + local plate = C_NamePlate.GetNamePlateForGUID(arg1) + if plate and plate.nameplate then + plate.nameplate.auraUpdate = true + end + elseif event == "PLAYER_TARGET_CHANGED" then -- Flag target plate for update via GUID registry local targetGuid = UnitGUID("target") @@ -1291,34 +1252,27 @@ end plate:CacheDebuffs(unitstr, verify) end - -- update all debuff icons - -- Use IterDebuffs when Nampower available to avoid blind 16-slot loop - -- debuffDisplayBuf is a module-level reusable buffer (no GC churn) + -- Pull debuffs from C_UnitAuras (HARMFUL range). owndebuffs adds the + -- PLAYER filter token so only auras whose caster GUID matches the local + -- player come through. debuffDisplayBuf is a module-level reusable + -- buffer (no GC churn). local debuffCount = 0 - for i = 1, 16 do debuffDisplayBuf[i].effect = nil end -- clear previous - if unitstr and libdebuff and libdebuff.IterDebuffs and UnitGUID then - - _iterDebuffCount = 0 - libdebuff:IterDebuffs(unitstr, iterDebuffCallback) - debuffCount = _iterDebuffCount - elseif unitstr and libdebuff then - for i = 1, 16 do - local effect, rank, texture, stacks, dtype, duration, timeleft - effect, rank, texture, stacks, dtype, duration, timeleft = libdebuff:UnitDebuff(unitstr, i) - if effect then - debuffCount = debuffCount + 1 - local b = debuffDisplayBuf[debuffCount] - b.effect, b.texture, b.stacks, b.dtype, b.duration, b.timeleft = effect, texture, stacks, dtype, duration, timeleft - end - end - elseif plate.verify == verify then - for i = 1, 16 do - local effect, rank, texture, stacks, dtype, duration, timeleft = plate:UnitDebuff(i) - if effect then - debuffCount = debuffCount + 1 - local b = debuffDisplayBuf[debuffCount] - b.effect, b.texture, b.stacks, b.dtype, b.duration, b.timeleft = effect, texture, stacks, dtype, duration, timeleft - end + 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 + 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 end end for i = 1, 16 do @@ -1346,8 +1300,15 @@ end end if duration and timeleft and cfg.debufftimers then + -- C_UnitAuras returns the Spell.dbc base duration, which talents + -- can extend past — Shadow Affinity bumps SW:P from 18s to 24s, + -- so a fresh cast has timeleft > duration and `now + timeleft - + -- duration` lands in the future, which CooldownFrame_SetTimer + -- treats as "not yet started" (no swirl). Widen to whichever is + -- larger so start <= now. + local effDuration = duration > timeleft and duration or timeleft plate.cdCache = plate.cdCache or {} - local newStart = GetTime() + timeleft - duration + local newStart = GetTime() + timeleft - effDuration local slotCache = plate.cdCache[index] local cachedStart = slotCache and slotCache.effect == effect and slotCache.start local cd = plate.debuffs[index].cd @@ -1361,7 +1322,7 @@ end cd.cachedText = cfg.debufftext cd.configCached = true end - CooldownFrame_SetTimer(cd, newStart, duration, 1) + CooldownFrame_SetTimer(cd, newStart, effDuration, 1) plate.cdCache[index] = plate.cdCache[index] or {} plate.cdCache[index].effect = effect plate.cdCache[index].start = newStart @@ -1588,17 +1549,6 @@ end update = true end - -- PERF: scan for debuff timeouts using indexed access instead of pairs() - if nameplate.debuffcache 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 - end - end - -- use timer based updates if not nameplate.tick or nameplate.tick < now then update = true diff --git a/pfUI.lua b/pfUI.lua index 22f9e743..5854990c 100644 --- a/pfUI.lua +++ b/pfUI.lua @@ -27,7 +27,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 = 10403 -- (X*10000 + Y*100 + Z) + local PFUI_CLASSIC_API_MIN = 10501 -- (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"