Re-base the libdebuff read path on C_UnitAuras

The four readers that answered "what is on this unit, for how long, cast by
whom" spliced two sources together: the engine's aura array for existence and
icon, lib.objects for duration and caster. Everything the second half supplied
is now in the AuraData the first half already returns -- duration is the
caster-modified value when ClassicAPI saw the cast, expirationTime gives the
true remaining, sourceUnit names the caster -- so they read one source.

That deletes the overflow-slot rule with them. UnitDebuff used to split its
index space at 16 and map 17-48 onto buff slots, because a debuff can be parked
in a buff slot on an NPC, then filter the results through GetDuration to guess
which of those buffs were really debuffs. isHarmful is the aura's real polarity,
so the HARMFUL range already contains them and the guess is gone.

timeleft keeps its -1-means-unknown convention. ClassicAPI reports
expirationTime 0 for an aura whose cast it never observed -- one predating
login, or a max-stack refresh whose cache entry elapsed -- which is the same
"present but untimed" state the store expressed by having no record, so callers
testing `timeleft > 0` see no change.

GetDebuffCaster, IsOurDebuff and GetAllDebuffsOnTarget are deleted outright
rather than re-based: all three have no callers anywhere in the addon, and they
were the only readers of the slotOwnership and allSlots mirrors.
This commit is contained in:
Brues
2026-09-12 07:59:28 -05:00
parent afc5e513ca
commit 7101ad167a
+40 -284
View File
@@ -1330,139 +1330,6 @@ function lib:CleanupStaleTrackingData()
end
-- Get the caster GUID for a debuff on a target
-- Returns: casterGuid or nil
function lib:GetDebuffCaster(unit, spellName)
if not spellName then return nil end
local guid = CleveRoids.GetGUID(unit)
if not guid then return nil end
-- Check own debuffs first
if lib.ownDebuffs[guid] and lib.ownDebuffs[guid][spellName] then
local playerGuid = CleveRoids.GetGUID("player")
return playerGuid
end
-- Check slotOwnership (pfUI 7.6+ GetUnitField edition) for caster info
if lib.slotOwnership[guid] then
for auraSlot, slotData in pairs(lib.slotOwnership[guid]) do
if slotData.spellName == spellName then
return slotData.casterGuid
end
end
end
-- LEGACY: Check allSlots for caster info (pre-pfUI 7.6)
if lib.allSlots[guid] then
for slot, slotData in pairs(lib.allSlots[guid]) do
if slotData.spellName == spellName then
return slotData.casterGuid
end
end
end
-- Check allAuraCasts
if lib.allAuraCasts[guid] and lib.allAuraCasts[guid][spellName] then
-- Return first caster found (there should typically be only one for unique debuffs)
for casterGuid, _ in pairs(lib.allAuraCasts[guid][spellName]) do
return casterGuid
end
end
return nil
end
-- Check if a debuff on a target is from the player
-- Returns: true if player's debuff, false otherwise
function lib:IsOurDebuff(unit, spellName)
if not spellName then return false end
local guid = CleveRoids.GetGUID(unit)
if not guid then return false end
-- Check own debuffs
if lib.ownDebuffs[guid] and lib.ownDebuffs[guid][spellName] then
return true
end
-- Check slotOwnership (pfUI 7.6+ GetUnitField edition) for isOurs flag
if lib.slotOwnership[guid] then
for auraSlot, slotData in pairs(lib.slotOwnership[guid]) do
if slotData.spellName == spellName then
return slotData.isOurs == true
end
end
end
-- LEGACY: Check allSlots for isOurs flag (pre-pfUI 7.6)
if lib.allSlots[guid] then
for slot, slotData in pairs(lib.allSlots[guid]) do
if slotData.spellName == spellName then
return slotData.isOurs == true
end
end
end
return false
end
-- Get all tracked debuffs on a target
-- Returns: table of {spellName = {duration, timeleft, casterGuid, isOurs, stacks}}
function lib:GetAllDebuffsOnTarget(guid)
if not guid then return {} end
guid = CleveRoids.NormalizeGUID(guid)
local result = {}
local now = GetTime()
-- Gather from ownDebuffs
if lib.ownDebuffs[guid] then
for spellName, data in pairs(lib.ownDebuffs[guid]) do
if data.startTime and data.duration then
local timeleft = (data.startTime + data.duration) - now
if timeleft > 0 then
result[spellName] = {
duration = data.duration,
timeleft = timeleft,
casterGuid = nil, -- Player's own, get from UnitExists("player")
isOurs = true,
stacks = 1,
rank = data.rank,
slot = data.slot,
}
end
end
end
end
-- Gather from allAuraCasts (other players' debuffs)
if lib.allAuraCasts[guid] then
for spellName, casterTable in pairs(lib.allAuraCasts[guid]) do
if not result[spellName] then
for casterGuid, data in pairs(casterTable) do
if data.startTime and data.duration then
local timeleft = (data.startTime + data.duration) - now
if timeleft > 0 then
result[spellName] = {
duration = data.duration,
timeleft = timeleft,
casterGuid = casterGuid,
isOurs = false,
stacks = 1,
rank = data.rank,
}
break -- Only store first active caster's data
end
end
end
end
end
end
return result
end
-- Check if a pending cast exists for a spell on a target
function lib:HasPendingCast(targetGuid, spellName)
if not targetGuid or not spellName then return false end
@@ -2270,167 +2137,56 @@ function lib:AddEffect(guid, unitName, spellID, duration, stacks, caster)
end
end
-- Unpack one AuraData into libdebuff's return shape:
-- name, rank, texture, stacks, dispelType, duration, timeleft, caster
-- timeleft is -1 when the aura's timing is unknown -- ClassicAPI reports
-- expirationTime 0 for an aura whose cast it never saw (one that predates login,
-- or a max-stack refresh whose cache entry has elapsed). -1 is what the manual
-- store returned for an untracked aura, so callers testing `timeleft > 0` are
-- unaffected; it means "no timer", not "expired".
local function UnpackAura(aura)
local timeleft = -1
if aura.expirationTime and aura.expirationTime > 0 then
timeleft = aura.expirationTime - GetTime()
end
return aura.name, nil, aura.icon, aura.applications, aura.dispelName,
aura.duration, timeleft, aura.sourceUnit
end
-- Query debuff data by index. The index space is C_UnitAuras' dense HARMFUL
-- range, so it is walked to the first nil at any index -- no 16-debuff /
-- 32-buff split and no overflow rule, because isHarmful is the aura's real
-- polarity and a debuff parked in a buff slot still reads harmful.
function lib:UnitDebuff(unit, id, filterCaster)
local guid = CleveRoids.GetGUID(unit)
if not guid then return nil end
local texture, stacks, dtype, spellID = nil, nil, nil, nil
-- SuperWoW debuff slots: 1-16 are regular debuffs, 17-48 overflow to buff slots 1-32
-- See: https://forum.turtle-wow.org/viewtopic.php?t=13281
if id <= 16 then
-- Regular debuff slot
texture, stacks, dtype, spellID = UnitDebuff(unit, id)
else
-- Overflow debuff in buff slot: debuff index 17 = buff index 1, etc.
local buffIndex = id - 16
if buffIndex <= 32 then
texture, stacks, spellID = UnitBuff(unit, buffIndex)
-- Only accept buffs that are known debuffs (either static or learned durations, including combo durations)
if texture and spellID and lib:GetDuration(spellID) <= 0 then
return nil
end
end
end
if not texture or not spellID then return nil end
local name = C_Spell.GetSpellName(spellID)
local duration, timeleft, caster = nil, -1, nil
local rec = lib.objects[guid] and lib.objects[guid][spellID]
if rec and rec.duration and rec.start then
local remaining = rec.duration + rec.start - GetTime()
if remaining > 0 then
duration = rec.duration
timeleft = remaining
caster = rec.caster
stacks = rec.stacks or stacks
-- Filter by caster if requested
-- Since personal debuffs are only tracked via UNIT_CASTEVENT (player only),
-- this filter only applies to shared debuffs where multiple casters can apply
if filterCaster and caster ~= filterCaster then
return nil
end
else
lib.objects[guid][spellID] = nil
end
elseif filterCaster then
-- No tracking record exists. This means either:
-- 1. Personal debuff from another player (wasn't tracked via UNIT_CASTEVENT)
-- 2. Shared debuff that expired from tracking
-- In both cases, if filtering by caster, we should reject it
return nil
end
return name, nil, texture, stacks, dtype, duration, timeleft, caster
local aura = C_UnitAuras.GetAuraDataByIndex(unit, id, "HARMFUL")
if not aura then return nil end
if filterCaster and aura.sourceUnit ~= filterCaster then return nil end
return UnpackAura(aura)
end
-- Query buff data with duration and caster tracking (buff slots only)
-- Query buff data by index (helpful range).
function lib:UnitBuff(unit, id, filterCaster)
local guid = CleveRoids.GetGUID(unit)
if not guid then return nil end
-- Only check buff slots
local texture, stacks, spellID = UnitBuff(unit, id)
if not texture or not spellID then return nil end
local name = C_Spell.GetSpellName(spellID)
local duration, timeleft, caster = nil, -1, nil
local rec = lib.objects[guid] and lib.objects[guid][spellID]
if rec and rec.duration and rec.start then
local remaining = rec.duration + rec.start - GetTime()
if remaining > 0 then
duration = rec.duration
timeleft = remaining
caster = rec.caster
stacks = rec.stacks or stacks
-- Filter by caster if requested
if filterCaster and caster ~= filterCaster then
return nil
end
else
lib.objects[guid][spellID] = nil
end
elseif filterCaster then
-- No tracking record exists - reject when filtering by caster
return nil
end
return name, nil, texture, stacks, nil, duration, timeleft, caster
local aura = C_UnitAuras.GetAuraDataByIndex(unit, id, "HELPFUL")
if not aura then return nil end
if filterCaster and aura.sourceUnit ~= filterCaster then return nil end
return UnpackAura(aura)
end
-- Find a player-cast debuff by spell ID (searches all slots including buff slots)
-- Find a player-cast debuff by spell ID. The PLAYER filter matches on
-- ClassicAPI's cached caster GUID, so an aura whose cast was never observed is
-- excluded -- the same answer the store's `caster == "player"` test gave, since
-- an unobserved cast was never recorded there either.
function lib:FindPlayerDebuff(unit, spellID)
local guid = CleveRoids.GetGUID(unit)
if not guid then return nil end
-- Check if we're tracking this spell for this unit
local rec = lib.objects[guid] and lib.objects[guid][spellID]
if not rec then return nil end
-- Only return if it was cast by player
if rec.caster ~= "player" then return nil end
-- Check if it's still active
local remaining = rec.duration + rec.start - GetTime()
if remaining <= 0 then
lib.objects[guid][spellID] = nil
return nil
end
-- Find the texture/stacks via one by-ID lookup (walks both debuff and buff
-- ranges), replacing the 16 debuff + 32 buff slot scan.
local texture, stacks = nil, rec.stacks
local aura = CleveRoids.ClassicAPI.GetUnitAuraBySpellID(unit, spellID)
if aura then
texture = aura.icon
stacks = aura.applications or stacks
end
if not texture then return nil end
local name = C_Spell.GetSpellName(spellID)
return name, nil, texture, stacks, nil, rec.duration, remaining, rec.caster
local aura = C_UnitAuras.GetUnitAuraBySpellID(unit, spellID, "HARMFUL|PLAYER")
if not aura then return nil end
return UnpackAura(aura)
end
-- Find a player-cast buff by spell ID (searches buff slots only)
-- Find a player-cast buff by spell ID.
function lib:FindPlayerBuff(unit, spellID)
local guid = CleveRoids.GetGUID(unit)
if not guid then return nil end
-- Check if we're tracking this spell for this unit
local rec = lib.objects[guid] and lib.objects[guid][spellID]
if not rec then return nil end
-- Only return if it was cast by player
if rec.caster ~= "player" then return nil end
-- Check if it's still active
local remaining = rec.duration + rec.start - GetTime()
if remaining <= 0 then
lib.objects[guid][spellID] = nil
return nil
end
-- Find the texture/stacks via one by-ID lookup restricted to buffs.
local texture, stacks = nil, rec.stacks
local aura = CleveRoids.ClassicAPI.GetUnitAuraBySpellID(unit, spellID, "HELPFUL")
if aura then
texture = aura.icon
stacks = aura.applications or stacks
end
if not texture then return nil end
local name = C_Spell.GetSpellName(spellID)
return name, nil, texture, stacks, nil, rec.duration, remaining, rec.caster
local aura = C_UnitAuras.GetUnitAuraBySpellID(unit, spellID, "HELPFUL|PLAYER")
if not aura then return nil end
return UnpackAura(aura)
end
local function SeedUnit(unit)