mirror of
https://github.com/brues-code/SuperCleveRoidMacros.git
synced 2026-09-16 03:38:00 +00:00
initial overflowtest
This commit is contained in:
+195
-1
@@ -849,6 +849,14 @@ CleveRoids.AuraCapStatus = {
|
||||
lastCleanupTime = 0,
|
||||
}
|
||||
|
||||
-- Overflow buff tracking: buffs applied while buff-capped (v2.34+)
|
||||
-- The server maintains up to 48 buff slots (32 visible + 16 invisible overflow).
|
||||
-- Buffs in slots 33-48 are mechanically active but have no client aura slot,
|
||||
-- so GetPlayerBuff/GetPlayerAuraDuration can't see them.
|
||||
-- Populated from AURA_CAST_ON_SELF when auraCapStatus indicates buff bar full.
|
||||
-- Format: [spellId] = { timestamp = GetTime(), durationSec = durationMs/1000 }
|
||||
CleveRoids.OverflowBuffs = {}
|
||||
|
||||
-- All-caster aura duration tracking (from AURA_CAST events)
|
||||
-- Tracks buff/debuff durations from ANY caster, not just player
|
||||
-- Structure: [targetGuid][spellId] = { start, duration, casterGuid }
|
||||
@@ -994,10 +1002,68 @@ local function OnAuraCastSelf(spellId, casterGuid, targetGuid, effect, effectAur
|
||||
local now = GetTime()
|
||||
CleveRoids.AuraCapStatus.playerLastUpdate = now
|
||||
|
||||
local buffCapped = HasHitFlag(auraCapStatus, AURA_CAP_BUFF_FULL)
|
||||
|
||||
-- Check buff bar full (bit 1)
|
||||
CleveRoids.AuraCapStatus.playerBuffCapped = HasHitFlag(auraCapStatus, AURA_CAP_BUFF_FULL)
|
||||
CleveRoids.AuraCapStatus.playerBuffCapped = buffCapped
|
||||
-- Check debuff bar full (bit 2)
|
||||
CleveRoids.AuraCapStatus.playerDebuffCapped = HasHitFlag(auraCapStatus, AURA_CAP_DEBUFF_FULL)
|
||||
|
||||
-- Track overflow buffs: if buff bar is full, this aura may land in server slots 33-48
|
||||
-- (no client aura slot). Store spellId + duration so /cancelaura and [mybuff] can find it.
|
||||
-- But first verify it's actually a buff — debuffs applied while buff-capped go into
|
||||
-- debuff slots (32-47), not buff overflow. Player debuffs never overflow into buff slots.
|
||||
if buffCapped and spellId and spellId > 0 and effectAuraName and effectAuraName > 0 then
|
||||
-- Check debuff slots (32-47) — if the spellId is there, it's a debuff, not overflow
|
||||
local isDebuff = false
|
||||
if _G.GetPlayerAuraDuration then
|
||||
for slot = 32, 47 do
|
||||
local sid = _G.GetPlayerAuraDuration(slot)
|
||||
if sid and sid == spellId then
|
||||
isDebuff = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not isDebuff then
|
||||
local entry = CleveRoids.OverflowBuffs[spellId]
|
||||
if not entry then
|
||||
entry = {}
|
||||
CleveRoids.OverflowBuffs[spellId] = entry
|
||||
end
|
||||
entry.timestamp = now
|
||||
entry.durationSec = durationMs and (durationMs / 1000) or 0
|
||||
end
|
||||
elseif not buffCapped and next(CleveRoids.OverflowBuffs) then
|
||||
-- No longer buff-capped: some overflow buffs may have gotten real slots.
|
||||
-- Only remove entries that now appear in a visible aura slot.
|
||||
-- The server does NOT auto-migrate overflow buffs into freed slots,
|
||||
-- so we can't blindly clear everything.
|
||||
if _G.GetPlayerAuraDuration then
|
||||
local visibleSpells = {}
|
||||
for slot = 0, 31 do
|
||||
local sid = _G.GetPlayerAuraDuration(slot)
|
||||
if sid and sid > 0 then
|
||||
visibleSpells[sid] = true
|
||||
end
|
||||
end
|
||||
for k in pairs(CleveRoids.OverflowBuffs) do
|
||||
if visibleSpells[k] then
|
||||
CleveRoids.OverflowBuffs[k] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Also prune expired entries
|
||||
for k, entry in pairs(CleveRoids.OverflowBuffs) do
|
||||
if entry.durationSec and entry.durationSec > 0 then
|
||||
local elapsed = now - (entry.timestamp or 0)
|
||||
if elapsed > entry.durationSec then
|
||||
CleveRoids.OverflowBuffs[k] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function OnAuraCastOther(spellId, casterGuid, targetGuid, effect, effectAuraName,
|
||||
@@ -1663,6 +1729,58 @@ end
|
||||
function CleveRoids.CancelAura(auraName)
|
||||
local ix = 0
|
||||
auraName = string.lower(string.gsub(auraName, "_"," "))
|
||||
|
||||
-- v2.34+ path: cancel by spell ID (works for buff-capped overflow auras too)
|
||||
local API = CleveRoids.NampowerAPI
|
||||
if API and API.features.hasCancelPlayerAuraSpellId and CleveRoids.hasSuperwow then
|
||||
-- First scan visible buffs via GetPlayerBuff (fast, covers normal case)
|
||||
while true do
|
||||
local aura_ix = GetPlayerBuff(ix, "HELPFUL")
|
||||
ix = ix + 1
|
||||
if aura_ix == -1 then break end
|
||||
local bid = GetPlayerBuffID(aura_ix)
|
||||
bid = (bid < -1) and (bid + 65536) or bid
|
||||
if string.lower(SpellInfo(bid)) == auraName then
|
||||
_G.CancelPlayerAuraSpellId(bid, 1)
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
-- Not found in visible buffs - scan all 32 raw aura slots
|
||||
-- GetPlayerAuraDuration reads unit data fields directly (same 32 slots but bypasses UI filtering)
|
||||
if API.features.hasGetPlayerAuraDuration and _G.GetPlayerAuraDuration then
|
||||
for slot = 0, 31 do
|
||||
local spellId = _G.GetPlayerAuraDuration(slot)
|
||||
if spellId and spellId > 0 then
|
||||
local name = SpellInfo(spellId)
|
||||
if name and string.lower(name) == auraName then
|
||||
_G.CancelPlayerAuraSpellId(spellId, 1)
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Final fallback: check overflow buff tracking (buffs applied while buff-capped
|
||||
-- that have NO client aura slot - tracked via AURA_CAST_ON_SELF events)
|
||||
for spellId, entry in pairs(CleveRoids.OverflowBuffs) do
|
||||
-- Skip expired overflow entries
|
||||
local elapsed = GetTime() - (entry.timestamp or 0)
|
||||
if entry.durationSec and entry.durationSec > 0 and elapsed > entry.durationSec then
|
||||
CleveRoids.OverflowBuffs[spellId] = nil
|
||||
else
|
||||
local name = SpellInfo(spellId)
|
||||
if name and string.lower(name) == auraName then
|
||||
_G.CancelPlayerAuraSpellId(spellId, 1)
|
||||
CleveRoids.OverflowBuffs[spellId] = nil
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- Legacy path for older Nampower versions
|
||||
while true do
|
||||
local aura_ix = GetPlayerBuff(ix,"HELPFUL")
|
||||
ix = ix + 1
|
||||
@@ -3281,6 +3399,82 @@ function CleveRoids.ValidateAura(unit, args, isbuff)
|
||||
end
|
||||
end
|
||||
|
||||
-- Overflow buff fallback: player buffs in server slots 33-48 (no client slot)
|
||||
-- Check AURA_CAST_ON_SELF tracked overflow data for presence + duration
|
||||
if not found and isPlayer and isbuff and next(CleveRoids.OverflowBuffs) then
|
||||
local now = GetTime()
|
||||
for spellId, entry in pairs(CleveRoids.OverflowBuffs) do
|
||||
-- Prune expired entries
|
||||
local elapsed = now - (entry.timestamp or 0)
|
||||
if entry.durationSec and entry.durationSec > 0 and elapsed > entry.durationSec then
|
||||
CleveRoids.OverflowBuffs[spellId] = nil
|
||||
else
|
||||
local match = false
|
||||
if searchID then
|
||||
match = (spellId == searchID)
|
||||
elseif searchName then
|
||||
local lowerName = GetLowercaseSpellName(spellId)
|
||||
match = (lowerName and lowerName == searchName)
|
||||
end
|
||||
if match then
|
||||
found = true
|
||||
stacks = 0
|
||||
-- Compute remaining time from apply timestamp + duration
|
||||
if entry.durationSec and entry.durationSec > 0 then
|
||||
remaining = entry.durationSec - elapsed
|
||||
if remaining < 0 then remaining = 0 end
|
||||
else
|
||||
remaining = -1 -- Unknown duration (permanent buff)
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Non-player overflow fallback: buff exists in server slots 33-48 (no client slot)
|
||||
-- AllCasterAuraTracking already has data from AURA_CAST_ON_OTHER for all aura applications.
|
||||
-- If the normal scan didn't find the buff, check there for presence + duration.
|
||||
-- Guard: verify the spell isn't a visible debuff on the target (AllCasterAuraTracking
|
||||
-- stores both buffs and debuffs, so without this check [buff:DebuffName] could false-positive).
|
||||
if not found and not isPlayer and isbuff and (searchID or searchName) then
|
||||
local _, targetGuid = UnitExists(unit)
|
||||
if targetGuid then
|
||||
-- Check if the spell is in a visible debuff slot — if so, it's a debuff, not a buff
|
||||
local isDebuff = false
|
||||
local di = 1
|
||||
while true do
|
||||
local dtex, _, _, dspellId = UnitDebuff(unit, di)
|
||||
if not dtex then break end
|
||||
if dspellId then
|
||||
if searchID then
|
||||
if dspellId == searchID then
|
||||
isDebuff = true
|
||||
break
|
||||
end
|
||||
elseif searchName then
|
||||
local lowerName = GetLowercaseSpellName(dspellId)
|
||||
if lowerName and lowerName == searchName then
|
||||
isDebuff = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
di = di + 1
|
||||
end
|
||||
|
||||
if not isDebuff then
|
||||
local trackRemaining = CleveRoids.FindAllCasterAuraByName(targetGuid,
|
||||
searchID and tostring(searchID) or args.name)
|
||||
if trackRemaining then
|
||||
found = true
|
||||
stacks = 0
|
||||
remaining = trackRemaining
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local ops = CleveRoids.operators
|
||||
local cmp = CleveRoids.comparators
|
||||
|
||||
|
||||
@@ -3181,16 +3181,15 @@ function CleveRoids.EquipBagItem(msg, slotOrOffhand)
|
||||
end
|
||||
end
|
||||
|
||||
-- Item not found via v2.18 lookup
|
||||
-- Item not found via v2.18 lookup - fall through to legacy path
|
||||
if CleveRoids.equipDebugLog then
|
||||
CleveRoids.Print("|cffff8800[EquipLog] Item '" .. tostring(msg) .. "' not found via v2.18 FindPlayerItemSlot|r")
|
||||
CleveRoids.Print("|cffff8800[EquipLog] Item '" .. tostring(msg) .. "' not found via v2.18 FindPlayerItemSlot, trying legacy fallback|r")
|
||||
-- Debug: Try direct FindPlayerItemSlot call
|
||||
if FindPlayerItemSlot then
|
||||
local bag, slot = FindPlayerItemSlot(msg)
|
||||
CleveRoids.Print("|cff888888[EquipLog] Direct FindPlayerItemSlot('" .. msg .. "') = bag:" .. tostring(bag) .. " slot:" .. tostring(slot) .. "|r")
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- Fallback for older Nampower versions
|
||||
@@ -3294,13 +3293,25 @@ function CleveRoids.EquipBagItem(msg, slotOrOffhand)
|
||||
if EquipItemByName then
|
||||
local ok = pcall(EquipItemByName, msg, invslot)
|
||||
if ok then
|
||||
-- Invalidate cache entry if it exists
|
||||
if CleveRoids.Items then
|
||||
CleveRoids.Items[msg] = nil
|
||||
CleveRoids.Items[string_lower(msg)] = nil
|
||||
-- Verify the item actually landed in the target slot
|
||||
-- EquipItemByName may silently no-op for same-named items in paired slots
|
||||
-- (e.g., dual-wielding Scimitar: MH copy found first, "equipped" to same slot)
|
||||
local newLink = GetInventoryItemLink("player", invslot)
|
||||
if newLink then
|
||||
local _, _, newName = string_find(newLink, "|h%[(.-)%]|h")
|
||||
if newName and string_lower(newName) == string_lower(msg) then
|
||||
if CleveRoids.Items then
|
||||
CleveRoids.Items[msg] = nil
|
||||
CleveRoids.Items[string_lower(msg)] = nil
|
||||
end
|
||||
InvalidateDisplacedItem()
|
||||
return true
|
||||
end
|
||||
end
|
||||
-- Verification failed - EquipItemByName didn't place item in target slot
|
||||
if CleveRoids.equipDebugLog then
|
||||
CleveRoids.Print("|cffff8800[EquipLog] EquipItemByName('" .. msg .. "', " .. invslot .. ") succeeded but item not in target slot, trying fallback|r")
|
||||
end
|
||||
InvalidateDisplacedItem()
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3336,12 +3347,23 @@ function CleveRoids.EquipBagItem(msg, slotOrOffhand)
|
||||
if item.name and EquipItemByName and item.name ~= msg then
|
||||
local ok = pcall(EquipItemByName, item.name, invslot)
|
||||
if ok then
|
||||
if CleveRoids.Items then
|
||||
CleveRoids.Items[item.name] = nil
|
||||
CleveRoids.Items[string_lower(item.name)] = nil
|
||||
-- Verify the item actually landed in the target slot (same guard as above)
|
||||
local newLink = GetInventoryItemLink("player", invslot)
|
||||
if newLink then
|
||||
local _, _, newName = string_find(newLink, "|h%[(.-)%]|h")
|
||||
if newName and string_lower(newName) == string_lower(item.name) then
|
||||
if CleveRoids.Items then
|
||||
CleveRoids.Items[item.name] = nil
|
||||
CleveRoids.Items[string_lower(item.name)] = nil
|
||||
end
|
||||
InvalidateDisplacedItem()
|
||||
return true
|
||||
end
|
||||
end
|
||||
-- Verification failed - fall through to manual cursor-based equip
|
||||
if CleveRoids.equipDebugLog then
|
||||
CleveRoids.Print("|cffff8800[EquipLog] EquipItemByName('" .. item.name .. "', " .. invslot .. ") succeeded but item not in target slot, trying manual fallback|r")
|
||||
end
|
||||
InvalidateDisplacedItem()
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5824,6 +5846,8 @@ SlashCmdList["CLEVEROID"] = function(msg)
|
||||
DEFAULT_CHAT_FRAME:AddMessage('/cleveroid tankdebug - Debug tank targeting conditionals')
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cffffaa00All-Caster Aura Tracking:|r")
|
||||
DEFAULT_CHAT_FRAME:AddMessage('/cleveroid auradebug - Debug buff/debuff time tracking from all casters')
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cffffaa00Overflow Buff Frame:|r")
|
||||
DEFAULT_CHAT_FRAME:AddMessage('/cleveroid overflowtest - Toggle overflow buff frame test mode')
|
||||
return
|
||||
end
|
||||
|
||||
@@ -6559,6 +6583,22 @@ SlashCmdList["CLEVEROID"] = function(msg)
|
||||
return
|
||||
end
|
||||
|
||||
-- overflowtest (toggle overflow buff frame test mode)
|
||||
if cmd == "overflowtest" then
|
||||
if CleveRoids.ToggleOverflowTest then
|
||||
CleveRoids.ToggleOverflowTest()
|
||||
else
|
||||
CleveRoids.Print("|cffff0000OverflowBuffFrame extension not loaded|r")
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
-- Delegate to extension console handlers (chain pattern)
|
||||
if CleveRoids.HandleConsoleCommand then
|
||||
CleveRoids.HandleConsoleCommand(msg)
|
||||
return
|
||||
end
|
||||
|
||||
-- Unknown command fallback
|
||||
CleveRoids.Print("Usage:")
|
||||
DEFAULT_CHAT_FRAME:AddMessage("/cleveroid - Show current settings")
|
||||
|
||||
@@ -29,6 +29,7 @@ local pendingValidation = false
|
||||
local lastSelectedMacro = nil
|
||||
local updateFrame = nil
|
||||
local hooked = false
|
||||
local measureFs = nil -- hidden FontString for measuring text width (wrap detection)
|
||||
|
||||
-- Cache for current errors (avoids re-validation on every frame)
|
||||
local currentErrors = nil
|
||||
@@ -186,18 +187,61 @@ local function UpdateLineHighlights(errors)
|
||||
local editWidth = MacroFrameText:GetWidth()
|
||||
if editWidth < 10 then editWidth = 260 end -- fallback
|
||||
|
||||
-- Create measurement FontString lazily (same font as EditBox, hidden)
|
||||
if not measureFs then
|
||||
measureFs = (MacroFrame or UIParent):CreateFontString(nil, "OVERLAY")
|
||||
measureFs:Hide()
|
||||
end
|
||||
local fontPath, fontSize, fontFlags = MacroFrameText:GetFont()
|
||||
if fontPath then
|
||||
measureFs:SetFont(fontPath, fontSize, fontFlags)
|
||||
end
|
||||
|
||||
-- Split macro text into logical lines for wrap calculation
|
||||
local text = MacroFrameText:GetText() or ""
|
||||
local logicalLines = {}
|
||||
local lineStart = 1
|
||||
while true do
|
||||
local nlPos = string.find(text, "\n", lineStart, true)
|
||||
if nlPos then
|
||||
table.insert(logicalLines, string.sub(text, lineStart, nlPos - 1))
|
||||
lineStart = nlPos + 1
|
||||
else
|
||||
table.insert(logicalLines, string.sub(text, lineStart))
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- Calculate visual line count for each logical line
|
||||
local visualCounts = {}
|
||||
for i, line in ipairs(logicalLines) do
|
||||
if line == "" or editWidth <= 0 then
|
||||
visualCounts[i] = 1
|
||||
else
|
||||
measureFs:SetText(line)
|
||||
local textWidth = measureFs:GetStringWidth()
|
||||
visualCounts[i] = math.max(1, math.ceil(textWidth / editWidth))
|
||||
end
|
||||
end
|
||||
|
||||
local highlightIdx = 1
|
||||
for lineNum, _ in pairs(errorLines) do
|
||||
if highlightIdx > HIGHLIGHT_POOL_SIZE then break end
|
||||
|
||||
local tex = lineHighlights[highlightIdx]
|
||||
-- Position: top text inset + (lineNum-1) lines down
|
||||
local yOffset = -topInset - ((lineNum - 1) * lineHeight)
|
||||
|
||||
-- Y offset: sum visual lines of all preceding logical lines
|
||||
local yLines = 0
|
||||
for i = 1, lineNum - 1 do
|
||||
yLines = yLines + (visualCounts[i] or 1)
|
||||
end
|
||||
local yOffset = -topInset - (yLines * lineHeight)
|
||||
local highlightHeight = (visualCounts[lineNum] or 1) * lineHeight
|
||||
|
||||
tex:ClearAllPoints()
|
||||
tex:SetPoint("TOPLEFT", MacroFrameText, "TOPLEFT", -2, yOffset)
|
||||
tex:SetWidth(editWidth + 4)
|
||||
tex:SetHeight(lineHeight)
|
||||
tex:SetHeight(highlightHeight)
|
||||
tex:Show()
|
||||
|
||||
highlightIdx = highlightIdx + 1
|
||||
|
||||
@@ -0,0 +1,620 @@
|
||||
--[[
|
||||
OverflowBuffFrame Extension
|
||||
Two frames displaying overflow buffs (slots 33-48) for the player
|
||||
and the current target (if a group/party member).
|
||||
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 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 in invisible slots 33-48
|
||||
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 then return end
|
||||
GameTooltip:SetOwner(btn, "ANCHOR_BOTTOMLEFT")
|
||||
local spellName = SpellInfo and SpellInfo(data.spellId) or ("Spell " .. data.spellId)
|
||||
if spellName then
|
||||
local baseName = string.gsub(spellName, "%s*%(Rank %d+%)", "")
|
||||
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
|
||||
|
||||
local now = GetTime()
|
||||
for spellId, entry in pairs(overflowBuffs) do
|
||||
if entry.timestamp and entry.durationSec then
|
||||
local remaining = entry.durationSec - (now - entry.timestamp)
|
||||
if remaining > 0 then
|
||||
table.insert(results, {
|
||||
spellId = spellId,
|
||||
remaining = remaining,
|
||||
source = "player",
|
||||
})
|
||||
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 = UnitExists("target")
|
||||
if not targetGuid then return results end
|
||||
|
||||
local trackingData = CleveRoids.AllCasterAuraTracking
|
||||
if not trackingData or not trackingData[targetGuid] then return results end
|
||||
|
||||
-- Build set of visible buff textures on target
|
||||
local visibleBuffs = {}
|
||||
for i = 1, 32 do
|
||||
local texture = UnitBuff("target", i)
|
||||
if not texture then break end
|
||||
visibleBuffs[texture] = true
|
||||
end
|
||||
|
||||
local now = GetTime()
|
||||
local lib = CleveRoids.libdebuff
|
||||
|
||||
for spellId, auraData in pairs(trackingData[targetGuid]) do
|
||||
if auraData.start and auraData.duration then
|
||||
local remaining = auraData.duration + auraData.start - now
|
||||
if remaining > 0 then
|
||||
local isVisible = false
|
||||
if not auraData._testEntry then
|
||||
local spellIcon = lib and lib:GetCachedIcon(spellId)
|
||||
isVisible = spellIcon and visibleBuffs[spellIcon]
|
||||
end
|
||||
if not isVisible then
|
||||
table.insert(results, {
|
||||
spellId = spellId,
|
||||
remaining = remaining,
|
||||
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.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.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 pBuffs = GetPlayerOverflowBuffs()
|
||||
lastPlayerCount = PopulateFrame(playerFrame, playerLabelFs, "Overflow (You)", playerIcons, pBuffs)
|
||||
|
||||
local tBuffs = GetTargetOverflowBuffs()
|
||||
local targetName = UnitExists("target") and UnitName("target") or "Target"
|
||||
lastTargetCount = PopulateFrame(targetFrame, targetLabelFs, "Overflow (" .. targetName .. ")", targetIcons, tBuffs)
|
||||
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 and entry.durationSec then
|
||||
local remaining = entry.durationSec - (now - entry.timestamp)
|
||||
if remaining > 0 then
|
||||
data.remaining = remaining
|
||||
data.duration:SetText(FormatDuration(remaining))
|
||||
else
|
||||
anyExpired = true
|
||||
end
|
||||
else
|
||||
anyExpired = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Target durations
|
||||
if targetFrame and targetFrame:IsVisible() then
|
||||
local targetGuid = nil
|
||||
if UnitExists("target") then
|
||||
local _, guid = UnitExists("target")
|
||||
targetGuid = guid
|
||||
end
|
||||
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 spellId, entry in pairs(targetData) do
|
||||
if entry._testEntry then
|
||||
targetData[spellId] = 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 = UnitExists("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
|
||||
CleveRoids.AllCasterAuraTracking[targetGuid][spellId] = {
|
||||
start = testStartTime,
|
||||
duration = dur,
|
||||
casterGuid = "test",
|
||||
_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
|
||||
|
||||
-- ============================================================================
|
||||
-- OnUpdate Handler
|
||||
-- ============================================================================
|
||||
local function OnUpdate()
|
||||
updateElapsed = updateElapsed + arg1
|
||||
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()
|
||||
RebuildFrame()
|
||||
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
|
||||
+40
-1
@@ -85,7 +85,13 @@
|
||||
- Reads from NampowerSettings addon when available
|
||||
- Falls back to CVars when addon not present
|
||||
|
||||
Current version: v2.33.0
|
||||
Aura Cancel Functions (v2.34+):
|
||||
- CancelPlayerAuraSlot(auraSlot) - Cancel buff/debuff by raw 0-based aura slot
|
||||
- CancelPlayerAuraSpellId(spellId, [ignoreMissing]) - Cancel buff/debuff by spell ID
|
||||
Pass ignoreMissing=1 to skip aura-slot presence check (for overflow buffs in
|
||||
server slots 33-48 that have no client aura slot)
|
||||
|
||||
Current version: v2.34.0
|
||||
]]
|
||||
|
||||
local _G = _G or getfenv(0)
|
||||
@@ -242,6 +248,10 @@ API.VERSION_REQUIREMENTS = {
|
||||
-- v2.33+ - UPDATE_DURATION spellId parameter and GetCastInfo guid fix
|
||||
["DurationEventSpellId"] = { 2, 33, 0 },
|
||||
["GetCastInfoGuidFix"] = { 2, 33, 0 },
|
||||
|
||||
-- v2.34+ - Aura cancel functions
|
||||
["CancelPlayerAuraSlot"] = { 2, 34, 0, "CancelPlayerAuraSlot" },
|
||||
["CancelPlayerAuraSpellId"] = { 2, 34, 0, "CancelPlayerAuraSpellId" },
|
||||
}
|
||||
|
||||
-- Check if a specific feature is available
|
||||
@@ -382,6 +392,10 @@ local function InitializeFeatures()
|
||||
f.hasDurationEventSpellId = API.HasFeature("DurationEventSpellId")
|
||||
f.hasGetCastInfoGuidFix = API.HasFeature("GetCastInfoGuidFix")
|
||||
|
||||
-- v2.34+ Aura cancel functions
|
||||
f.hasCancelPlayerAuraSlot = API.HasFeature("CancelPlayerAuraSlot")
|
||||
f.hasCancelPlayerAuraSpellId = API.HasFeature("CancelPlayerAuraSpellId")
|
||||
|
||||
-- Runtime detection for enhanced spell functions (verify by testing)
|
||||
if f.hasEnhancedSpellFunctions and GetSpellTexture then
|
||||
local success, result = pcall(function()
|
||||
@@ -2982,5 +2996,30 @@ function API.GetSpellPower(mode)
|
||||
return _G.GetSpellPower(mode)
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- AURA CANCEL FUNCTIONS (v2.34+)
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
-- Cancel a player aura by raw 0-based aura slot (v2.34+)
|
||||
-- auraSlot: 0-31 for buffs, 32-47 for debuffs
|
||||
function API.CancelPlayerAuraSlot(auraSlot)
|
||||
if not API.features.hasCancelPlayerAuraSlot or not _G.CancelPlayerAuraSlot then
|
||||
return false
|
||||
end
|
||||
_G.CancelPlayerAuraSlot(auraSlot)
|
||||
return true
|
||||
end
|
||||
|
||||
-- Cancel a player aura by spell ID (v2.34+)
|
||||
-- spellId: the spell ID to cancel
|
||||
-- ignoreMissing: pass 1 to skip aura-slot presence check (useful for buff-capped hidden auras)
|
||||
function API.CancelPlayerAuraSpellId(spellId, ignoreMissing)
|
||||
if not API.features.hasCancelPlayerAuraSpellId or not _G.CancelPlayerAuraSpellId then
|
||||
return false
|
||||
end
|
||||
_G.CancelPlayerAuraSpellId(spellId, ignoreMissing)
|
||||
return true
|
||||
end
|
||||
|
||||
-- Expose API globally for other addons
|
||||
_G.CleveRoidsNampowerAPI = API
|
||||
|
||||
@@ -43,4 +43,5 @@ Extensions\Mouseover\Cursive.lua
|
||||
|
||||
Extensions\Tooltip\Generic.lua
|
||||
Extensions\CursiveCustomSpells.lua
|
||||
Extensions\OverflowBuffFrame.lua
|
||||
|
||||
|
||||
+19
@@ -4913,6 +4913,25 @@ ev:SetScript("OnEvent", function()
|
||||
CleveRoids.castTracking[guid] = nil
|
||||
end
|
||||
|
||||
-- Clean up overflow buff tracking (death removes all buffs)
|
||||
local _, playerGUID = UnitExists("player")
|
||||
if playerGUID and guid == playerGUID then
|
||||
-- Player died: clear all overflow buff entries and reset cap status
|
||||
if CleveRoids.OverflowBuffs then
|
||||
for k in pairs(CleveRoids.OverflowBuffs) do
|
||||
CleveRoids.OverflowBuffs[k] = nil
|
||||
end
|
||||
end
|
||||
if CleveRoids.AuraCapStatus then
|
||||
CleveRoids.AuraCapStatus.playerBuffCapped = false
|
||||
CleveRoids.AuraCapStatus.playerDebuffCapped = false
|
||||
end
|
||||
end
|
||||
-- Clean up all-caster aura tracking for the dead unit
|
||||
if CleveRoids.AllCasterAuraTracking and CleveRoids.AllCasterAuraTracking[guid] then
|
||||
CleveRoids.AllCasterAuraTracking[guid] = nil
|
||||
end
|
||||
|
||||
-- Clean up GUID to name mapping (after 5 seconds to allow final lookups)
|
||||
-- Actually, keep it for a bit in case we need it for immunity detection
|
||||
-- lib.guidToName[guid] = nil
|
||||
|
||||
Reference in New Issue
Block a user