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:
+166
-36
@@ -223,12 +223,13 @@ function PTUnitFrame:UpdateSight()
|
||||
end
|
||||
end
|
||||
|
||||
local preciseDistance = util.CanClientGetPreciseDistance()
|
||||
local CanClientGetPreciseDistance = util.CanClientGetPreciseDistance -- Read on use: ClassicAPI may provide it once options load
|
||||
local RANGE_PREFIX_FAR = GetColorPrefix(0.75, 0.75, 0.75)
|
||||
local RANGE_PREFIX_OUT = GetColorPrefix(1, 0.3, 0.3)
|
||||
local RANGE_PREFIX_NEAR = GetColorPrefix(1, 0.6, 0)
|
||||
function PTUnitFrame:UpdateRangeText()
|
||||
local dist = ceil(self.distance)
|
||||
local preciseDistance = CanClientGetPreciseDistance()
|
||||
local prefix
|
||||
if dist >= (preciseDistance and self:GetShowDistanceThreshold() or 28) and dist < 9999 then
|
||||
if dist > 80 then
|
||||
@@ -855,7 +856,7 @@ local AURA_DURATION_TEXT_FLASH_THRESHOLD = 5
|
||||
local AURA_DURATION_TEXT_LOW_THRESHOLD = 30
|
||||
-- A map of all seconds below the flash threshold to an array of colors to interpolate
|
||||
local durationTextFlashColorsRange
|
||||
if util.IsSuperWowPresent() then
|
||||
do -- Built for every client, since ClassicAPI can enable timers after this file loads
|
||||
local flashColorsReset = {{1, 1, 0.75}, {1, 0.7, 0.55}, {1, 0.6, 0.6}}
|
||||
local flashColors = {{1, 1, 0.25}, {1, 0.6, 0.35}, {1, 0.4, 0.4}}
|
||||
|
||||
@@ -872,6 +873,96 @@ if util.IsSuperWowPresent() then
|
||||
durationTextFlashColorsRange[seconds] = {colors[2], colors[1]}
|
||||
end
|
||||
end
|
||||
-- Whole-second strings for the duration text, so a ticking timer doesn't convert a number every second
|
||||
local secondsTexts = {}
|
||||
for i = 0, 60 do
|
||||
secondsTexts[i] = tostring(i)
|
||||
end
|
||||
|
||||
-- Aura timers drawn by ClassicAPI's native Cooldown frames. The sweep animates in the client; only the
|
||||
-- duration text is updated here, by one shared driver instead of a script on every icon.
|
||||
local nativeCooldownsUsable -- nil until the first try
|
||||
local function CreateNativeCooldown(parent)
|
||||
if nativeCooldownsUsable == false or not util.IsClassicAPIActive("Cooldown") then
|
||||
return nil
|
||||
end
|
||||
local ok, cooldown = pcall(CreateFrame, "Cooldown", nil, parent, "CooldownFrameTemplate")
|
||||
if not ok or not cooldown or not cooldown.SetCooldown or not cooldown.SetDrawBling
|
||||
or not cooldown.SetHideCountdownNumbers then
|
||||
nativeCooldownsUsable = false
|
||||
if ok and cooldown then
|
||||
cooldown:Hide()
|
||||
end
|
||||
return nil
|
||||
end
|
||||
nativeCooldownsUsable = true
|
||||
cooldown.native = true
|
||||
cooldown:SetDrawBling(false) -- No flash each time an aura runs out
|
||||
cooldown:SetHideCountdownNumbers(true) -- Puppeteer draws its own text
|
||||
return cooldown
|
||||
end
|
||||
|
||||
local activeAuraTimers = {} -- Key: aura component with a native cooldown | Value: true
|
||||
local auraTimerDriver = CreateFrame("Frame", "PTAuraTimerDriver")
|
||||
|
||||
local function UpdateNativeTimerText(component, now)
|
||||
local duration = component.duration
|
||||
local durationText = component.durationText
|
||||
local secondsPrecise = duration.timerEnd - now
|
||||
if secondsPrecise <= 0 then
|
||||
durationText:SetSeconds(nil)
|
||||
activeAuraTimers[component] = nil
|
||||
return
|
||||
end
|
||||
local seconds = floor(secondsPrecise)
|
||||
if seconds <= (duration.displayAt or AURA_DURATION_TEXT_FLASH_THRESHOLD) then
|
||||
local changed = durationText.seconds ~= seconds
|
||||
if changed then
|
||||
durationText:SetSeconds(seconds)
|
||||
end
|
||||
if seconds <= AURA_DURATION_TEXT_FLASH_THRESHOLD then
|
||||
durationText:SetTextColor(
|
||||
util.InterpolateColorsNoTable(durationTextFlashColorsRange[seconds], secondsPrecise - seconds))
|
||||
elseif changed then
|
||||
if seconds <= AURA_DURATION_TEXT_LOW_THRESHOLD then
|
||||
durationText:SetTextColor(1, 1, 0.25)
|
||||
else
|
||||
durationText:SetTextColor(1, 1, 1)
|
||||
end
|
||||
end
|
||||
elseif durationText.seconds ~= nil then
|
||||
durationText:SetSeconds(nil)
|
||||
end
|
||||
end
|
||||
|
||||
local function AuraTimerDriver_OnUpdate()
|
||||
local now = GetTime()
|
||||
for component in pairs(activeAuraTimers) do
|
||||
if component.frame:IsVisible() then
|
||||
UpdateNativeTimerText(component, now)
|
||||
end
|
||||
end
|
||||
if next(activeAuraTimers) == nil then
|
||||
auraTimerDriver:SetScript("OnUpdate", nil)
|
||||
end
|
||||
end
|
||||
|
||||
local function StartNativeTimer(component)
|
||||
activeAuraTimers[component] = true
|
||||
if not auraTimerDriver:GetScript("OnUpdate") then
|
||||
auraTimerDriver:SetScript("OnUpdate", AuraTimerDriver_OnUpdate)
|
||||
end
|
||||
UpdateNativeTimerText(component, GetTime())
|
||||
end
|
||||
|
||||
-- Hides an aura icon's timer sweep and text
|
||||
local function ClearAuraTimer(component)
|
||||
activeAuraTimers[component] = nil
|
||||
component.durationText:SetSeconds(nil)
|
||||
CooldownFrame_SetTimer(component.duration, 0, 0, 0)
|
||||
component.hasTimer = nil
|
||||
end
|
||||
|
||||
-- "Nm" duration strings, cached so they aren't rebuilt every time a timer ticks
|
||||
local minutesTexts = {}
|
||||
function GetMinutesText(minutes)
|
||||
@@ -896,9 +987,9 @@ function PTUnitFrame:AllocateAura()
|
||||
local stackText = frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
|
||||
stackText:SetTextColor(1, 1, 1)
|
||||
|
||||
-- Duration display, only used when SuperWoW is present
|
||||
if util.IsSuperWowPresent() then
|
||||
local duration = CreateFrame("Model", nil, frame, "CooldownFrameTemplate")
|
||||
-- Duration display, only used when SuperWoW or ClassicAPI provides aura times
|
||||
if util.HasAuraTimers() then
|
||||
local duration = CreateNativeCooldown(frame) or CreateFrame("Model", nil, frame, "CooldownFrameTemplate")
|
||||
duration.noCooldownCount = true
|
||||
duration:SetAlpha(0.8)
|
||||
local durationOverlayFrame = CreateFrame("Frame", nil, frame)
|
||||
@@ -911,7 +1002,7 @@ function PTUnitFrame:AllocateAura()
|
||||
self:SetText("")
|
||||
return
|
||||
end
|
||||
self:SetText(seconds <= 60 and seconds or GetMinutesText(ceil(seconds / 60)))
|
||||
self:SetText(seconds <= 60 and secondsTexts[seconds] or GetMinutesText(ceil(seconds / 60)))
|
||||
local fontSize = ceil(frame:GetHeight() * (seconds < 540 and (seconds < 10 and 0.6 or 0.45) or 0.35))
|
||||
if fontSize ~= self.fontSize then
|
||||
self.fontSize = fontSize
|
||||
@@ -933,6 +1024,10 @@ function PTUnitFrame:AllocateAura()
|
||||
end
|
||||
duration:SetScript("OnUpdate", nil)
|
||||
end
|
||||
if duration.native then
|
||||
return {["frame"] = frame, ["icon"] = icon, ["border"] = border, ["stackText"] = stackText,
|
||||
["overlay"] = durationOverlayFrame, ["durationText"] = durationText, ["duration"] = duration}
|
||||
end
|
||||
local SetSequenceTime = duration.SetSequenceTime
|
||||
local GetTime = GetTime
|
||||
duration:SetScript("OnUpdateModel", function()
|
||||
@@ -1075,9 +1170,8 @@ function PTUnitFrame:ReleaseAuras()
|
||||
table.insert(self.auraButtonPool, button)
|
||||
end
|
||||
|
||||
if util.IsSuperWowPresent() then
|
||||
aura.durationText:SetSeconds(nil)
|
||||
CooldownFrame_SetTimer(aura.duration, 0, 0, 0)
|
||||
if aura.duration then
|
||||
ClearAuraTimer(aura)
|
||||
end
|
||||
|
||||
table.insert(self.auraIconPool, aura)
|
||||
@@ -1108,10 +1202,13 @@ local enemyDebuffSorter = function(a, b)
|
||||
if not a.time and not b.time then
|
||||
return a.name > b.name
|
||||
end
|
||||
if UnitIsUnit(a.time.owner, "player") and not UnitIsUnit(b.time.owner, "player") then
|
||||
-- ClassicAPI times have no owner when the cast wasn't seen
|
||||
local aMine = a.time.owner ~= nil and UnitIsUnit(a.time.owner, "player")
|
||||
local bMine = b.time.owner ~= nil and UnitIsUnit(b.time.owner, "player")
|
||||
if aMine and not bMine then
|
||||
return true
|
||||
end
|
||||
if not UnitIsUnit(a.time.owner, "player") and UnitIsUnit(b.time.owner, "player") then
|
||||
if not aMine and bMine then
|
||||
return false
|
||||
end
|
||||
return (a.time.startTime + a.time.duration) < (b.time.startTime + b.time.duration)
|
||||
@@ -1177,7 +1274,7 @@ function PTUnitFrame:UpdateAuras()
|
||||
if not enemy then
|
||||
table.sort(debuffs, self.DebuffSorter)
|
||||
util.AppendArrayElements(debuffs, typedDebuffs)
|
||||
elseif util.IsSuperWowPresent() then
|
||||
elseif util.HasAuraTimers() then
|
||||
table.sort(debuffs, enemyDebuffSorter)
|
||||
end
|
||||
|
||||
@@ -1224,7 +1321,10 @@ function PTUnitFrame:ApplyAuraTooltip(auraFrame)
|
||||
tooltip:SetOwner(auraFrame, "ANCHOR_BOTTOMLEFT")
|
||||
local unit = self:GetResolvedUnit()
|
||||
if auraData then
|
||||
if type == "Buff" then
|
||||
if auraData.filter and tooltip.SetUnitAura then
|
||||
-- ClassicAPI auras are indexed in C_UnitAuras order, read from the cache's unit
|
||||
tooltip:SetUnitAura(cache.Unit or unit, index, auraData.filter)
|
||||
elseif type == "Buff" then
|
||||
tooltip:SetUnitBuff(unit, index)
|
||||
else
|
||||
tooltip:SetUnitDebuff(unit, index)
|
||||
@@ -1298,6 +1398,11 @@ function PTUnitFrame:CreateAura(component, aura, xOffset, yOffset, type, size)
|
||||
frame.auraData = aura
|
||||
frame.auraIndex = aura.index
|
||||
frame.auraType = type
|
||||
-- Kept so RefreshAuras can update the icon in place
|
||||
component.xOffset = xOffset
|
||||
component.yOffset = yOffset
|
||||
component.auraKind = type
|
||||
component.size = size
|
||||
|
||||
local icon = component.icon
|
||||
icon:SetAllPoints(frame)
|
||||
@@ -1306,24 +1411,30 @@ function PTUnitFrame:CreateAura(component, aura, xOffset, yOffset, type, size)
|
||||
local button = component.button
|
||||
button:SetAllPoints(frame)
|
||||
|
||||
if util.IsSuperWowPresent() then
|
||||
local durationUI = component.duration
|
||||
if durationUI then
|
||||
local overlay = component.overlay
|
||||
overlay:SetAllPoints()
|
||||
|
||||
local duration = component.duration
|
||||
duration:SetAllPoints()
|
||||
duration:SetScale(size * 0.0275)
|
||||
durationUI:SetAllPoints()
|
||||
if not durationUI.native then
|
||||
durationUI:SetScale(size * 0.0275)
|
||||
end
|
||||
end
|
||||
|
||||
local stackText = component.stackText
|
||||
if stacks > 1 then
|
||||
local stackText = component.stackText
|
||||
stackText:SetPoint("CENTER", frame, "CENTER", 0, 0)
|
||||
stackText:SetFont("Fonts\\FRIZQT__.TTF", math.ceil(size * (stacks < 10 and 0.75 or 0.6)))
|
||||
stackText:SetText(stacks)
|
||||
else
|
||||
stackText:SetText("")
|
||||
end
|
||||
|
||||
-- ClassicAPI times have no owner when the cast wasn't seen
|
||||
local castByPlayer = aura.time ~= nil and aura.time.owner ~= nil and UnitIsUnit(aura.time.owner, "player")
|
||||
if type == "Buff" then
|
||||
if selfBorderedBuffs[aura.name] and aura.time and UnitIsUnit(aura.time.owner, "player") then
|
||||
if selfBorderedBuffs[aura.name] and castByPlayer then
|
||||
local border = component.border
|
||||
border:Show()
|
||||
border:SetVertexColor(1, 1, 0)
|
||||
@@ -1334,34 +1445,53 @@ function PTUnitFrame:CreateAura(component, aura, xOffset, yOffset, type, size)
|
||||
local border = component.border
|
||||
border:Show()
|
||||
local color = debuffTypeBorderColors[aura.type] or debuffTypeBorderColors["Other"]
|
||||
if aura.time and UnitIsUnit(aura.time.owner, "player") and self:IsEnemy() then
|
||||
if castByPlayer and self:IsEnemy() then
|
||||
border:SetVertexColor(1, 1, 0)
|
||||
else
|
||||
border:SetVertexColor(color[1], color[2], color[3])
|
||||
end
|
||||
end
|
||||
|
||||
if aura.time then
|
||||
local startTime = aura.time.startTime
|
||||
local endTime = aura.time.endTime
|
||||
local duration = aura.time.duration
|
||||
if endTime - GetTime() > -4 then -- Don't display duration if the predicted time has lapsed
|
||||
local durationUI = component.duration
|
||||
if not durationUI then
|
||||
return
|
||||
end
|
||||
local auraTime = aura.time
|
||||
if auraTime and auraTime.endTime - GetTime() > -4 then -- Don't display duration if the predicted time has lapsed
|
||||
local startTime = auraTime.startTime
|
||||
local duration = auraTime.duration
|
||||
|
||||
CooldownFrame_SetTimer(durationUI, startTime, duration, 1)
|
||||
CooldownFrame_SetTimer(durationUI, startTime, duration, 1)
|
||||
component.hasTimer = true
|
||||
|
||||
if duration < 60 then
|
||||
durationUI.displayAt = PTOptions.ShowAuraTimesAt.Short
|
||||
elseif duration <= 60 * 2 then
|
||||
durationUI.displayAt = PTOptions.ShowAuraTimesAt.Medium
|
||||
else
|
||||
durationUI.displayAt = PTOptions.ShowAuraTimesAt.Long
|
||||
end
|
||||
if duration < 60 then
|
||||
durationUI.displayAt = PTOptions.ShowAuraTimesAt.Short
|
||||
elseif duration <= 60 * 2 then
|
||||
durationUI.displayAt = PTOptions.ShowAuraTimesAt.Medium
|
||||
else
|
||||
durationUI.displayAt = PTOptions.ShowAuraTimesAt.Long
|
||||
end
|
||||
|
||||
-- To prevent having a frame where the duration is not updated
|
||||
component.durationText:SetSeconds(nil)
|
||||
-- To prevent having a frame where the duration is not updated
|
||||
component.durationText:SetSeconds(nil)
|
||||
if durationUI.native then
|
||||
durationUI.timerEnd = startTime + duration
|
||||
StartNativeTimer(component)
|
||||
else
|
||||
util.CallWithThis(durationUI, durationUI:GetScript("OnUpdateModel"))
|
||||
end
|
||||
elseif component.hasTimer then -- A refreshed aura that lost its time
|
||||
ClearAuraTimer(component)
|
||||
end
|
||||
end
|
||||
|
||||
-- Updates the shown aura icons in place, for auras that were only refreshed (new duration or stack count).
|
||||
-- The aura list itself must be unchanged; otherwise use UpdateAuras.
|
||||
function PTUnitFrame:RefreshAuras()
|
||||
for _, component in ipairs(self.auraIcons) do
|
||||
local aura = component.frame.auraData
|
||||
if aura and component.size then
|
||||
self:CreateAura(component, aura, component.xOffset, component.yOffset, component.auraKind, component.size)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user