Optional ClassicAPI support
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
This commit is contained in:
+234
-39
@@ -13,6 +13,11 @@ 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
|
||||
@@ -22,7 +27,8 @@ PTUnit.Cached = {}
|
||||
PTUnit.Unit = nil
|
||||
|
||||
PTUnit.AurasPopulated = false
|
||||
-- Buff/debuff entry contents: {"name", "stacks", "texture", "index", "type", "id"(SuperWoW only)}
|
||||
-- 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
|
||||
@@ -46,6 +52,20 @@ 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
|
||||
@@ -203,24 +223,35 @@ 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)
|
||||
return PTHealPredict.IsBeingResurrected(self.Unit) or hasClassicResurrection(self.Unit)
|
||||
end
|
||||
return Puppeteer.HealComm:UnitisResurrecting(UnitName(self.Unit))
|
||||
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
|
||||
return PTHealPredict.GetResurrectionCount(self.Unit)
|
||||
count = PTHealPredict.GetResurrectionCount(self.Unit)
|
||||
else
|
||||
count = Puppeteer.HealComm:UnitisResurrecting(UnitName(self.Unit)) and 1 or 0
|
||||
end
|
||||
return Puppeteer.HealComm:UnitisResurrecting(UnitName(self.Unit)) and 1 or 0
|
||||
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:
|
||||
@@ -310,6 +341,187 @@ function PTUnit:ClearAuras()
|
||||
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
|
||||
|
||||
@@ -326,11 +538,20 @@ function PTUnit:UpdateAuras()
|
||||
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
|
||||
local time = GetTime()
|
||||
for index = 1, 32 do
|
||||
local texture, stacks, id = UnitBuff(unit, index)
|
||||
if not texture then
|
||||
@@ -340,21 +561,7 @@ function PTUnit:UpdateAuras()
|
||||
if PuppeteerSettings.TrackedHealingBuffs[name] then
|
||||
self.HasHealingModifier = true
|
||||
end
|
||||
local auraTime = nil
|
||||
if id and self.AuraTimes[id] then
|
||||
local auraTimes = self.AuraTimes[id]
|
||||
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
|
||||
end
|
||||
local auraTime = FindTrackedTime(self, id, time)
|
||||
local buff = GetEntry(buffEntries, index)
|
||||
buff.name = name
|
||||
buff.index = index
|
||||
@@ -395,21 +602,7 @@ function PTUnit:UpdateAuras()
|
||||
if PuppeteerSettings.ImportantDebuffs[name] then
|
||||
self.HasImportantDebuff = true
|
||||
end
|
||||
local auraTime = nil
|
||||
if id and self.AuraTimes[id] then
|
||||
local auraTimes = self.AuraTimes[id]
|
||||
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
|
||||
end
|
||||
local auraTime = FindTrackedTime(self, id, time)
|
||||
local debuff = GetEntry(debuffEntries, index)
|
||||
debuff.name = name
|
||||
debuff.index = index
|
||||
@@ -542,10 +735,12 @@ function PTUnit:ApplyTimedAura(spellName, spellID, owner, duration, isNampower)
|
||||
end
|
||||
|
||||
function PTUnit:GetAuraTimeRemaining(name)
|
||||
if not superwow then
|
||||
return
|
||||
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
|
||||
local auraTime = self.AuraTimesByName[name]
|
||||
if not auraTime then
|
||||
return
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user