Files
Brues 22633aa16c Replace GetSpellRecField name/rank reads with C_Spell equivalents
Swap 166 call sites from GetSpellRecField(id, "name") to
C_Spell.GetSpellName(id) and GetSpellRecField(id, "rank") to
C_Spell.GetSpellSubtext(id) across Core, Conditionals, Utility,
ComboPointTracker, CursiveCustomSpells, pfUI, OverflowBuffFrame, and
Generic. Guard forms and the _GetSpellRecField alias calls collapse to the
direct C_Spell call.

GetSpellRecField stays for fields with no C_Spell equivalent (school,
spellIconID, mechanic, effectMechanic, effectApplyAuraName, stackAmount,
rangeIndex) and inside the NampowerAPI wrapper layer.
2026-07-26 14:07:31 -05:00

816 lines
28 KiB
Lua

--[[
OverflowBuffFrame Extension
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
License: MIT License
]]
local _G = _G or getfenv(0)
local CleveRoids = _G.CleveRoids or {}
-- ============================================================================
-- Constants
-- ============================================================================
local ICON_SIZE = 24
local ICON_SPACING = 2
local ICONS_PER_ROW = 8
local NUM_ROWS = 2
local ICONS_PER_FRAME = ICONS_PER_ROW * NUM_ROWS -- 16
local ROW_SPACING = 16 -- vertical gap between rows (icon + duration text)
local DURATION_FONT_SIZE = 10
local DURATION_OFFSET_Y = -3
local UPDATE_INTERVAL = 0.2
local LABEL_HEIGHT = 12
-- ============================================================================
-- State
-- ============================================================================
local playerFrame = nil
local targetFrame = nil
local playerIcons = {} -- [1..16]
local targetIcons = {} -- [1..16]
local playerLabelFs = nil
local targetLabelFs = nil
local updateFrame = nil
local updateElapsed = 0
local testMode = false
local dirty = false -- Flag for deferred rebuild (avoids event ordering race)
local lastPlayerCount = 0
local lastTargetCount = 0
-- ============================================================================
-- Duration Formatting (matches pfUI api.lua:1324 style)
-- ============================================================================
local function FormatDuration(seconds)
if not seconds or seconds <= 0 then return "" end
if seconds >= 100 then
return math.floor(seconds / 60 + 0.5) .. "m"
elseif seconds > 5 then
return math.floor(seconds + 0.5) .. "s"
else
return string.format("%.1f", seconds)
end
end
-- Forward declarations (needed by OnClick closures in CreateIconButton)
local RebuildFrame
-- ============================================================================
-- Icon Button Creation
-- ============================================================================
local function CreateIconButton(parent, index, iconTable)
local btn = CreateFrame("Button", nil, parent)
btn:SetWidth(ICON_SIZE)
btn:SetHeight(ICON_SIZE)
btn:RegisterForClicks("RightButtonUp")
-- Dark 1px border backdrop (pfUI style)
btn:SetBackdrop({
bgFile = "Interface\\Buttons\\WHITE8x8",
edgeFile = "Interface\\Buttons\\WHITE8x8",
tile = false, tileSize = 0, edgeSize = 1,
insets = { left = 0, right = 0, top = 0, bottom = 0 },
})
btn:SetBackdropColor(0, 0, 0, 0.6)
btn:SetBackdropBorderColor(0.1, 0.1, 0.1, 0.9)
-- Icon texture (inset by 1px for border)
local icon = btn:CreateTexture(nil, "ARTWORK")
icon:SetPoint("TOPLEFT", btn, "TOPLEFT", 1, -1)
icon:SetPoint("BOTTOMRIGHT", btn, "BOTTOMRIGHT", -1, 1)
icon:SetTexCoord(0.07, 0.93, 0.07, 0.93)
icon:Hide()
-- Duration text below icon
local dur = btn:CreateFontString(nil, "OVERLAY")
dur:SetFont("Fonts\\FRIZQT__.TTF", DURATION_FONT_SIZE, "OUTLINE")
dur:SetPoint("TOP", btn, "BOTTOM", 0, DURATION_OFFSET_Y)
dur:SetJustifyH("CENTER")
dur:SetTextColor(1, 1, 1, 1)
dur:SetText("")
-- Right-click to cancel player overflow buffs
btn:SetScript("OnClick", function()
local data = iconTable[index]
if not data or not data.spellId then return end
if data.source ~= "player" then return end
-- CancelPlayerAuraSpellId(spellId, ignoreMissing)
-- ignoreMissing=1 is required for overflow buffs that have no client aura slot
if CancelPlayerAuraSpellId then
CancelPlayerAuraSpellId(data.spellId, 1)
end
-- Remove from overflow tracking
if CleveRoids.OverflowBuffs then
CleveRoids.OverflowBuffs[data.spellId] = nil
end
-- Hide tooltip and rebuild
GameTooltip:Hide()
RebuildFrame()
end)
-- Tooltip
btn:SetScript("OnEnter", function()
local data = iconTable[index]
if not data or (not data.spellId and not data.displayName) then return end
GameTooltip:SetOwner(btn, "ANCHOR_BOTTOMLEFT")
local spellName
if data.spellId then
spellName = C_Spell.GetSpellName(data.spellId) or ("Spell " .. data.spellId)
else
spellName = data.displayName
end
if spellName then
local baseName = CleveRoids.StripRank(spellName)
GameTooltip:AddLine(baseName, 1, 1, 1)
end
if data.remaining and data.remaining > 0 then
GameTooltip:AddLine(FormatDuration(data.remaining) .. " remaining", 0.7, 0.7, 0.7)
end
if data.source == "player" then
GameTooltip:AddLine("Right-click to cancel", 0.8, 0.8, 0.5)
else
GameTooltip:AddLine("Overflow buff (target)", 0.5, 1.0, 0.8)
end
GameTooltip:Show()
end)
btn:SetScript("OnLeave", function()
GameTooltip:Hide()
end)
btn:Hide()
return {
button = btn,
icon = icon,
duration = dur,
spellId = nil,
remaining = nil,
source = nil,
}
end
-- ============================================================================
-- Frame Creation
-- ============================================================================
-- Compute frame dimensions
local FRAME_WIDTH = ICONS_PER_ROW * (ICON_SIZE + ICON_SPACING) - ICON_SPACING
local FRAME_HEIGHT = LABEL_HEIGHT + NUM_ROWS * (ICON_SIZE + ROW_SPACING)
local function CreateBuffFrame(name, savedPosKey, defaultY, labelColor, iconTable)
local frame = CreateFrame("Frame", name, UIParent)
frame:SetWidth(FRAME_WIDTH)
frame:SetHeight(FRAME_HEIGHT)
frame:SetPoint("TOP", UIParent, "TOP", 0, defaultY)
frame:SetFrameStrata("MEDIUM")
frame:SetMovable(true)
frame:EnableMouse(false)
frame:Hide()
-- Section label
local label = frame:CreateFontString(nil, "OVERLAY")
label:SetFont("Fonts\\FRIZQT__.TTF", 9, "OUTLINE")
label:SetPoint("TOPLEFT", frame, "TOPLEFT", 0, 0)
label:SetTextColor(labelColor[1], labelColor[2], labelColor[3], 0.8)
label:SetText("")
-- Create 16 icon slots in 2 rows of 8
for i = 1, ICONS_PER_FRAME do
iconTable[i] = CreateIconButton(frame, i, iconTable)
local row = math.floor((i - 1) / ICONS_PER_ROW)
local col = math.mod(i - 1, ICONS_PER_ROW)
local xOff = col * (ICON_SIZE + ICON_SPACING)
local yOff = -(LABEL_HEIGHT + row * (ICON_SIZE + ROW_SPACING))
iconTable[i].button:SetPoint("TOPLEFT", frame, "TOPLEFT", xOff, yOff)
end
-- Shift+drag to move
frame:SetScript("OnMouseDown", function()
if IsShiftKeyDown() then
frame:StartMoving()
end
end)
frame:SetScript("OnMouseUp", function()
frame:StopMovingOrSizing()
local _, _, _, x, y = frame:GetPoint(1)
CleveRoidMacros = CleveRoidMacros or {}
CleveRoidMacros[savedPosKey] = { x = x, y = y }
end)
-- Restore saved position
CleveRoidMacros = CleveRoidMacros or {}
if CleveRoidMacros[savedPosKey] then
frame:ClearAllPoints()
frame:SetPoint("TOP", UIParent, "TOP",
CleveRoidMacros[savedPosKey].x or 0,
CleveRoidMacros[savedPosKey].y or defaultY)
end
return frame, label
end
-- ============================================================================
-- Data Collection
-- ============================================================================
local function GetPlayerOverflowBuffs()
local results = {}
local overflowBuffs = CleveRoids.OverflowBuffs
if not overflowBuffs then return results end
-- If the player is no longer buff-capped (< 32 buff slots occupied),
-- overflow entries are stale — the server doesn't migrate overflow buffs
-- into freed slots, so they've expired. Wipe and return early.
if not testMode and _G.GetPlayerAuraDuration then
local buffSlotCount = 0
for slot = 0, 31 do
local sid = _G.GetPlayerAuraDuration(slot)
if sid and sid > 0 then
buffSlotCount = buffSlotCount + 1
end
end
if buffSlotCount < 32 then
for k in pairs(overflowBuffs) do
overflowBuffs[k] = nil
end
-- Also wipe combat log fallback entries
if CleveRoids.OverflowBuffsByName then
for k in pairs(CleveRoids.OverflowBuffsByName) do
CleveRoids.OverflowBuffsByName[k] = nil
end
end
return results
end
end
-- Build set of spell IDs in debuff slots (32-47) to filter out debuffs
-- that were incorrectly added due to AURA_CAST_ON_SELF race condition
-- (debuff slot not yet assigned when the event fired)
local debuffSpellIds
if _G.GetPlayerAuraDuration then
for slot = 32, 47 do
local sid = _G.GetPlayerAuraDuration(slot)
if sid and sid > 0 then
if not debuffSpellIds then debuffSpellIds = {} end
debuffSpellIds[sid] = true
end
end
end
local now = GetTime()
for spellId, entry in pairs(overflowBuffs) do
-- v3.0+: Skip hidden auras (not real overflow)
if _G.IsAuraHidden and _G.IsAuraHidden(spellId) == 1 then
-- Hidden aura, don't show in overflow UI
elseif debuffSpellIds and debuffSpellIds[spellId] then
-- Debuff in a visible debuff slot — not an overflow buff, clean up
overflowBuffs[spellId] = nil
elseif entry.timestamp then
local permanent = not entry.durationSec or entry.durationSec <= 0
if permanent then
table.insert(results, {
spellId = spellId,
remaining = 0,
source = "player",
})
else
local remaining = entry.durationSec - (now - entry.timestamp)
if remaining > 0 then
table.insert(results, {
spellId = spellId,
remaining = remaining,
source = "player",
})
end
end
end
end
-- Include combat log fallback entries (name-based, no spell ID)
if CleveRoids.OverflowBuffsByName then
for lowerName, entry in pairs(CleveRoids.OverflowBuffsByName) do
if entry.timestamp then
local elapsed = now - entry.timestamp
if entry.durationSec and entry.durationSec > 0 and elapsed > entry.durationSec then
CleveRoids.OverflowBuffsByName[lowerName] = nil
else
local rem = entry.durationSec and entry.durationSec > 0
and (entry.durationSec - elapsed) or 0
if rem > 0 or not entry.durationSec or entry.durationSec <= 0 then
table.insert(results, {
spellId = nil,
remaining = rem > 0 and rem or 0,
source = "player",
displayName = entry.displayName or lowerName,
})
end
end
end
end
end
table.sort(results, function(a, b) return a.remaining > b.remaining end)
return results
end
local function GetTargetOverflowBuffs()
local results = {}
if not UnitExists("target") then return results end
if not testMode and not (UnitInParty("target") or UnitInRaid("target")) then return results end
local targetGuid = CleveRoids.GetGUID("target")
if not targetGuid then return results end
local trackingData, isPfUI = CleveRoids.GetAuraTrackingData(targetGuid)
if not trackingData then return results end
-- Only show overflow for targets that are buff-capped (all 32 buff slots occupied).
-- GetUnitField "aura" returns spell IDs for all 48 slots including hidden auras,
-- so counting occupied buff slots 1-32 gives the true buff count in real-time.
-- Without this gate, AllCasterAuraTracking (which stores ALL aura casts, not
-- just overflow) would show false-positive overflow entries on uncapped targets.
if not testMode then
local buffSlotCount = 0
local auras = _G.GetUnitField and _G.GetUnitField("target", "aura")
if auras then
for slot = 1, 32 do
if auras[slot] and auras[slot] > 0 then
buffSlotCount = buffSlotCount + 1
end
end
if buffSlotCount < 32 then return results end
else
-- Fallback: auraCapStatus from last AURA_CAST_ON_OTHER event
local capEntry = CleveRoids.AuraCapStatus.targetCapStatus[targetGuid]
if not capEntry or not capEntry.buffCapped then return results end
end
end
-- Build set of visible aura spell IDs on target (buffs + debuffs)
-- Use spellId matching instead of texture matching for accuracy:
-- texture matching fails when icon isn't cached (false positive) or when
-- two different buffs share the same icon (false negative).
local visibleSpellIds = {}
for i = 1, 32 do
local texture, _, spellId = UnitBuff("target", i)
if not texture then break end
if spellId then
visibleSpellIds[spellId] = true
end
end
for i = 1, 16 do
local texture, _, _, spellId = UnitDebuff("target", i)
if not texture then break end
if spellId then
visibleSpellIds[spellId] = true
end
end
local now = GetTime()
for spellName, casters in pairs(trackingData) do
-- Find best entry across all casters for this spell
local bestEntry = nil
local bestRemaining = 0
for casterGuid, auraData in pairs(casters) do
local startTime = isPfUI and auraData.startTime or auraData.start
if startTime and auraData.duration then
local remaining = auraData.duration + startTime - now
if remaining > bestRemaining then
bestRemaining = remaining
bestEntry = auraData
end
end
end
if bestEntry and bestRemaining > 0 then
-- Our table stores .spellId directly; pfUI doesn't, so resolve from name
local spellId = bestEntry.spellId
or (_G.GetSpellIdForName and _G.GetSpellIdForName(spellName))
-- v3.0+: Skip hidden auras (not real overflow)
if not (_G.IsAuraHidden and _G.IsAuraHidden(spellId) == 1) then
local isVisible = bestEntry._testEntry == nil and visibleSpellIds[spellId]
if not isVisible then
table.insert(results, {
spellId = spellId,
remaining = bestRemaining,
source = "target",
})
end
end
end
end
table.sort(results, function(a, b) return a.remaining > b.remaining end)
return results
end
-- ============================================================================
-- Layout
-- ============================================================================
local function PopulateFrame(frame, label, labelText, iconTable, buffs, countRef)
local count = table.getn(buffs)
if count > ICONS_PER_FRAME then count = ICONS_PER_FRAME end
local lib = CleveRoids.libdebuff
for i = 1, count do
local data = iconTable[i]
local buff = buffs[i]
local texture = lib and lib:GetCachedIcon(buff.spellId)
if texture then
data.icon:SetTexture(texture)
else
data.icon:SetTexture("Interface\\Icons\\INV_Misc_QuestionMark")
end
data.icon:Show()
data.spellId = buff.spellId
data.displayName = buff.displayName
data.remaining = buff.remaining
data.source = buff.source
data.duration:SetText(FormatDuration(buff.remaining))
data.button:EnableMouse(true)
data.button:Show()
end
-- Hide unused
for i = count + 1, ICONS_PER_FRAME do
local data = iconTable[i]
data.button:Hide()
data.icon:Hide()
data.duration:SetText("")
data.spellId = nil
data.displayName = nil
data.remaining = nil
data.source = nil
end
if count > 0 then
label:SetText(labelText)
frame:EnableMouse(true)
frame:Show()
else
label:SetText("")
frame:EnableMouse(false)
frame:Hide()
end
return count
end
RebuildFrame = function()
if not playerFrame then return end
local enabled = testMode or (CleveRoidMacros and CleveRoidMacros.overflowPlayerEnabled)
if enabled then
local pBuffs = GetPlayerOverflowBuffs()
lastPlayerCount = PopulateFrame(playerFrame, playerLabelFs,
"SuperCleveRoid Overflow Buffs (You) *shift+drag*", playerIcons, pBuffs)
else
playerFrame:Hide()
playerFrame:EnableMouse(false)
playerLabelFs:SetText("")
for i = 1, ICONS_PER_FRAME do
if playerIcons[i] then
playerIcons[i].button:Hide()
end
end
lastPlayerCount = 0
end
if enabled then
local tBuffs = GetTargetOverflowBuffs()
local targetName = UnitExists("target") and UnitName("target") or "Target"
lastTargetCount = PopulateFrame(targetFrame, targetLabelFs, "SuperCleveRoid Overflow Buffs (" .. targetName .. ") *shift+drag*", targetIcons, tBuffs)
else
targetFrame:Hide()
targetFrame:EnableMouse(false)
targetLabelFs:SetText("")
for i = 1, ICONS_PER_FRAME do
if targetIcons[i] then
targetIcons[i].button:Hide()
end
end
lastTargetCount = 0
end
end
-- Refresh only duration text (no full rebuild)
local function RefreshDurations()
local anyExpired = false
local now = GetTime()
-- Player durations
if playerFrame and playerFrame:IsVisible() then
for i = 1, lastPlayerCount do
local data = playerIcons[i]
if data.spellId then
local entry = CleveRoids.OverflowBuffs and CleveRoids.OverflowBuffs[data.spellId]
if entry and entry.timestamp then
local permanent = not entry.durationSec or entry.durationSec <= 0
if permanent then
data.remaining = 0
data.duration:SetText("")
else
local remaining = entry.durationSec - (now - entry.timestamp)
if remaining > 0 then
data.remaining = remaining
data.duration:SetText(FormatDuration(remaining))
else
anyExpired = true
end
end
else
anyExpired = true
end
end
end
end
-- Target durations
if targetFrame and targetFrame:IsVisible() then
local targetGuid = CleveRoids.GetGUID("target")
for i = 1, lastTargetCount do
local data = targetIcons[i]
if data.spellId and targetGuid then
local remaining = CleveRoids.GetAllCasterAuraTimeRemaining(targetGuid, data.spellId) or 0
if remaining > 0 then
data.remaining = remaining
data.duration:SetText(FormatDuration(remaining))
else
anyExpired = true
end
else
anyExpired = true
end
end
end
if anyExpired then
RebuildFrame()
end
end
-- ============================================================================
-- Test Mode
-- ============================================================================
local TEST_PLAYER_SPELL_IDS = {
1126, -- Mark of the Wild
1243, -- Power Word: Fortitude
1461, -- Arcane Intellect
14752, -- Divine Spirit
976, -- Shadow Protection
6307, -- Blood Pact
20217, -- Blessing of Kings
19740, -- Blessing of Might
21849, -- Gift of the Wild
25898, -- Greater Blessing of Kings
25899, -- Greater Blessing of Sanctuary
25890, -- Greater Blessing of Light
10938, -- Greater Power Word: Fortitude
10157, -- Greater Arcane Intellect
20911, -- Blessing of Sanctuary
25782, -- Greater Blessing of Might
}
local TEST_TARGET_SPELL_IDS = {
467, -- Thorns
10060, -- Power Infusion
17007, -- Leader of the Pack
24932, -- Leader of the Pack aura
19506, -- Trueshot Aura
8936, -- Regrowth
774, -- Rejuvenation
139, -- Renew
1022, -- Blessing of Protection
6346, -- Fear Ward
1044, -- Blessing of Freedom
10958, -- Shadow Protection (rank)
27681, -- Prayer of Spirit
21562, -- Prayer of Fortitude
20914, -- Blessing of Sanctuary (rank)
25916, -- Greater Blessing of Wisdom
}
local testTargetGuid = nil
local testStartTime = 0 -- when test mode was enabled (for duration calc)
-- Remove test entries from a specific GUID in AllCasterAuraTracking
local function ClearTestTargetData(guid)
if not guid or not CleveRoids.AllCasterAuraTracking then return end
local targetData = CleveRoids.AllCasterAuraTracking[guid]
if not targetData then return end
for spellName, casters in pairs(targetData) do
for casterGuid, entry in pairs(casters) do
if entry._testEntry then
casters[casterGuid] = nil
end
end
if not next(casters) then
targetData[spellName] = nil
end
end
if not next(targetData) then
CleveRoids.AllCasterAuraTracking[guid] = nil
end
end
-- Inject test entries for the current target
local function InjectTestTargetData()
if not UnitExists("target") then return end
local targetGuid = CleveRoids.GetGUID("target")
if not targetGuid then return end
-- Clean up old test target if it changed
if testTargetGuid and testTargetGuid ~= targetGuid then
ClearTestTargetData(testTargetGuid)
end
testTargetGuid = targetGuid
CleveRoids.AllCasterAuraTracking = CleveRoids.AllCasterAuraTracking or {}
if not CleveRoids.AllCasterAuraTracking[targetGuid] then
CleveRoids.AllCasterAuraTracking[targetGuid] = {}
end
local targetDurations = { 8, 15, 25, 40, 55, 90, 150, 240, 400, 500, 700, 1000, 1300, 1600, 2000, 3000 }
for i = 1, table.getn(TEST_TARGET_SPELL_IDS) do
local spellId = TEST_TARGET_SPELL_IDS[i]
local dur = targetDurations[i] or 60
local spellName = C_Spell.GetSpellName(spellId) or ("TestSpell" .. spellId)
if not CleveRoids.AllCasterAuraTracking[targetGuid][spellName] then
CleveRoids.AllCasterAuraTracking[targetGuid][spellName] = {}
end
CleveRoids.AllCasterAuraTracking[targetGuid][spellName]["test"] = {
start = testStartTime,
duration = dur,
spellId = spellId,
_testEntry = true,
}
end
end
local function EnableTestMode()
testMode = true
testStartTime = GetTime()
CleveRoids.OverflowBuffs = CleveRoids.OverflowBuffs or {}
local playerDurations = { 5, 10, 30, 45, 60, 120, 180, 300, 450, 600, 900, 1200, 1500, 1800, 2400, 3600 }
for i = 1, table.getn(TEST_PLAYER_SPELL_IDS) do
local spellId = TEST_PLAYER_SPELL_IDS[i]
local dur = playerDurations[i] or 60
CleveRoids.OverflowBuffs[spellId] = {
timestamp = testStartTime,
durationSec = dur,
_testEntry = true,
}
end
-- Inject target test data if we have a target
InjectTestTargetData()
CleveRoids.Print("|cff00ff00Overflow buff frame test mode enabled|r")
CleveRoids.Print(" Shift+drag to reposition the frames")
RebuildFrame()
end
local function DisableTestMode()
testMode = false
if CleveRoids.OverflowBuffs then
for spellId, entry in pairs(CleveRoids.OverflowBuffs) do
if entry._testEntry then
CleveRoids.OverflowBuffs[spellId] = nil
end
end
end
ClearTestTargetData(testTargetGuid)
testTargetGuid = nil
CleveRoids.Print("|cffff9900Overflow buff frame test mode disabled|r")
RebuildFrame()
end
local function ToggleTestMode()
if testMode then
DisableTestMode()
else
EnableTestMode()
end
end
CleveRoids.ToggleOverflowTest = ToggleTestMode
CleveRoids.RebuildOverflowFrame = function() RebuildFrame() end
CleveRoids.ResetOverflowFramePositions = function()
if not playerFrame or not targetFrame then return end
CleveRoidMacros = CleveRoidMacros or {}
CleveRoidMacros.overflowPlayerPos = nil
CleveRoidMacros.overflowTargetPos = nil
playerFrame:ClearAllPoints()
playerFrame:SetPoint("TOP", UIParent, "TOP", 0, -100)
targetFrame:ClearAllPoints()
targetFrame:SetPoint("TOP", UIParent, "TOP", 0, -170)
CleveRoids.Print("Overflow frame positions reset to defaults")
end
-- ============================================================================
-- OnUpdate Handler
-- ============================================================================
local function OnUpdate()
updateElapsed = updateElapsed + arg1
-- Check dirty flag every frame for responsive rebuilds after AURA_CAST events.
-- By the time OnUpdate fires, the core handler has already updated OverflowBuffs.
if dirty then
dirty = false
updateElapsed = 0
RebuildFrame()
return
end
if updateElapsed < UPDATE_INTERVAL then return end
updateElapsed = 0
RefreshDurations()
end
-- ============================================================================
-- Extension Registration
-- ============================================================================
local ext = CleveRoids.RegisterExtension("OverflowBuffFrame")
ext.OnTargetChanged = function()
-- Re-inject test data for the new target during test mode
if testMode then
InjectTestTargetData()
end
RebuildFrame()
end
ext.OnAuraCast = function()
-- Defer rebuild to OnUpdate: the core handler that populates OverflowBuffs
-- registers for AURA_CAST_ON_SELF later (PLAYER_ENTERING_WORLD) than this
-- extension (ADDON_LOADED), so it fires AFTER us. A direct RebuildFrame()
-- here would read stale data.
dirty = true
end
ext.OnGroupChanged = function()
RebuildFrame()
end
ext.OnPlayerDead = function()
if CleveRoids.OverflowBuffs then
if testMode then
for spellId, entry in pairs(CleveRoids.OverflowBuffs) do
if not entry._testEntry then
CleveRoids.OverflowBuffs[spellId] = nil
end
end
else
CleveRoids.OverflowBuffs = {}
end
end
RebuildFrame()
end
ext.OnLoad = function()
playerFrame, playerLabelFs = CreateBuffFrame(
"CleveRoidsOverflowPlayer", "overflowPlayerPos", -100,
{ 0.4, 0.7, 1.0 }, playerIcons)
targetFrame, targetLabelFs = CreateBuffFrame(
"CleveRoidsOverflowTarget", "overflowTargetPos", -170,
{ 0.4, 1.0, 0.7 }, targetIcons)
ext.RegisterEvent("PLAYER_TARGET_CHANGED", "OnTargetChanged")
ext.RegisterEvent("PARTY_MEMBERS_CHANGED", "OnGroupChanged")
ext.RegisterEvent("RAID_ROSTER_UPDATE", "OnGroupChanged")
ext.RegisterEvent("PLAYER_DEAD", "OnPlayerDead")
if CleveRoids.hasNampower then
ext.RegisterEvent("AURA_CAST_ON_SELF", "OnAuraCast")
end
-- Shared OnUpdate frame
updateFrame = CreateFrame("Frame")
updateFrame:SetScript("OnUpdate", OnUpdate)
RebuildFrame()
end
_G["CleveRoids"] = CleveRoids