When ClassicAPI is installed and "Use ClassicAPI" is on (Mods tab, reload): - Detect ClassicAPI and each feature separately; show it in the Mods tab - Read auras with C_UnitAuras.GetAuraSlots (fill form) + UnitAuraBySlot, reusing entries, with ClassicAPI durations, expirations and casters - UNIT_AURA_DELTA: refresh-only events update stacks and timers in place without rebuilding the aura icons or the health bar - Aura timers without SuperWoW; native Cooldown frames with one shared text driver instead of a Model and OnUpdateModel per icon - UNIT_IN_RANGE_UPDATE, UNIT_DISTANCE_CHECK_UPDATE, UNIT_CONNECTION and INCOMING_RESURRECT_CHANGED; UnitHasIncomingResurrection in res checks - HealPredict learns heals from COMBAT_LOG_EVENT_UNFILTERED when the GUID formats match SuperWoW's - Distance and line of sight when UnitXP SP3 and SuperWoW are missing Without ClassicAPI, or with the option off, behaviour is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019W3rwwTK1hwTEWHqsan2mV
748 lines
23 KiB
Lua
748 lines
23 KiB
Lua
-- Caches important information about units and makes the data easily readable at any time.
|
|
-- If using SuperWoW, the cache map will have GUIDs as the key instead of unit IDs.
|
|
|
|
PTUnit = {}
|
|
PTUtil.SetEnvironment(PTUnit)
|
|
local _G = getfenv(0)
|
|
|
|
local util = PTUtil
|
|
local GetAuraInfo = util.GetAuraInfo
|
|
local AllUnits = util.AllUnits
|
|
local AllRealUnits = util.AllRealUnits
|
|
local AllUnitsSet = util.AllUnitsSet
|
|
local superwow = util.IsSuperWowPresent()
|
|
local canGetAuraIDs = util.CanClientGetAuraIDs()
|
|
|
|
-- ClassicAPI aura reading, decided once options load (see ApplyClassicAPIOptions)
|
|
local classicAuras = false
|
|
local classicResurrection = false
|
|
local GetAuraSlots, UnitAuraBySlot
|
|
|
|
local compost = PTPool
|
|
|
|
-- Non-instance variable
|
|
-- Key: Unit ID(Unmodded) or GUID(SuperWoW) | Value: PTUnit Instance
|
|
PTUnit.Cached = {}
|
|
|
|
PTUnit.Unit = nil
|
|
|
|
PTUnit.AurasPopulated = false
|
|
-- Buff/debuff entry contents: {"name", "stacks", "texture", "index", "type", "id"(SuperWoW/ClassicAPI only), "time"}
|
|
-- With ClassicAPI, "index" is the C_UnitAuras index and "filter" is "HELPFUL" or "HARMFUL" (used for tooltips)
|
|
PTUnit.Buffs = {} -- Array of all buffs
|
|
PTUnit.BuffsMap = {} -- Key: Name | Value: Array of buffs with key's name
|
|
PTUnit.BuffsIDSet = {} -- Set of currently applied buff IDs
|
|
PTUnit.Debuffs = {} -- Array of all debuffs
|
|
PTUnit.DebuffsMap = {} -- Key: Name | Value: Array of debuffs with key's name
|
|
PTUnit.DebuffsIDSet = {} -- Set of currently applied debuff IDs
|
|
PTUnit.TypedDebuffs = {} -- Key: Type | Value: Array of debuffs that are the type
|
|
PTUnit.AfflictedDebuffTypes = {} -- Set of the afflicted debuff types
|
|
PTUnit.TrackedDebuffTypes = {} -- Set of debuff types that exclude frivilous debuffs
|
|
|
|
PTUnit.HasHealingModifier = false
|
|
PTUnit.HasImportantDebuff = false
|
|
|
|
-- Only used with SuperWoW, managed in AuraTracker.lua
|
|
PTUnit.AuraTimes = {} -- Key: Aura Name | Value: {"startTime", "duration"}
|
|
PTUnit.AuraTimesByName = {}
|
|
|
|
PTUnit.DisplayPVP = false -- This is not the real PVP status of the unit, this is affected by other conditions
|
|
|
|
PTUnit.Distance = 0
|
|
PTUnit.InSight = true
|
|
PTUnit.IsNew = false
|
|
|
|
function ApplyClassicAPIOptions()
|
|
classicAuras = util.IsClassicAPIActive("AuraSlots")
|
|
if classicAuras then
|
|
GetAuraSlots = C_UnitAuras.GetAuraSlots
|
|
UnitAuraBySlot = C_UnitAuras.UnitAuraBySlot
|
|
canGetAuraIDs = true -- Spell IDs come with every aura
|
|
end
|
|
classicResurrection = util.IsClassicAPIActive("Resurrection")
|
|
end
|
|
|
|
function UsesClassicAuras()
|
|
return classicAuras
|
|
end
|
|
|
|
-- Non-GUID function
|
|
function CreateCaches()
|
|
if superwow then
|
|
Puppeteer.print("Tried to create non-SuperWoW caches while using SuperWoW!")
|
|
return
|
|
end
|
|
for _, unit in ipairs(AllUnits) do
|
|
PTUnit:New(unit)
|
|
end
|
|
end
|
|
|
|
function UpdateGuidCaches()
|
|
local cached = PTUnit.Cached
|
|
local prevCached = PTUtil.CloneTableCompost(cached)
|
|
for _, unit in ipairs(AllRealUnits) do
|
|
local exists, guid = UnitExists(unit)
|
|
if exists then
|
|
if not cached[guid] then
|
|
PTUnit:New(guid)
|
|
Puppeteer.EvaluateTracking(unit, true)
|
|
end
|
|
prevCached[guid] = nil
|
|
end
|
|
end
|
|
for guid, units in pairs(PTUnitProxy.GUIDCustomUnitMap) do
|
|
if not cached[guid] then
|
|
PTUnit:New(guid)
|
|
for _, unit in ipairs(units) do
|
|
Puppeteer.EvaluateTracking(unit, true)
|
|
end
|
|
end
|
|
prevCached[guid] = nil
|
|
end
|
|
for garbageGuid, cache in pairs(prevCached) do
|
|
cache:Dispose()
|
|
compost:Reclaim(cache)
|
|
cached[garbageGuid] = nil
|
|
end
|
|
compost:Reclaim(prevCached)
|
|
end
|
|
|
|
-- Likely never needed to be called when using GUIDs
|
|
function UpdateAllUnits()
|
|
for _, cache in pairs(PTUnit.Cached) do
|
|
cache:UpdateAll()
|
|
end
|
|
end
|
|
|
|
-- Get the PTUnit by unit ID. If using SuperWoW, GUID or unit ID is accepted.
|
|
function Get(unit)
|
|
if superwow and AllUnitsSet[unit] then
|
|
return PTUnit.Cached[PTGuidRoster.GetUnitGuid(unit)] or PTUnit
|
|
end
|
|
return PTUnit.Cached[unit] or PTUnit
|
|
end
|
|
|
|
function GetAllUnits()
|
|
return PTUnit.Cached
|
|
end
|
|
|
|
function PTUnit:New(unit)
|
|
local obj = compost:AcquireHash("Unit", unit)
|
|
setmetatable(obj, self)
|
|
self.__index = self
|
|
PTUnit.Cached[unit] = obj
|
|
obj:AllocateAuras()
|
|
obj.AurasPopulated = true -- To force aura fields to generate
|
|
obj.IsNew = true
|
|
obj.IsSelf = UnitIsUnit(unit, "player")
|
|
if superwow then
|
|
obj.AuraTimes = compost:GetTable()
|
|
obj.AuraTimesByName = compost:GetTable()
|
|
end
|
|
obj:UpdateAll()
|
|
return obj
|
|
end
|
|
|
|
function PTUnit:Dispose()
|
|
ReleaseArrayMap(self.BuffsMap)
|
|
ReleaseArrayMap(self.DebuffsMap)
|
|
ReleaseArrayMap(self.TypedDebuffs)
|
|
compost:Reclaim(self.Buffs)
|
|
compost:Reclaim(self.BuffsMap)
|
|
compost:Reclaim(self.BuffsIDSet)
|
|
compost:Reclaim(self.Debuffs)
|
|
compost:Reclaim(self.DebuffsMap)
|
|
compost:Reclaim(self.DebuffsIDSet)
|
|
compost:Reclaim(self.TypedDebuffs)
|
|
compost:Reclaim(self.AfflictedDebuffTypes)
|
|
compost:Reclaim(self.TrackedDebuffTypes)
|
|
compost:Reclaim(self.BuffEntries, 1)
|
|
compost:Reclaim(self.DebuffEntries, 1)
|
|
util.CompostReclaim(self.AuraTimes)
|
|
compost:Reclaim(self.AuraTimesByName)
|
|
end
|
|
|
|
function PTUnit:UpdateAll()
|
|
self:UpdateAuras()
|
|
self:UpdatePVP()
|
|
self:UpdateDistance()
|
|
self:UpdateSight()
|
|
end
|
|
|
|
-- Returns true if this unit is new, clearing its new status.
|
|
function PTUnit:CheckNew()
|
|
if self.IsNew then
|
|
self.IsNew = false
|
|
return true
|
|
end
|
|
end
|
|
|
|
function PTUnit:UpdatePVP()
|
|
if not self.Unit then
|
|
return
|
|
end
|
|
local shouldDisplay = UnitIsPVP(self.Unit) and (not util.IsReallyInInstance() or not UnitIsVisible(self.Unit))
|
|
if self.DisplayPVP ~= (shouldDisplay == 1 or shouldDisplay == true) then
|
|
self.DisplayPVP = shouldDisplay
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
function PTUnit:ShouldDisplayPVP()
|
|
return self.DisplayPVP
|
|
end
|
|
|
|
-- Returns true if the distance changed
|
|
function PTUnit:UpdateDistance()
|
|
if not self.Unit then
|
|
return
|
|
end
|
|
local prevDist = self.Distance
|
|
self.Distance = self.IsSelf and 0 or util.GetDistanceTo(self.Unit)
|
|
|
|
return self.Distance ~= prevDist
|
|
end
|
|
|
|
function PTUnit:GetDistance()
|
|
return self.Distance
|
|
end
|
|
|
|
-- Returns true if the sight state has changed
|
|
function PTUnit:UpdateSight()
|
|
if not self.Unit then
|
|
return
|
|
end
|
|
local wasInSight = self.InSight
|
|
self.InSight = self.IsSelf or util.IsInSight(self.Unit)
|
|
|
|
return self.InSight ~= wasInSight
|
|
end
|
|
|
|
function PTUnit:IsInSight()
|
|
return self.InSight
|
|
end
|
|
|
|
-- ClassicAPI sees resurrections from any nearby caster, including players without HealComm
|
|
local function hasClassicResurrection(unit)
|
|
return classicResurrection and UnitHasIncomingResurrection(unit) == true
|
|
end
|
|
|
|
function PTUnit:IsBeingResurrected()
|
|
if not self.Unit then
|
|
return false
|
|
end
|
|
if PTHealPredict then
|
|
return PTHealPredict.IsBeingResurrected(self.Unit) or hasClassicResurrection(self.Unit)
|
|
end
|
|
return Puppeteer.HealComm:UnitisResurrecting(UnitName(self.Unit)) or hasClassicResurrection(self.Unit)
|
|
end
|
|
|
|
function PTUnit:GetResurrectionCasts()
|
|
if not self.Unit then
|
|
return 0
|
|
end
|
|
local count
|
|
if PTHealPredict then
|
|
count = PTHealPredict.GetResurrectionCount(self.Unit)
|
|
else
|
|
count = Puppeteer.HealComm:UnitisResurrecting(UnitName(self.Unit)) and 1 or 0
|
|
end
|
|
if count == 0 and hasClassicResurrection(self.Unit) then
|
|
count = 1
|
|
end
|
|
return count
|
|
end
|
|
|
|
-- Aura storage is reused across rescans so that UNIT_AURA produces no garbage:
|
|
-- * Each unit keeps one entry table per aura slot (BuffEntries/DebuffEntries) that is overwritten in place.
|
|
-- * The name/type -> array maps hand their arrays back to a dedicated free list when cleared.
|
|
-- * Buffs, Debuffs and the sets are emptied in place rather than swapped for new tables.
|
|
local freeArrays = {}
|
|
local freeArrayCount = 0
|
|
local MAX_FREE_ARRAYS = 1024
|
|
local getn, setn = table.getn, table.setn
|
|
|
|
local function GetArray()
|
|
if freeArrayCount > 0 then
|
|
local array = freeArrays[freeArrayCount]
|
|
freeArrays[freeArrayCount] = nil
|
|
freeArrayCount = freeArrayCount - 1
|
|
return array
|
|
end
|
|
return {}
|
|
end
|
|
|
|
local function ClearArray(array)
|
|
for i = getn(array), 1, -1 do
|
|
array[i] = nil
|
|
end
|
|
setn(array, 0)
|
|
end
|
|
|
|
-- Empties a map whose values are arrays, keeping the arrays for reuse
|
|
function ReleaseArrayMap(map)
|
|
for key, array in pairs(map) do
|
|
map[key] = nil
|
|
ClearArray(array)
|
|
if freeArrayCount < MAX_FREE_ARRAYS then
|
|
freeArrayCount = freeArrayCount + 1
|
|
freeArrays[freeArrayCount] = array
|
|
end
|
|
end
|
|
end
|
|
local ReleaseArrayMap = ReleaseArrayMap
|
|
|
|
local function ClearSet(set)
|
|
for key in pairs(set) do
|
|
set[key] = nil
|
|
end
|
|
end
|
|
|
|
local function GetEntry(entries, slot)
|
|
local entry = entries[slot]
|
|
if not entry then
|
|
entry = {}
|
|
entries[slot] = entry
|
|
end
|
|
return entry
|
|
end
|
|
|
|
function PTUnit:AllocateAuras()
|
|
self.Buffs = compost:GetTable()
|
|
self.BuffsMap = compost:GetTable()
|
|
self.BuffsIDSet = compost:GetTable()
|
|
self.Debuffs = compost:GetTable()
|
|
self.DebuffsMap = compost:GetTable()
|
|
self.DebuffsIDSet = compost:GetTable()
|
|
self.TypedDebuffs = compost:GetTable()
|
|
self.AfflictedDebuffTypes = compost:GetTable()
|
|
self.TrackedDebuffTypes = compost:GetTable()
|
|
self.BuffEntries = compost:GetTable()
|
|
self.DebuffEntries = compost:GetTable()
|
|
end
|
|
|
|
function PTUnit:ClearAuras()
|
|
if not self.AurasPopulated or self == PTUnit then
|
|
return
|
|
end
|
|
ClearArray(self.Buffs)
|
|
ReleaseArrayMap(self.BuffsMap)
|
|
ClearSet(self.BuffsIDSet)
|
|
ClearArray(self.Debuffs)
|
|
ReleaseArrayMap(self.DebuffsMap)
|
|
ClearSet(self.DebuffsIDSet)
|
|
ReleaseArrayMap(self.TypedDebuffs)
|
|
ClearSet(self.AfflictedDebuffTypes)
|
|
ClearSet(self.TrackedDebuffTypes)
|
|
self.HasHealingModifier = false
|
|
self.AurasPopulated = false
|
|
self.HasImportantDebuff = false
|
|
end
|
|
|
|
local claimedAuras = {}
|
|
|
|
-- The SuperWoW/Nampower aura time with the most time left for this spell ID that no other aura claimed yet
|
|
local function FindTrackedTime(self, id, time)
|
|
local auraTimes = id and self.AuraTimes[id]
|
|
if not auraTimes then
|
|
return nil
|
|
end
|
|
local auraTime = nil
|
|
local highestDuration = -100
|
|
for owner, aura in pairs(auraTimes) do
|
|
local timeLeft = aura.endTime - time
|
|
if not claimedAuras[aura] and timeLeft > highestDuration then
|
|
auraTime = aura
|
|
highestDuration = timeLeft
|
|
end
|
|
end
|
|
if auraTime then
|
|
claimedAuras[auraTime] = 1
|
|
end
|
|
return auraTime
|
|
end
|
|
|
|
-- ClassicAPI aura reading. One slot enumeration per range, then a positional read per aura,
|
|
-- so a scan creates no tables after each entry's first use.
|
|
local slotBuffer = {}
|
|
|
|
-- Fills the entry's own time table from ClassicAPI's duration and expiration. Returns nil when unknown.
|
|
local function ApplyClassicTime(entry, duration, expirationTime, source, now)
|
|
if not expirationTime or expirationTime <= 0 or not duration or duration <= 0 then
|
|
return nil
|
|
end
|
|
local startTime = expirationTime - duration
|
|
if startTime > now then -- A talent-extended aura must not start in the future
|
|
startTime = now
|
|
duration = expirationTime - now
|
|
end
|
|
if duration <= 0 then
|
|
return nil
|
|
end
|
|
local auraTime = entry.classicTime
|
|
if not auraTime then
|
|
auraTime = {}
|
|
entry.classicTime = auraTime
|
|
end
|
|
auraTime.startTime = startTime
|
|
auraTime.endTime = expirationTime
|
|
auraTime.duration = duration
|
|
auraTime.owner = source -- A unit token, or nil when the cast was not seen
|
|
auraTime.ownerName = source and UnitName(source) or nil
|
|
return auraTime
|
|
end
|
|
|
|
function PTUnit:ScanClassicAuras(unit, time)
|
|
local buffs = self.Buffs
|
|
local buffsMap = self.BuffsMap
|
|
local buffsIDSet = self.BuffsIDSet
|
|
local buffEntries = self.BuffEntries
|
|
local trackedHealingBuffs = PuppeteerSettings.TrackedHealingBuffs
|
|
local _, count = GetAuraSlots(unit, "HELPFUL", nil, nil, slotBuffer)
|
|
for index = 1, count or 0 do
|
|
local name, texture, stacks, type, duration, expirationTime, source, _, _, id =
|
|
UnitAuraBySlot(unit, slotBuffer[index])
|
|
if name then
|
|
if trackedHealingBuffs[name] then
|
|
self.HasHealingModifier = true
|
|
end
|
|
local buff = GetEntry(buffEntries, getn(buffs) + 1)
|
|
buff.name = name
|
|
buff.index = index
|
|
buff.filter = "HELPFUL"
|
|
buff.texture = texture
|
|
buff.stacks = stacks or 0
|
|
buff.type = type or ""
|
|
buff.id = id
|
|
buff.time = ApplyClassicTime(buff, duration, expirationTime, source, time) or FindTrackedTime(self, id, time)
|
|
local nameArray = buffsMap[name]
|
|
if not nameArray then
|
|
nameArray = GetArray()
|
|
buffsMap[name] = nameArray
|
|
end
|
|
if id ~= nil then
|
|
buffsIDSet[id] = true
|
|
end
|
|
table.insert(nameArray, buff)
|
|
table.insert(buffs, buff)
|
|
end
|
|
end
|
|
|
|
local afflictedDebuffTypes = self.AfflictedDebuffTypes
|
|
local trackedDebuffTypes = self.TrackedDebuffTypes
|
|
local debuffs = self.Debuffs
|
|
local debuffsMap = self.DebuffsMap
|
|
local debuffsIDSet = self.DebuffsIDSet
|
|
local typedDebuffs = self.TypedDebuffs -- Dispellable debuffs
|
|
local debuffEntries = self.DebuffEntries
|
|
local trackedHealingDebuffs = PuppeteerSettings.TrackedHealingDebuffs
|
|
local importantDebuffs = PuppeteerSettings.ImportantDebuffs
|
|
local ignoredDispellableDebuffs = PuppeteerSettings.IgnoredDispellableDebuffs
|
|
-- HARMFUL also returns debuffs the server moved into free buff slots, which UnitDebuff misses
|
|
_, count = GetAuraSlots(unit, "HARMFUL", nil, nil, slotBuffer)
|
|
for index = 1, count or 0 do
|
|
local name, texture, stacks, type, duration, expirationTime, source, _, _, id =
|
|
UnitAuraBySlot(unit, slotBuffer[index])
|
|
if name then
|
|
type = type or ""
|
|
if trackedHealingDebuffs[name] then
|
|
self.HasHealingModifier = true
|
|
end
|
|
if importantDebuffs[name] then
|
|
self.HasImportantDebuff = true
|
|
end
|
|
local debuff = GetEntry(debuffEntries, getn(debuffs) + 1)
|
|
debuff.name = name
|
|
debuff.index = index
|
|
debuff.filter = "HARMFUL"
|
|
debuff.texture = texture
|
|
debuff.stacks = stacks or 0
|
|
debuff.type = type
|
|
debuff.id = id
|
|
debuff.time = ApplyClassicTime(debuff, duration, expirationTime, source, time) or FindTrackedTime(self, id, time)
|
|
local nameArray = debuffsMap[name]
|
|
if not nameArray then
|
|
nameArray = GetArray()
|
|
debuffsMap[name] = nameArray
|
|
end
|
|
if id ~= nil then
|
|
debuffsIDSet[id] = true
|
|
end
|
|
table.insert(nameArray, debuff)
|
|
if type ~= "" then
|
|
afflictedDebuffTypes[type] = 1
|
|
if not ignoredDispellableDebuffs[name] then
|
|
trackedDebuffTypes[type] = 1
|
|
end
|
|
local typeArray = typedDebuffs[type]
|
|
if not typeArray then
|
|
typeArray = GetArray()
|
|
typedDebuffs[type] = typeArray
|
|
end
|
|
table.insert(typeArray, debuff)
|
|
end
|
|
table.insert(debuffs, debuff)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Re-reads stacks and timers of the scanned auras in place. Returns false if the aura list itself
|
|
-- changed, in which case a full UpdateAuras is needed.
|
|
local function RefreshClassicList(unit, filter, list, now)
|
|
local _, count = GetAuraSlots(unit, filter, nil, nil, slotBuffer)
|
|
count = count or 0
|
|
if count ~= getn(list) then
|
|
return false
|
|
end
|
|
for index = 1, count do
|
|
local name, _, stacks, _, duration, expirationTime, source, _, _, id = UnitAuraBySlot(unit, slotBuffer[index])
|
|
local entry = list[index]
|
|
if not name or entry.id ~= id or entry.index ~= index then
|
|
return false
|
|
end
|
|
entry.stacks = stacks or 0
|
|
local classicTime = ApplyClassicTime(entry, duration, expirationTime, source, now)
|
|
if classicTime then
|
|
entry.time = classicTime
|
|
elseif entry.time == entry.classicTime then
|
|
entry.time = nil
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
-- For UNIT_AURA_DELTA events that only refreshed auras (new duration or stack count)
|
|
function PTUnit:RefreshClassicAuras()
|
|
local unit = self.Unit
|
|
if not classicAuras or not unit or self == PTUnit or not self.AurasPopulated then
|
|
return false
|
|
end
|
|
local now = GetTime()
|
|
return RefreshClassicList(unit, "HELPFUL", self.Buffs, now) and RefreshClassicList(unit, "HARMFUL", self.Debuffs, now)
|
|
end
|
|
|
|
function PTUnit:UpdateAuras()
|
|
local unit = self.Unit
|
|
|
|
if not unit then
|
|
return
|
|
end
|
|
|
|
Puppeteer.StartTiming("PTUnitAuraUpdate")
|
|
|
|
self:ClearAuras()
|
|
|
|
if not UnitExists(unit) then
|
|
Puppeteer.EndTiming("PTUnitAuraUpdate")
|
|
return
|
|
end
|
|
|
|
local time = GetTime()
|
|
|
|
if classicAuras then
|
|
self:ScanClassicAuras(unit, time)
|
|
self.AurasPopulated = true
|
|
util.ClearTable(claimedAuras)
|
|
Puppeteer.EndTiming("PTUnitAuraUpdate")
|
|
return
|
|
end
|
|
|
|
local buffs = self.Buffs
|
|
local buffsMap = self.BuffsMap
|
|
local buffsIDSet = self.BuffsIDSet
|
|
local buffEntries = self.BuffEntries
|
|
for index = 1, 32 do
|
|
local texture, stacks, id = UnitBuff(unit, index)
|
|
if not texture then
|
|
break
|
|
end
|
|
local name, type = GetAuraInfo(unit, index, "Buff", id)
|
|
if PuppeteerSettings.TrackedHealingBuffs[name] then
|
|
self.HasHealingModifier = true
|
|
end
|
|
local auraTime = FindTrackedTime(self, id, time)
|
|
local buff = GetEntry(buffEntries, index)
|
|
buff.name = name
|
|
buff.index = index
|
|
buff.texture = texture
|
|
buff.stacks = stacks
|
|
buff.type = type
|
|
buff.id = id
|
|
buff.time = auraTime
|
|
local nameArray = buffsMap[name]
|
|
if not nameArray then
|
|
nameArray = GetArray()
|
|
buffsMap[name] = nameArray
|
|
end
|
|
if id ~= nil then
|
|
buffsIDSet[id] = true
|
|
end
|
|
table.insert(nameArray, buff)
|
|
table.insert(buffs, buff)
|
|
end
|
|
|
|
local afflictedDebuffTypes = self.AfflictedDebuffTypes
|
|
local trackedDebuffTypes = self.TrackedDebuffTypes
|
|
local debuffs = self.Debuffs
|
|
local debuffsMap = self.DebuffsMap
|
|
local debuffsIDSet = self.DebuffsIDSet
|
|
local typedDebuffs = self.TypedDebuffs -- Dispellable debuffs
|
|
local debuffEntries = self.DebuffEntries
|
|
for index = 1, 16 do
|
|
local texture, stacks, type, id = UnitDebuff(unit, index)
|
|
if not texture then
|
|
break
|
|
end
|
|
type = type or ""
|
|
local name = GetAuraInfo(unit, index, "Debuff", id)
|
|
if PuppeteerSettings.TrackedHealingDebuffs[name] then
|
|
self.HasHealingModifier = true
|
|
end
|
|
if PuppeteerSettings.ImportantDebuffs[name] then
|
|
self.HasImportantDebuff = true
|
|
end
|
|
local auraTime = FindTrackedTime(self, id, time)
|
|
local debuff = GetEntry(debuffEntries, index)
|
|
debuff.name = name
|
|
debuff.index = index
|
|
debuff.texture = texture
|
|
debuff.stacks = stacks
|
|
debuff.type = type
|
|
debuff.id = id
|
|
debuff.time = auraTime
|
|
local nameArray = debuffsMap[name]
|
|
if not nameArray then
|
|
nameArray = GetArray()
|
|
debuffsMap[name] = nameArray
|
|
end
|
|
if id ~= nil then
|
|
debuffsIDSet[id] = true
|
|
end
|
|
table.insert(nameArray, debuff)
|
|
if type ~= "" then
|
|
afflictedDebuffTypes[type] = 1
|
|
if not PuppeteerSettings.IgnoredDispellableDebuffs[name] then
|
|
trackedDebuffTypes[type] = 1
|
|
end
|
|
local typeArray = typedDebuffs[type]
|
|
if not typeArray then
|
|
typeArray = GetArray()
|
|
typedDebuffs[type] = typeArray
|
|
end
|
|
table.insert(typeArray, debuff)
|
|
end
|
|
table.insert(debuffs, debuff)
|
|
end
|
|
self.AurasPopulated = true
|
|
util.ClearTable(claimedAuras)
|
|
Puppeteer.EndTiming("PTUnitAuraUpdate")
|
|
end
|
|
|
|
function PTUnit:HasBuff(name)
|
|
return self.BuffsMap[name] ~= nil
|
|
end
|
|
|
|
-- SuperWoW/WoW 1.18.1 Clients only
|
|
function PTUnit:HasBuffID(id)
|
|
return self.BuffsIDSet[id] ~= nil
|
|
end
|
|
|
|
-- Looks for ID if SuperWoW/WoW 1.18.1 Clients is present, otherwise searches by name
|
|
function PTUnit:HasBuffIDOrName(id, name)
|
|
if canGetAuraIDs then
|
|
return self:HasBuffID(id)
|
|
end
|
|
return self:HasBuff(name)
|
|
end
|
|
|
|
function PTUnit:HasDebuff(name)
|
|
return self.DebuffsMap[name] ~= nil
|
|
end
|
|
|
|
-- SuperWoW/WoW 1.18.1 Clients only
|
|
function PTUnit:HasDebuffID(id)
|
|
return self.DebuffsIDSet[id] ~= nil
|
|
end
|
|
|
|
-- Looks for ID if SuperWoW/WoW 1.18.1 Clients is present, otherwise searches by name
|
|
function PTUnit:HasDebuffIDOrName(id, name)
|
|
if canGetAuraIDs then
|
|
return self:HasDebuffID(id)
|
|
end
|
|
return self:HasDebuff(name)
|
|
end
|
|
|
|
function PTUnit:HasDebuffType(type)
|
|
return self.AfflictedDebuffTypes[type]
|
|
end
|
|
|
|
function PTUnit:HasTrackedDebuffType(type)
|
|
return self.TrackedDebuffTypes[type]
|
|
end
|
|
|
|
-- Returns the first buff with the provided name
|
|
function PTUnit:GetBuff(name)
|
|
if not self:HasBuff(name) then
|
|
return
|
|
end
|
|
return self.BuffsMap[name][1]
|
|
end
|
|
|
|
-- Returns the table of all buffs with the provided name
|
|
function PTUnit:GetBuffs(name)
|
|
return self.BuffsMap[name]
|
|
end
|
|
|
|
function PTUnit:GetDebuff(name)
|
|
if not self:HasDebuff(name) then
|
|
return
|
|
end
|
|
return self.DebuffsMap[name][1]
|
|
end
|
|
|
|
function PTUnit:GetDebuffs(name)
|
|
return self.DebuffsMap[name]
|
|
end
|
|
|
|
function PTUnit:ApplyTimedAura(spellName, spellID, owner, duration, isNampower)
|
|
if not self.AuraTimes[spellID] then
|
|
self.AuraTimes[spellID] = compost:GetTable()
|
|
end
|
|
if not self.AuraTimesByName[spellName] then
|
|
self.AuraTimesByName[spellName] = compost:GetTable()
|
|
end
|
|
duration = duration or 0
|
|
local time = GetTime()
|
|
local entry = compost:AcquireHash(
|
|
"startTime", time,
|
|
"endTime", time + duration,
|
|
"duration", duration,
|
|
"owner", owner,
|
|
"ownerName", UnitName(owner),
|
|
"nampower", isNampower ~= nil and isNampower ~= false
|
|
)
|
|
if self.AuraTimes[spellID][owner] then
|
|
compost:Reclaim(self.AuraTimes[spellID][owner])
|
|
end
|
|
self.AuraTimes[spellID][owner] = entry
|
|
self.AuraTimesByName[spellName] = entry
|
|
|
|
self:UpdateAuras()
|
|
for ui in Puppeteer.UnitFrames(self.Unit) do
|
|
ui:UpdateAuras()
|
|
end
|
|
end
|
|
|
|
function PTUnit:GetAuraTimeRemaining(name)
|
|
local auraTime = superwow and self.AuraTimesByName[name] or nil
|
|
if not auraTime and classicAuras then -- ClassicAPI times are kept on the aura entries
|
|
local array = self.BuffsMap[name] or self.DebuffsMap[name]
|
|
local entry = array and array[1]
|
|
auraTime = entry and entry.time
|
|
end
|
|
if not auraTime then
|
|
return
|
|
end
|
|
return auraTime.startTime - GetTime() + auraTime.duration
|
|
end |