update for pfui buff tracking and nampower buff tracking fallback

This commit is contained in:
Jrc13245
2026-02-17 19:19:07 -05:00
parent 37afbd047c
commit ec43a636d6
6 changed files with 366 additions and 28 deletions
+130 -11
View File
@@ -877,9 +877,9 @@ CleveRoids.AuraCapStatus = {
}
-- Overflow buff tracking: buffs applied while buff-capped (v2.34+)
-- The server maintains up to 48 buff slots (32 visible + 16 invisible overflow).
-- Buffs in slots 33-48 are mechanically active but have no client aura slot,
-- so GetPlayerBuff/GetPlayerAuraDuration can't see them.
-- GetUnitField "aura" returns 48 slots: [1-32] = buff slots, [33-48] = debuff slots.
-- Beyond these 48 client slots, the server can hold additional overflow buffs with
-- no aura slot at all -- invisible to GetPlayerBuff and GetPlayerAuraDuration.
-- Populated from AURA_CAST_ON_SELF when auraCapStatus indicates buff bar full.
-- Format: [spellId] = { timestamp = GetTime(), durationSec = durationMs/1000 }
CleveRoids.OverflowBuffs = {}
@@ -1036,12 +1036,10 @@ local function OnAuraCastSelf(spellId, casterGuid, targetGuid, effect, effectAur
-- Check debuff bar full (bit 2)
CleveRoids.AuraCapStatus.playerDebuffCapped = HasHitFlag(auraCapStatus, AURA_CAP_DEBUFF_FULL)
-- Track overflow buffs: if buff bar is full, this aura may land in server slots 33-48
-- (no client aura slot). Store spellId + duration so /cancelaura and [mybuff] can find it.
-- But first verify it's actually a buff — debuffs applied while buff-capped go into
-- debuff slots (32-47), not buff overflow. Player debuffs never overflow into buff slots.
if buffCapped and spellId and spellId > 0 and effectAuraName and effectAuraName > 0 then
-- Check debuff slots (32-47) — if the spellId is there, it's a debuff, not overflow
-- Determine if this aura is a buff (not debuff) — debuffs land in GetPlayerAuraDuration
-- slots 32-47, so checking those slots identifies them regardless of cap status.
local isBuffNotDebuff = false
if spellId and spellId > 0 and effectAuraName and effectAuraName > 0 then
local isDebuff = false
if _G.GetPlayerAuraDuration then
for slot = 32, 47 do
@@ -1052,8 +1050,11 @@ local function OnAuraCastSelf(spellId, casterGuid, targetGuid, effect, effectAur
end
end
end
isBuffNotDebuff = not isDebuff
if not isDebuff then
-- Track overflow buffs: if buff bar is full, this buff has no client aura slot.
-- Store spellId + duration so /cancelaura and [mybuff] can find it.
if buffCapped and isBuffNotDebuff then
local entry = CleveRoids.OverflowBuffs[spellId]
if not entry then
entry = {}
@@ -1062,7 +1063,9 @@ local function OnAuraCastSelf(spellId, casterGuid, targetGuid, effect, effectAur
entry.timestamp = now
entry.durationSec = durationMs and (durationMs / 1000) or 0
end
elseif not buffCapped and next(CleveRoids.OverflowBuffs) then
end
if not buffCapped and next(CleveRoids.OverflowBuffs) then
-- No longer buff-capped: some overflow buffs may have gotten real slots.
-- Only remove entries that now appear in a visible aura slot.
-- The server does NOT auto-migrate overflow buffs into freed slots,
@@ -1091,6 +1094,33 @@ local function OnAuraCastSelf(spellId, casterGuid, targetGuid, effect, effectAur
end
end
end
-- NEW: Populate ownBuffCasts and allBuffAuras for all player buffs (not just overflow)
-- Use the isBuffNotDebuff result determined above (avoids redundant slot scanning)
local lib = CleveRoids.libdebuff
if isBuffNotDebuff and spellId and durationMs and durationMs > 0 and lib and not lib.hasPfUIEnhanced then
local spellName = SpellInfo and SpellInfo(spellId)
if spellName then
local _, playerGuidRaw = UnitExists("player")
local playerGuid = playerGuidRaw and CleveRoids.NormalizeGUID(playerGuidRaw)
if playerGuid then
lib.ownBuffCasts[playerGuid] = lib.ownBuffCasts[playerGuid] or {}
lib.ownBuffCasts[playerGuid][spellName] = {
startTime = now,
duration = durationMs / 1000,
spellId = spellId,
casterGuid = casterGuid,
}
lib.allBuffAuras[playerGuid] = lib.allBuffAuras[playerGuid] or {}
lib.allBuffAuras[playerGuid][spellName] = lib.allBuffAuras[playerGuid][spellName] or {}
lib.allBuffAuras[playerGuid][spellName][casterGuid or "unknown"] = {
startTime = now,
duration = durationMs / 1000,
rank = 0,
}
end
end
end
end
local function OnAuraCastOther(spellId, casterGuid, targetGuid, effect, effectAuraName,
@@ -1126,6 +1156,25 @@ local function OnAuraCastOther(spellId, casterGuid, targetGuid, effect, effectAu
spellName, spellId, string.sub(tostring(targetGuid), 1, 16), durationMs / 1000
))
end
-- NEW: Store in pendingBuffCasts for BUFF_ADDED_OTHER to confirm as buff
-- (AURA_CAST_ON_OTHER fires for both buffs and debuffs; BUFF_ADDED_OTHER confirms buff)
local lib = CleveRoids.libdebuff
if lib and not lib.hasPfUIEnhanced then
local spellNameForPending = SpellInfo and SpellInfo(spellId)
if spellNameForPending then
local normTargetGuid = CleveRoids.NormalizeGUID(targetGuid)
if normTargetGuid then
lib.pendingBuffCasts[normTargetGuid] = lib.pendingBuffCasts[normTargetGuid] or {}
lib.pendingBuffCasts[normTargetGuid][spellId] = {
casterGuid = CleveRoids.NormalizeGUID(casterGuid),
duration = durationMs / 1000,
spellName = spellNameForPending,
time = now,
}
end
end
end
end
-- Store cap status for this target GUID (if available)
@@ -3464,6 +3513,36 @@ function CleveRoids.ValidateAura(unit, args, isbuff)
end
end
-- allBuffAuras fallback for player buff timing: when slow path found the buff but
-- returned no remaining time, check lib.allBuffAuras for cached AURA_CAST timing.
if found and remaining == nil and isPlayer and isbuff and searchName then
local lib = type(CleveRoids.libdebuff) == "table" and CleveRoids.libdebuff or nil
if lib and lib.allBuffAuras then
local _, playerGuidRaw = UnitExists("player")
local playerGuid = CleveRoids.NormalizeGUID(playerGuidRaw)
if playerGuid and lib.allBuffAuras[playerGuid] then
-- Try exact name match first
local casters = lib.allBuffAuras[playerGuid][args.name]
-- Try lowercase match if exact didn't work
if not casters then
for bName, c in pairs(lib.allBuffAuras[playerGuid]) do
if _string_lower(bName) == searchName then
casters = c
break
end
end
end
if casters then
for _, cData in pairs(casters) do
local elapsed = GetTime() - (cData.startTime or 0)
remaining = cData.duration > 0 and (cData.duration - elapsed) or -1
break
end
end
end
end
end
-- Non-player overflow fallback: buff exists in server slots 33-48 (no client slot)
-- AllCasterAuraTracking already has data from AURA_CAST_ON_OTHER for all aura applications.
-- If the normal scan didn't find the buff, check there for presence + duration.
@@ -3517,6 +3596,46 @@ function CleveRoids.ValidateAura(unit, args, isbuff)
-- (showing ~1000s instead of actual remaining time). Skip it for buff time checks
-- and rely on all-caster tracking from AURA_CAST events instead.
-- Fast path: Check lib.allBuffAuras (spellName-indexed, O(1) lookup)
-- More efficient than FindAllCasterAuraByName which does ID→name translation
if nonPlayerAuraTimeRemaining == nil and isbuff then
local lib = type(CleveRoids.libdebuff) == "table" and CleveRoids.libdebuff or nil
if lib and lib.allBuffAuras then
local _, targetGuid = UnitExists(unit)
targetGuid = targetGuid and CleveRoids.NormalizeGUID(targetGuid)
if targetGuid then
local buffEntries = lib.allBuffAuras[targetGuid]
if buffEntries then
-- Try exact name match first
local casters = buffEntries[args.name]
-- Try lowercase match if exact didn't work
if not casters and searchName then
for bName, c in pairs(buffEntries) do
if _string_lower(bName) == searchName then
casters = c
break
end
end
end
if casters then
for _, cData in pairs(casters) do
local elapsed = GetTime() - (cData.startTime or 0)
local rem = cData.duration > 0 and (cData.duration - elapsed) or -1
if rem == nil or rem > 0 or cData.duration <= 0 then
nonPlayerAuraTimeRemaining = rem
if not found then
found = true
stacks = 0
end
break
end
end
end
end
end
end
end
-- Second try: All-caster tracking from AURA_CAST events (works for any caster)
-- Only use if libdebuff didn't find it (libdebuff has more accurate timing for player casts)
if nonPlayerAuraTimeRemaining == nil then
+3 -3
View File
@@ -2370,7 +2370,7 @@ function CleveRoids.DoWithConditionals(msg, hook, fixEmptyTargetFunc, targetBefo
conditionals.target = origTarget
return false
end
-- Set target to the found GUID for soft-casting via SuperWoW
-- Set target to the found GUID for soft-casting via SuperWoW or Nampower v2.37+
conditionals.target = scanResult
-- Don't need to retarget since we're using GUID directly
needRetarget = false
@@ -2402,7 +2402,7 @@ function CleveRoids.DoWithConditionals(msg, hook, fixEmptyTargetFunc, targetBefo
end
end
if conditionals.target ~= nil and targetBeforeAction and not (CleveRoids.hasSuperwow and action == CastSpellByName) then
if conditionals.target ~= nil and targetBeforeAction and not (action == CastSpellByName and (CleveRoids.hasSuperwow or CleveRoids.hasCastSpellByNameUnitToken)) then
if not UnitIsUnit("target", conditionals.target) then
if SpellIsTargeting() then
SpellStopCasting()
@@ -2501,7 +2501,7 @@ function CleveRoids.DoWithConditionals(msg, hook, fixEmptyTargetFunc, targetBefo
-- Legacy path: !Attack without explicit conditionals (checkchanneled injected)
if msg == CleveRoids.Localized.Attack and conditionals.checkchanneled then
AttackTarget()
elseif CleveRoids.hasSuperwow and conditionals.target then
elseif (CleveRoids.hasSuperwow or CleveRoids.hasCastSpellByNameUnitToken) and conditionals.target then
-- Let Nampower DLL handle queuing natively via its CastSpellByName hook
CastSpellByName(castMsg, conditionals.target)
else
+5 -3
View File
@@ -1,7 +1,9 @@
--[[
OverflowBuffFrame Extension
Two frames displaying overflow buffs (slots 33-48) for the player
and the current target (if a group/party member).
Two frames displaying server-side overflow buffs (no client aura slot) for
the player and the current target (if a group/party member).
Player data: CleveRoids.OverflowBuffs (AURA_CAST_ON_SELF when buff-capped).
Target data: CleveRoids.AllCasterAuraTracking filtered by UnitBuff visibility.
Each frame shows 2 rows of 8 icons = 16 slots.
Author: Mewtiny
@@ -98,7 +100,7 @@ local function CreateIconButton(parent, index, iconTable)
if data.source ~= "player" then return end
-- CancelPlayerAuraSpellId(spellId, ignoreMissing)
-- ignoreMissing=1 is required for overflow buffs in invisible slots 33-48
-- ignoreMissing=1 is required for overflow buffs that have no client aura slot
if CancelPlayerAuraSpellId then
CancelPlayerAuraSpellId(data.spellId, 1)
end
+2
View File
@@ -324,6 +324,8 @@ initFrame:SetScript("OnEvent", function()
CleveRoids.hasGetUnitData = API.features.hasGetUnitData
CleveRoids.hasGetSpellModifiers = API.features.hasGetSpellModifiers
CleveRoids.hasEnhancedSpellFunctions = API.features.hasEnhancedSpellFunctions
-- v2.37+: CastSpellByName supports unit token strings as 2nd param
CleveRoids.hasCastSpellByNameUnitToken = API.features.hasCastSpellByNameUnitToken
end
-- Initialize the API
+18 -3
View File
@@ -88,8 +88,8 @@
Aura Cancel Functions (v2.34+):
- CancelPlayerAuraSlot(auraSlot) - Cancel buff/debuff by raw 0-based aura slot
- CancelPlayerAuraSpellId(spellId, [ignoreMissing]) - Cancel buff/debuff by spell ID
Pass ignoreMissing=1 to skip aura-slot presence check (for overflow buffs in
server slots 33-48 that have no client aura slot)
Pass ignoreMissing=1 to skip aura-slot presence check (for server-side
overflow buffs that have no client aura slot in the 48-slot array)
GUID String Fix & Talent Helper (v2.35+):
- GetUnitData/GetUnitField now return GUID-type fields (charm, summon, charmedBy,
@@ -102,7 +102,14 @@
- PlayerIsRooted() - Returns 1 if player is rooted, nil if not
- PlayerIsSwimming() - Returns 1 if player is swimming, nil if not
Current version: v2.36.0
Unit Token Cast & Mouseover (v2.37+):
- CastSpellByName(spellName, unitToken) - 2nd param now accepts unit token strings
(e.g. "mouseover", "party1", "focus") in addition to GUIDs and 1 (self).
Enables soft-casting on unit tokens without changing the player's visible target.
- SetMouseoverUnit(unitToken) - Programmatically set the mouseover unit.
Previously only available via SuperWoW; now also provided by Nampower.
Current version: v2.37.0
]]
local _G = _G or getfenv(0)
@@ -272,6 +279,10 @@ API.VERSION_REQUIREMENTS = {
["PlayerIsMoving"] = { 2, 36, 0, "PlayerIsMoving" },
["PlayerIsRooted"] = { 2, 36, 0, "PlayerIsRooted" },
["PlayerIsSwimming"] = { 2, 36, 0, "PlayerIsSwimming" },
-- v2.37+ - Unit token cast support and SetMouseoverUnit
["CastSpellByNameUnitToken"]= { 2, 37, 0 }, -- CastSpellByName accepts unit token strings as 2nd param
["SetMouseoverUnit"] = { 2, 37, 0, "SetMouseoverUnit" },
}
-- Check if a specific feature is available
@@ -425,6 +436,10 @@ local function InitializeFeatures()
f.hasPlayerIsRooted = API.HasFeature("PlayerIsRooted")
f.hasPlayerIsSwimming = API.HasFeature("PlayerIsSwimming")
-- v2.37+ Unit token cast support and SetMouseoverUnit
f.hasCastSpellByNameUnitToken = API.HasFeature("CastSpellByNameUnitToken")
f.hasSetMouseoverUnit = API.HasFeature("SetMouseoverUnit")
-- Runtime detection for enhanced spell functions (verify by testing)
if f.hasEnhancedSpellFunctions and GetSpellTexture then
local success, result = pcall(function()
+208 -8
View File
@@ -328,7 +328,8 @@ do
local isUpdatingMouseover = false
local function apply(unit)
if CleveRoids.hasSuperwow and _G.SetMouseoverUnit then
if _G.SetMouseoverUnit then
-- Available via SuperWoW or Nampower v2.37+
-- Set flag so UPDATE_MOUSEOVER_UNIT handler knows we triggered this
CleveRoids.__mo.selfTriggered = true
-- Use empty string instead of nil to properly clear mouseover.
@@ -658,7 +659,7 @@ lib.guidToName = lib.guidToName or {}
-- duration calculation, and miss/dodge/parry/resist/immune detection.
-- Enhanced tables (populated by pfUI or standalone Nampower handlers)
lib.ownDebuffs = lib.ownDebuffs or {} -- [targetGUID][spellName] = {startTime, duration, texture, rank, slot}
lib.ownDebuffs = lib.ownDebuffs or {} -- [targetGUID][spellName] = {startTime, duration, texture, rank, spellId}
lib.ownSlots = lib.ownSlots or {} -- [targetGUID][slot] = spellName (LEGACY - empty when pfUI 7.6+ is active)
lib.allSlots = lib.allSlots or {} -- [targetGUID][slot] = {spellName, casterGuid, isOurs} (LEGACY - empty when pfUI 7.6+ is active)
lib.slotOwnership = lib.slotOwnership or {} -- [targetGUID][auraSlot] = {casterGuid, spellName, spellId, isOurs} (pfUI 7.6+ GetUnitField edition)
@@ -667,6 +668,16 @@ lib.pendingCasts = lib.pendingCasts or {} -- [targetGUID][spellName] = {caster
lib.recentMisses = lib.recentMisses or {} -- [targetGUID][spellName] = {time, spellId, targetName, reason} for miss/dodge/parry detection
lib.iconCache = lib.iconCache or {} -- [spellId] = texture (shared with pfUI 7.6 or standalone)
-- Buff tracking tables (parallel to debuff tables, standalone Nampower mode only)
-- Buff tables are NOT linked to pfUI — pfUI has no public buff tracking tables.
-- They remain empty when hasPfUIEnhanced=true since all buff event handlers are gated by it.
lib.ownBuffCasts = lib.ownBuffCasts or {} -- [targetGUID][buffName] = {startTime, duration, spellId, casterGuid}
-- Player's own buff casts on self or others (AURA_CAST events)
lib.allBuffAuras = lib.allBuffAuras or {} -- [targetGUID][buffName][casterGuid] = {startTime, duration, rank}
-- All buffs from all casters on any unit (confirmed by BUFF_ADDED events)
lib.pendingBuffCasts = lib.pendingBuffCasts or {} -- [targetGUID][spellId] = {casterGuid, duration, spellName, time}
-- Temp storage from AURA_CAST_ON_OTHER, consumed by BUFF_ADDED_OTHER
-- Flag indicating whether enhanced pfUI tracking is available
lib.hasPfUIEnhanced = false
lib.hasStandaloneNampower = false
@@ -702,7 +713,7 @@ function lib:HasEnhancedPfUILibdebuff()
end
-- Verify Nampower version based on pfUI version:
-- - pfUI 7.6+ (GetUnitField edition): requires Nampower v2.31.0+
-- - pfUI 7.6+ (GetUnitField edition): requires Nampower v2.37.0+
-- - pfUI 7.4.3 to 7.5.x (legacy): requires Nampower v2.26+
if not GetNampowerVersion then return false end
local npMajor, npMinor, npPatch = GetNampowerVersion()
@@ -712,9 +723,9 @@ function lib:HasEnhancedPfUILibdebuff()
local isPfUI76 = pfUI.libdebuff_slot_ownership ~= nil
if isPfUI76 then
-- pfUI 7.6+ requires Nampower v2.31.0+
-- pfUI 7.6+ requires Nampower v2.37.0+
if npMajor < 2 then return false end
if npMajor == 2 and npMinor < 31 then return false end
if npMajor == 2 and npMinor < 37 then return false end
else
-- Legacy pfUI 7.4.3-7.5.x requires Nampower v2.26+
if npMajor < 2 or (npMajor == 2 and npMinor < 26) then return false end
@@ -732,7 +743,7 @@ function lib:HasEnhancedPfUILibdebuff()
end
-- Check if pfUI v7.6+ with enhanced cast tracking is available
-- pfUI 7.6 requires Nampower v2.31.0+ and exposes additional tables
-- pfUI 7.6+ requires Nampower v2.37.0+ and exposes additional tables
function lib:HasPfUI76()
if not pfUI then return false end
@@ -743,12 +754,12 @@ function lib:HasPfUI76()
if v.major < 7 then return false end
if v.major == 7 and (v.minor or 0) < 6 then return false end
-- Verify Nampower v2.31.0+ (pfUI 7.6 hard requirement)
-- Verify Nampower v2.37.0+ (pfUI 7.6+ hard requirement)
if not GetNampowerVersion then return false end
local npMajor, npMinor, npPatch = GetNampowerVersion()
npPatch = npPatch or 0
if npMajor < 2 then return false end
if npMajor == 2 and npMinor < 31 then return false end
if npMajor == 2 and npMinor < 37 then return false end
-- Verify the new tables exist
if not pfUI.libdebuff_casts then return false end
@@ -1182,6 +1193,58 @@ function lib:CleanupStaleTrackingData()
end
end
end
-- Clean ownBuffCasts (player-cast buffs on targets)
if lib.ownBuffCasts then
for guid, buffs in pairs(lib.ownBuffCasts) do
for buffName, data in pairs(buffs) do
local elapsed = now - (data.startTime or 0)
local dur = data.duration or 0
-- Remove if expired (duration > 0 and past end time) or stale (no duration and old)
if (dur > 0 and elapsed > dur) or (dur <= 0 and elapsed > staleTime) then
buffs[buffName] = nil
end
end
if not next(buffs) then
lib.ownBuffCasts[guid] = nil
end
end
end
-- Clean allBuffAuras (all-caster buff tracking)
if lib.allBuffAuras then
for guid, buffs in pairs(lib.allBuffAuras) do
for buffName, casters in pairs(buffs) do
for cGuid, data in pairs(casters) do
local elapsed = now - (data.startTime or 0)
local dur = data.duration or 0
if (dur > 0 and elapsed > dur) or (dur <= 0 and elapsed > staleTime) then
casters[cGuid] = nil
end
end
if not next(casters) then
buffs[buffName] = nil
end
end
if not next(buffs) then
lib.allBuffAuras[guid] = nil
end
end
end
-- Clean pendingBuffCasts (short-lived correlation data, 2 second TTL)
if lib.pendingBuffCasts then
for guid, spells in pairs(lib.pendingBuffCasts) do
for spellId, data in pairs(spells) do
if (now - (data.time or 0)) > 2 then
spells[spellId] = nil
end
end
if not next(spells) then
lib.pendingBuffCasts[guid] = nil
end
end
end
end
-- Get the caster GUID for a debuff on a target
@@ -3689,6 +3752,13 @@ if CleveRoids.hasNampower then
ev:RegisterEvent("DEBUFF_REMOVED_OTHER")
ev:RegisterEvent("UNIT_DIED") -- Instant cleanup on target death
-- BUFF_ADDED/REMOVED events require v2.30+ for auraSlot and state args
if npMajor > 2 or (npMajor == 2 and npMinor >= 30) then
ev:RegisterEvent("BUFF_ADDED_OTHER")
ev:RegisterEvent("BUFF_REMOVED_SELF")
ev:RegisterEvent("BUFF_REMOVED_OTHER")
end
if CleveRoids.debug then
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00[Nampower]|r Registered UNIT_DIED for instant cleanup (v2.26+)")
end
@@ -4886,6 +4956,125 @@ ev:SetScript("OnEvent", function()
end
end
-- NAMPOWER v2.30+ BUFF_ADDED_OTHER - Confirm pending AURA_CAST_ON_OTHER data as buff
elseif event == "BUFF_ADDED_OTHER" then
if lib.hasPfUIEnhanced then return end
local guid = CleveRoids.NormalizeGUID(arg1)
local spellId = arg3
local state = arg7 -- 0=added, 1=removed, 2=modified
if not guid or not spellId then return end
-- Consume pending AURA_CAST data for this buff
local pending = lib.pendingBuffCasts[guid] and lib.pendingBuffCasts[guid][spellId]
if pending then
local spellName = pending.spellName or (SpellInfo and SpellInfo(spellId))
if spellName then
local casterGuid = pending.casterGuid
local duration = pending.duration
local now = GetTime()
-- allBuffAuras: confirmed buff from any caster
lib.allBuffAuras[guid] = lib.allBuffAuras[guid] or {}
lib.allBuffAuras[guid][spellName] = lib.allBuffAuras[guid][spellName] or {}
lib.allBuffAuras[guid][spellName][casterGuid or "unknown"] = {
startTime = now,
duration = duration or 0,
rank = 0,
}
-- ownBuffCasts: only if player is the caster
local _, playerGuidRaw = UnitExists("player")
local playerGuid = playerGuidRaw and CleveRoids.NormalizeGUID(playerGuidRaw)
if playerGuid and casterGuid == playerGuid then
lib.ownBuffCasts[guid] = lib.ownBuffCasts[guid] or {}
lib.ownBuffCasts[guid][spellName] = {
startTime = now,
duration = duration or 0,
spellId = spellId,
casterGuid = casterGuid,
}
end
end
lib.pendingBuffCasts[guid][spellId] = nil
end
-- NAMPOWER v2.30+ BUFF_REMOVED_SELF - Player's own buffs removed
elseif event == "BUFF_REMOVED_SELF" then
if lib.hasPfUIEnhanced then return end
local spellId = arg3
local state = arg7
if not spellId then return end
if state == 2 then return end -- Stack decrease only, not full removal
local spellName = SpellInfo and SpellInfo(spellId)
local _, playerGuidRaw = UnitExists("player")
local playerGuid = playerGuidRaw and CleveRoids.NormalizeGUID(playerGuidRaw)
if spellName and playerGuid then
if lib.ownBuffCasts[playerGuid] then
lib.ownBuffCasts[playerGuid][spellName] = nil
if not next(lib.ownBuffCasts[playerGuid]) then
lib.ownBuffCasts[playerGuid] = nil
end
end
if lib.allBuffAuras[playerGuid] then
lib.allBuffAuras[playerGuid][spellName] = nil
if not next(lib.allBuffAuras[playerGuid]) then
lib.allBuffAuras[playerGuid] = nil
end
end
end
-- Also prune from OverflowBuffs (existing system)
if spellId and CleveRoids.OverflowBuffs and CleveRoids.OverflowBuffs[spellId] then
CleveRoids.OverflowBuffs[spellId] = nil
end
-- NAMPOWER v2.30+ BUFF_REMOVED_OTHER - Buffs removed from other units
elseif event == "BUFF_REMOVED_OTHER" then
if lib.hasPfUIEnhanced then return end
local guid = CleveRoids.NormalizeGUID(arg1)
local spellId = arg3
local state = arg7
if not guid or not spellId then return end
if state == 2 then return end -- Stack decrease only, not full removal
local spellName = SpellInfo and SpellInfo(spellId)
if spellName then
if lib.allBuffAuras[guid] then
lib.allBuffAuras[guid][spellName] = nil
if not next(lib.allBuffAuras[guid]) then
lib.allBuffAuras[guid] = nil
end
end
if lib.ownBuffCasts[guid] then
lib.ownBuffCasts[guid][spellName] = nil
if not next(lib.ownBuffCasts[guid]) then
lib.ownBuffCasts[guid] = nil
end
end
end
-- Clean pending correlation data
if lib.pendingBuffCasts[guid] then
lib.pendingBuffCasts[guid][spellId] = nil
if not next(lib.pendingBuffCasts[guid]) then
lib.pendingBuffCasts[guid] = nil
end
end
-- Also clean AllCasterAuraTracking (existing system)
if CleveRoids.AllCasterAuraTracking[guid] then
CleveRoids.AllCasterAuraTracking[guid][spellId] = nil
if not next(CleveRoids.AllCasterAuraTracking[guid]) then
CleveRoids.AllCasterAuraTracking[guid] = nil
end
end
-- NAMPOWER v2.26+ UNIT_DIED - Instant cleanup on target death
elseif event == "UNIT_DIED" then
local guid = arg1
@@ -4918,6 +5107,17 @@ ev:SetScript("OnEvent", function()
lib.recentMisses[guid] = nil
end
-- Clean up buff tracking tables
if lib.ownBuffCasts[guid] then
lib.ownBuffCasts[guid] = nil
end
if lib.allBuffAuras[guid] then
lib.allBuffAuras[guid] = nil
end
if lib.pendingBuffCasts[guid] then
lib.pendingBuffCasts[guid] = nil
end
-- Clean up cast tracking for this unit (they can't be casting if dead)
if not lib.hasPfUI76 and CleveRoids.castTracking[guid] then
CleveRoids.castTracking[guid] = nil