improve memory and cpu usage and fix some spell debuffs and inject pfui with debuffs we track

This commit is contained in:
Jrc13245
2026-01-04 14:40:19 -05:00
parent 96ae51a228
commit 1a1f2ff65b
4 changed files with 295 additions and 153 deletions
+35 -17
View File
@@ -926,6 +926,24 @@ function CleveRoids.GetSpammableConditional(name)
return CleveRoids.spamConditions[name] or "nomybuff"
end
-- PERFORMANCE: Cache for stripped spell names (removes rank suffix)
local _strippedNameCache = {}
local _strippedCacheSize = 0
local _STRIPPED_CACHE_MAX = 128
local function GetStrippedSpellName(name)
if not name or name == "" then return "" end
local cached = _strippedNameCache[name]
if cached then return cached end
local stripped = string.gsub(name, "%(.-%)%s*", "")
if _strippedCacheSize < _STRIPPED_CACHE_MAX then
_strippedNameCache[name] = stripped
_strippedCacheSize = _strippedCacheSize + 1
end
return stripped
end
-- Checks whether or not we're currently casting a spell with cast time
-- Returns TRUE if we should allow the cast (not casting, or not casting the specified spell)
-- Returns FALSE if we should block the cast (currently casting)
@@ -948,8 +966,9 @@ function CleveRoids.CheckCasting(castingSpell)
end
-- With parameter: check if we're casting a specific spell
local spellName = string.gsub(CleveRoids.CurrentSpell.spellName or "", "%(.-%)%s*", "")
local casting = string.gsub(castingSpell, "%(.-%)%s*", "")
-- PERFORMANCE: Use cached stripped names to avoid string.gsub per call
local spellName = GetStrippedSpellName(CleveRoids.CurrentSpell.spellName)
local casting = GetStrippedSpellName(castingSpell)
-- If we're casting this specific spell, block the recast
if CleveRoids.CurrentSpell.type == "cast" and spellName == casting then
@@ -982,8 +1001,9 @@ function CleveRoids.CheckChanneled(channeledSpell)
end
-- Remove the "(Rank X)" part from the spells name in order to allow downranking
local spellName = string.gsub(CleveRoids.CurrentSpell.spellName or "", "%(.-%)%s*", "")
local channeled = string.gsub(channeledSpell, "%(.-%)%s*", "")
-- PERFORMANCE: Use cached stripped names to avoid string.gsub per call
local spellName = GetStrippedSpellName(CleveRoids.CurrentSpell.spellName)
local channeled = GetStrippedSpellName(channeledSpell)
-- If we're channeling this specific spell, block the recast
if CleveRoids.CurrentSpell.type == "channeled" and spellName == channeled then
@@ -2001,20 +2021,18 @@ function CleveRoids.ValidateUnitDebuff(unit, args)
)
end
-- FALLBACK: If not found in tracking table (e.g., shared debuff from another player),
-- scan actual debuffs on target using UnitDebuff()
-- IMPORTANT: Skip this fallback for personal debuffs - they should only match if player cast them
local isPersonalDebuff = false
if table.getn(matchingSpellIDs) > 0 then
for _, spellID in ipairs(matchingSpellIDs) do
if CleveRoids.libdebuff:IsPersonalDebuff(spellID) then
isPersonalDebuff = true
break
end
end
end
-- FALLBACK: If not found in tracking table, scan actual debuffs on target
-- This handles:
-- 1. Shared debuffs from other players (we only track player's own casts)
-- 2. Personal debuffs during the 0.2s pending delay after casting
-- 3. Any debuffs not yet registered in tracking (edge cases)
--
-- For simple existence checks ([nodebuff]Spell), we want to find the debuff
-- regardless of whether it's in our tracking table. Time-remaining checks
-- will use tracking table data when available, but existence is from scan.
local isSimpleExistenceCheck = not args.operator and not args.amount
if not found and not isPersonalDebuff and CleveRoids.hasSuperwow then
if not found and CleveRoids.hasSuperwow and isSimpleExistenceCheck then
-- Scan debuff slots
for i = 1, 16 do
local tex, debuffStacks, _, debuffSpellID = UnitDebuff(unit, i)
+13 -7
View File
@@ -1934,17 +1934,20 @@ end
function CleveRoids.GetParsedMsg(msg)
if not msg then return end
-- ALWAYS refresh the side-flag for '?' even when we hit the cache
local _, ignorecount = string.gsub(CleveRoids.Trim(msg), "^%?", "")
CleveRoids._ignoretooltip = ignorecount
-- PERFORMANCE: Check cache first before doing string operations
local cached = CleveRoids.ParsedMsg[msg]
if cached then
-- keep a per-msg copy too (helps future readers/tools)
cached.ignoretooltip = cached.ignoretooltip or ignorecount
-- Use cached ignoretooltip value (already computed during first parse)
CleveRoids._ignoretooltip = cached.ignoretooltip or 0
return cached.action, cached.conditionals
end
-- Only compute ignoretooltip for new messages (not cache hits)
-- PERFORMANCE: Use string.sub for simple prefix check instead of gsub
local trimmed = CleveRoids.Trim(msg)
local ignorecount = (string.sub(trimmed, 1, 1) == "?") and 1 or 0
CleveRoids._ignoretooltip = ignorecount
local action, conditionals = CleveRoids.ParseMsg(msg)
CleveRoids.ParsedMsg[msg] = {
action = action,
@@ -2055,13 +2058,16 @@ function CleveRoids.TestAction(cmd, args)
CleveRoids.FixEmptyTarget(conditionals)
for k, v in pairs(conditionals) do
-- PERFORMANCE: Use next() directly instead of pairs() to avoid iterator allocation
local k, v = next(conditionals)
while k do
if not CleveRoids.ignoreKeywords[k] then
if not CleveRoids.Keywords[k] or not CleveRoids.Keywords[k](conditionals) then
conditionals.target = origTarget
return
end
end
k, v = next(conditionals, k)
end
conditionals.target = origTarget
+2 -5
View File
@@ -250,11 +250,8 @@ local function HookCursiveLoadCurses()
-- Call original first
originalLoadCurses(self)
-- Then inject our custom spells
local count = InjectCustomSpells()
if count > 0 then
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00[CursiveCustomSpells]|r Injected " .. count .. " custom spells into Cursive.")
end
-- Then inject our custom spells (silent)
InjectCustomSpells()
end
Cursive.curses._customSpellsHooked = true
+245 -124
View File
@@ -906,12 +906,13 @@ lib.personalDebuffs = lib.personalDebuffs or {
-- NOTE: 51750 is the CAST spell for Turtle WoW, not a debuff (debuff is 51752)
-- SHAMAN
[8050] = 12, -- Flame Shock (Rank 1)
[8052] = 12, -- Flame Shock (Rank 2)
[8053] = 12, -- Flame Shock (Rank 3)
[10447] = 12, -- Flame Shock (Rank 4)
[10448] = 12, -- Flame Shock (Rank 5)
[29228] = 12, -- Flame Shock (Rank 6)
-- NOTE: Flame Shock is 15s in Turtle WoW (was 12s in vanilla)
[8050] = 15, -- Flame Shock (Rank 1)
[8052] = 15, -- Flame Shock (Rank 2)
[8053] = 15, -- Flame Shock (Rank 3)
[10447] = 15, -- Flame Shock (Rank 4)
[10448] = 15, -- Flame Shock (Rank 5)
[29228] = 15, -- Flame Shock (Rank 6)
-- Frost Shock
[8056] = 8, -- Frost Shock (Rank 1)
@@ -1386,6 +1387,36 @@ function lib:AddEffect(guid, unitName, spellID, duration, stacks, caster)
lib.objects[guid][spellID] = rec
-- PFUI INTEGRATION: Inject all tracked debuffs into pfUI's libdebuff
-- This allows pfUI to show accurate timers for all player-cast debuffs
if pfUI and pfUI.api and pfUI.api.libdebuff and unitName and caster == "player" then
local pflib = pfUI.api.libdebuff
local spellName = SpellInfo(spellID)
if spellName and pflib.AddEffect then
-- Get target level for pfUI's tracking structure
local targetLevel = UnitLevel(guid) or UnitLevel("target") or 1
-- Strip rank from spell name for pfUI (it uses base names)
local baseName = string.gsub(spellName, "%s*%(Rank %d+%)", "")
-- Also register the duration in pfUI's duration table
if pflib.debuffs then
pflib.debuffs[baseName] = duration
end
-- Add the effect to pfUI's tracking
pflib:AddEffect(unitName, targetLevel, baseName, duration, "player")
if CleveRoids.debug then
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cff00ff00[pfUI Inject]|r %s (%ds) on %s (level %d)",
baseName, duration, unitName, targetLevel)
)
end
end
end
-- DEBUG: Show what we stored
if CleveRoids.debug then
local spellName = SpellInfo(spellID) or "Unknown"
@@ -2082,37 +2113,89 @@ end
-- Frame for delayed personal debuff tracking and judgement scanning
-- NOTE: Carnage refresh is now handled via PLAYER_COMBO_POINTS event in ComboPointTracker.lua
-- PERFORMANCE: Upvalues for frequently called functions
local _GetTime = GetTime
local _UnitExists = UnitExists
local _UnitDebuff = UnitDebuff
local _UnitIsDead = UnitIsDead
local _UnitName = UnitName
local _SpellInfo = SpellInfo
local _string_find = string.find
local _string_format = string.format
local _string_sub = string.sub
local _tostring = tostring
local _next = next
local _getn = table.getn -- Lua 5.0 compatible table length
-- PERFORMANCE: Static buffers for removal tracking - reused every frame
-- Using mark-and-sweep pattern instead of table.remove() for O(1) removal
local _pendingJudgementsBuffer = {}
local _pendingPersonalBuffer = {}
local _pendingCCBuffer = {}
local _pendingSharedBuffer = {}
-- PERFORMANCE: Throttling - run at 20Hz max instead of every frame (60+Hz)
-- Minimum delay is 0.2s, so 20Hz (50ms) gives us 4 checks per minimum delay
local _lastDelayedTrackingUpdate = 0
local _DELAYED_TRACKING_INTERVAL = 0.05 -- 50ms = 20Hz
local delayedTrackingFrame = CreateFrame("Frame", "CleveRoidsDelayedTrackingFrame", UIParent)
delayedTrackingFrame:SetScript("OnUpdate", function()
-- PERFORMANCE: Throttle updates - most pending items have 0.2-0.5s delays anyway
local currentTime = _GetTime()
if (currentTime - _lastDelayedTrackingUpdate) < _DELAYED_TRACKING_INTERVAL then
return
end
_lastDelayedTrackingUpdate = currentTime
-- PERFORMANCE: Early exit if all queues are empty
local hasJudgements = lib.pendingJudgements and lib.pendingJudgements[1]
local hasPersonal = lib.pendingPersonalDebuffs and lib.pendingPersonalDebuffs[1]
local hasCC = lib.pendingCCDebuffs and lib.pendingCCDebuffs[1]
local hasShared = lib.pendingSharedDebuffs and lib.pendingSharedDebuffs[1]
local hasOverrides = lib.rankRefreshOverrides and _next(lib.rankRefreshOverrides)
if not (hasJudgements or hasPersonal or hasCC or hasShared or hasOverrides) then
return
end
-- Cache frequently accessed values
local hasSuperwow = CleveRoids.hasSuperwow
local debug = CleveRoids.debug
-- Process pending judgement scans to detect actual debuff IDs
if lib.pendingJudgements then
local toRemove = {}
for i, pending in ipairs(lib.pendingJudgements) do
local elapsed = GetTime() - pending.timestamp
if hasJudgements then
local writeIdx = 0
local pendingList = lib.pendingJudgements
for i = 1, _getn(pendingList) do
local pending = pendingList[i]
local elapsed = currentTime - pending.timestamp
-- Scan target after 0.5 seconds to find the actual judgement debuff
if elapsed >= 0.5 then
-- Check if this is still the current target
local _, currentTargetGUID = UnitExists("target")
local _, currentTargetGUID = _UnitExists("target")
currentTargetGUID = CleveRoids.NormalizeGUID(currentTargetGUID)
if currentTargetGUID == pending.targetGUID and CleveRoids.hasSuperwow then
if currentTargetGUID == pending.targetGUID and hasSuperwow then
-- Scan all debuffs on target to find judgement-type debuffs
for slot = 1, 16 do
local _, _, _, debuffSpellID = UnitDebuff("target", slot)
local _, _, _, debuffSpellID = _UnitDebuff("target", slot)
if not debuffSpellID then break end
local debuffName = SpellInfo(debuffSpellID)
local debuffName = _SpellInfo(debuffSpellID)
-- Check if this is a judgement debuff (name starts with "Judgement")
if debuffName and string.find(debuffName, "^Judgement") then
if debuffName and _string_find(debuffName, "^Judgement") then
-- Found a judgement debuff! Store it for refresh tracking
if not lib.detectedJudgementDebuffIDs[debuffSpellID] then
lib.detectedJudgementDebuffIDs[debuffSpellID] = true
lib.judgementSpells[debuffSpellID] = true -- Add to refresh list
if CleveRoids.debug then
if debug then
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cff00ffff[Judgement Detect]|r Found debuff %s (ID:%d) from cast (ID:%d) - added to refresh list",
_string_format("|cff00ffff[Judgement Detect]|r Found debuff %s (ID:%d) from cast (ID:%d) - added to refresh list",
debuffName, debuffSpellID, pending.castSpellID)
)
end
@@ -2120,35 +2203,41 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
end
end
end
-- Mark for removal
table.insert(toRemove, i)
-- Item processed, don't copy to output
else
-- Item not ready, keep it
writeIdx = writeIdx + 1
if writeIdx ~= i then
pendingList[writeIdx] = pending
end
end
end
-- Remove processed scans (iterate backwards to avoid index shifting)
for i = table.getn(toRemove), 1, -1 do
table.remove(lib.pendingJudgements, toRemove[i])
-- PERFORMANCE: Clear remaining slots and update length
for i = writeIdx + 1, _getn(pendingList) do
pendingList[i] = nil
end
end
-- Process pending personal debuffs
if lib.pendingPersonalDebuffs then
local pendingCount = table.getn(lib.pendingPersonalDebuffs)
if hasPersonal then
local pendingCount = _getn(lib.pendingPersonalDebuffs)
-- Debug: Show pending count every few seconds (avoid spam)
if CleveRoids.debug and pendingCount > 0 then
local now = GetTime()
if not lib._lastPendingDebugTime or (now - lib._lastPendingDebugTime) > 2.0 then
lib._lastPendingDebugTime = now
if debug and pendingCount > 0 then
if not lib._lastPendingDebugTime or (currentTime - lib._lastPendingDebugTime) > 2.0 then
lib._lastPendingDebugTime = currentTime
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cffaaaaaa[Pending Debug]|r %d personal debuffs waiting", pendingCount)
_string_format("|cffaaaaaa[Pending Debug]|r %d personal debuffs waiting", pendingCount)
)
end
end
local toRemove = {}
for i, pending in ipairs(lib.pendingPersonalDebuffs) do
local elapsed = GetTime() - pending.timestamp
local writeIdx = 0
local pendingList = lib.pendingPersonalDebuffs
for i = 1, _getn(pendingList) do
local pending = pendingList[i]
local elapsed = currentTime - pending.timestamp
-- Add debuff after 0.2 second delay (enough time for server sync)
if elapsed >= 0.2 then
@@ -2157,7 +2246,7 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
local isBleedSpell = CleveRoids.BleedSpellIDs and CleveRoids.BleedSpellIDs[pending.spellID]
local bleedVerified = true
if isBleedSpell and CleveRoids.hasSuperwow and pending.targetGUID then
if isBleedSpell and hasSuperwow and pending.targetGUID then
-- Check if mob is in bleed whitelist (skip verification for known bleeders)
local isWhitelisted = CleveRoids.MobsThatBleed and CleveRoids.MobsThatBleed[pending.targetGUID]
@@ -2165,12 +2254,12 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
-- Skip verification if target is dead (debuffs are removed on death)
-- Dead targets would cause false positive immunity detection
-- Note: SuperWoW allows GUID-based queries for all unit functions
if UnitIsDead(pending.targetGUID) then
if _UnitIsDead(pending.targetGUID) then
-- Target died - can't verify immunity, assume bleed landed
if CleveRoids.debug then
local spellNameDebug = SpellInfo(pending.spellID) or "Bleed"
if debug then
local spellNameDebug = _SpellInfo(pending.spellID) or "Bleed"
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cffff6600[Bleed Verify Skip]|r Target %s is dead - skipping immunity check for %s",
_string_format("|cffff6600[Bleed Verify Skip]|r Target %s is dead - skipping immunity check for %s",
pending.targetName or "Unknown", spellNameDebug)
)
end
@@ -2180,7 +2269,7 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
local totalDebuffs = 0
for slot = 1, 48 do
local _, _, _, debuffSpellID = UnitDebuff(pending.targetGUID, slot)
local _, _, _, debuffSpellID = _UnitDebuff(pending.targetGUID, slot)
if not debuffSpellID then
if slot <= 16 then break end -- Regular debuffs are dense, overflow continues on nil
else
@@ -2201,26 +2290,26 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
-- Record as BLEED immunity directly (bypass split damage override in RecordImmunity)
-- RecordImmunity would record Rake/Pounce as "physical" (initial school),
-- but we specifically detected the BLEED debuff didn't land
local spellNameForImmunity = SpellInfo(pending.spellID) or "Bleed"
local spellNameForImmunity = _SpellInfo(pending.spellID) or "Bleed"
if not CleveRoids_ImmunityData["bleed"] then
CleveRoids_ImmunityData["bleed"] = {}
end
CleveRoids_ImmunityData["bleed"][pending.targetName] = true
if CleveRoids.debug then
if debug then
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cffff6600[Bleed Immunity]|r %s is immune to bleed (%s) - only %d debuffs on target",
_string_format("|cffff6600[Bleed Immunity]|r %s is immune to bleed (%s) - only %d debuffs on target",
pending.targetName, spellNameForImmunity, totalDebuffs)
)
end
end
else
-- Many debuffs = likely pushed off at debuff cap
if CleveRoids.debug then
local spellNameDebug = SpellInfo(pending.spellID) or "Bleed"
if debug then
local spellNameDebug = _SpellInfo(pending.spellID) or "Bleed"
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cffff6600[Debuff Cap]|r %s not found on %s - likely pushed off (%d debuffs on target)",
_string_format("|cffff6600[Debuff Cap]|r %s not found on %s - likely pushed off (%d debuffs on target)",
spellNameDebug, pending.targetName or "Unknown", totalDebuffs)
)
end
@@ -2233,11 +2322,11 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
-- Only apply the debuff to tracking if it was verified (or not a bleed/was whitelisted)
if bleedVerified then
-- Debug: Show what we're about to add to tracking
if CleveRoids.debug then
local spellNameDebug = SpellInfo(pending.spellID) or "Unknown"
local guidStr = string.sub(tostring(pending.targetGUID or "nil"), 1, 20)
if debug then
local spellNameDebug = _SpellInfo(pending.spellID) or "Unknown"
local guidStr = _string_sub(_tostring(pending.targetGUID or "nil"), 1, 20)
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cff88ff88[Pending Process]|r Adding %s (ID:%d, %ds) GUID:%s",
_string_format("|cff88ff88[Pending Process]|r Adding %s (ID:%d, %ds) GUID:%s",
spellNameDebug, pending.spellID, pending.duration or 0, guidStr)
)
end
@@ -2256,19 +2345,19 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
if CleveRoids.lastRakeCast.targetGUID == pending.targetGUID and
CleveRoids.lastRakeCast.spellID == pending.spellID then
CleveRoids.lastRakeCast.pending = nil -- Mark as verified
if CleveRoids.debug then
if debug then
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cff00ff00[Carnage]|r Rake verified on %s - ready for Ferocious Bite refresh",
_string_format("|cff00ff00[Carnage]|r Rake verified on %s - ready for Ferocious Bite refresh",
pending.targetName or "Unknown")
)
end
end
end
if CleveRoids.debug then
local spellName = SpellInfo(pending.spellID) or "Unknown"
if debug then
local spellName = _SpellInfo(pending.spellID) or "Unknown"
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cff00ff00[Delayed Track]|r Applied %s (ID:%d) to tracking on %s",
_string_format("|cff00ff00[Delayed Track]|r Applied %s (ID:%d) to tracking on %s",
spellName, pending.spellID, pending.targetName or "Unknown")
)
end
@@ -2279,32 +2368,39 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
if CleveRoids.lastRakeCast.targetGUID == pending.targetGUID and
CleveRoids.lastRakeCast.spellID == pending.spellID then
CleveRoids.lastRakeCast = nil -- Clear invalid Rake data
if CleveRoids.debug then
if debug then
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cffff6600[Carnage]|r Rake failed to apply on %s - clearing tracking data",
_string_format("|cffff6600[Carnage]|r Rake failed to apply on %s - clearing tracking data",
pending.targetName or "Unknown")
)
end
end
end
end
-- Mark for removal
table.insert(toRemove, i)
-- Item processed, don't copy to output
else
-- Item not ready, keep it
writeIdx = writeIdx + 1
if writeIdx ~= i then
pendingList[writeIdx] = pending
end
end
end
-- Remove processed debuffs (iterate backwards to avoid index shifting)
for i = table.getn(toRemove), 1, -1 do
table.remove(lib.pendingPersonalDebuffs, toRemove[i])
-- PERFORMANCE: Clear remaining slots
for i = writeIdx + 1, _getn(pendingList) do
pendingList[i] = nil
end
end
-- Process pending CC debuffs for immunity detection
if lib.pendingCCDebuffs then
local toRemove = {}
for i, pending in ipairs(lib.pendingCCDebuffs) do
local elapsed = GetTime() - pending.timestamp
if hasCC then
local writeIdx = 0
local pendingList = lib.pendingCCDebuffs
for i = 1, _getn(pendingList) do
local pending = pendingList[i]
local elapsed = currentTime - pending.timestamp
-- Check after delay: 0.4s for hidden CC (no visible debuff), 0.2s for normal CC
-- Hidden CC (e.g., Pounce stun) needs longer delay to wait for "afflicted by" messages
@@ -2322,13 +2418,13 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
-- CC IMMUNITY VERIFICATION: Check if CC effect actually landed
-- Uses hybrid approach: direct spell ID match OR mechanic-based validation
-- (CC debuff IDs often differ from cast IDs, e.g., Pounce cast ≠ Pounce Stun debuff)
if not ccVerified and CleveRoids.hasSuperwow and pending.targetGUID then
if not ccVerified and hasSuperwow and pending.targetGUID then
-- Skip verification if target is dead (debuffs are removed on death)
if UnitIsDead(pending.targetGUID) then
if _UnitIsDead(pending.targetGUID) then
ccVerified = true -- Assume CC landed, can't verify on dead target
if CleveRoids.debug then
if debug then
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cffff6600[CC Verify Skip]|r Target %s is dead - skipping immunity check for %s",
_string_format("|cffff6600[CC Verify Skip]|r Target %s is dead - skipping immunity check for %s",
pending.targetName or "Unknown", pending.ccType or "CC")
)
end
@@ -2336,17 +2432,17 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
-- Method 1: Direct spell ID matching (scan debuffs for exact spell ID)
if pending.spellID then
for slot = 1, 48 do
local texture, _, _, debuffSpellID = UnitDebuff(pending.targetGUID, slot)
local texture, _, _, debuffSpellID = _UnitDebuff(pending.targetGUID, slot)
if not texture then
if slot <= 16 then break end -- Regular debuffs are dense, overflow continues on nil
else
totalDebuffs = totalDebuffs + 1
-- Debug: Show each debuff found during CC verification
if CleveRoids.debug then
local debuffName = SpellInfo(debuffSpellID) or "Unknown"
if debug then
local debuffName = _SpellInfo(debuffSpellID) or "Unknown"
local mechanic = CleveRoids.GetSpellMechanic and CleveRoids.GetSpellMechanic(debuffSpellID) or 0
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cffaaaaaa[CC Scan]|r Slot %d: %s (ID:%d, Mech:%d) - Looking for %s (ID:%d)",
_string_format("|cffaaaaaa[CC Scan]|r Slot %d: %s (ID:%d, Mech:%d) - Looking for %s (ID:%d)",
slot, debuffName, debuffSpellID or 0, mechanic, pending.spellName or "CC", pending.spellID or 0)
)
end
@@ -2362,16 +2458,16 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
-- CC debuff IDs often differ from cast IDs (e.g., Pounce stun uses different ID)
-- Check if target has ANY debuff of the correct CC type (stun, fear, etc.)
if not ccVerified and pending.ccType and CleveRoids.ValidateUnitCC then
if CleveRoids.debug then
if debug then
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cffaaaa00[CC Verify]|r Direct ID match failed, trying mechanic check for %s...",
_string_format("|cffaaaa00[CC Verify]|r Direct ID match failed, trying mechanic check for %s...",
pending.ccType)
)
end
ccVerified = CleveRoids.ValidateUnitCC(pending.targetGUID, pending.ccType)
if CleveRoids.debug then
if debug then
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cff00aaff[CC Verify]|r Mechanic check result: %s",
_string_format("|cff00aaff[CC Verify]|r Mechanic check result: %s",
ccVerified and "FOUND" or "NOT FOUND")
)
end
@@ -2380,12 +2476,12 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
end
-- If CC didn't land, check if it's immunity or debuff cap
if not ccVerified and not UnitIsDead(pending.targetGUID) then
if not ccVerified and not _UnitIsDead(pending.targetGUID) then
-- totalDebuffs already counted above, reuse it
if totalDebuffs == 0 and CleveRoids.hasSuperwow and pending.targetGUID then
if totalDebuffs == 0 and hasSuperwow and pending.targetGUID then
-- Count wasn't done (dead target check skipped counting), do it now
for slot = 1, 48 do
local texture, _, _, debuffSpellID = UnitDebuff(pending.targetGUID, slot)
local texture, _, _, debuffSpellID = _UnitDebuff(pending.targetGUID, slot)
if not texture then
if slot <= 16 then break end
else
@@ -2401,15 +2497,15 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
resolvedTargetName = lib.guidToName[pending.targetGUID]
-- Try to resolve from current target
if not resolvedTargetName then
local _, currentTargetGUID = UnitExists("target")
local _, currentTargetGUID = _UnitExists("target")
if currentTargetGUID and CleveRoids.NormalizeGUID(currentTargetGUID) == pending.targetGUID then
resolvedTargetName = UnitName("target")
resolvedTargetName = _UnitName("target")
lib.guidToName[pending.targetGUID] = resolvedTargetName
end
end
-- Try to resolve using SuperWoW GUID-based query
if not resolvedTargetName and CleveRoids.hasSuperwow then
local name = UnitName(pending.targetGUID)
if not resolvedTargetName and hasSuperwow then
local name = _UnitName(pending.targetGUID)
if name and name ~= "Unknown" then
resolvedTargetName = name
lib.guidToName[pending.targetGUID] = resolvedTargetName
@@ -2423,23 +2519,23 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
if resolvedTargetName and resolvedTargetName ~= "" and pending.ccType then
CleveRoids.RecordCCImmunity(resolvedTargetName, pending.ccType, nil, pending.spellName)
if CleveRoids.debug then
if debug then
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cff00ff00[CC Immunity]|r %s is immune to %s (%s) - verified: debuff missing, only %d debuffs on target",
_string_format("|cff00ff00[CC Immunity]|r %s is immune to %s (%s) - verified: debuff missing, only %d debuffs on target",
resolvedTargetName, pending.ccType, pending.spellName or "Unknown", totalDebuffs)
)
end
elseif CleveRoids.debug then
elseif debug then
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cffff6600[CC Immunity Skip]|r Could not resolve target name for GUID %s - cannot record %s immunity",
_string_format("|cffff6600[CC Immunity Skip]|r Could not resolve target name for GUID %s - cannot record %s immunity",
pending.targetGUID or "nil", pending.ccType or "Unknown")
)
end
else
-- Many debuffs = likely pushed off
if CleveRoids.debug then
if debug then
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cffff6600[CC Debuff Cap]|r %s not found on %s - likely pushed off (%d debuffs)",
_string_format("|cffff6600[CC Debuff Cap]|r %s not found on %s - likely pushed off (%d debuffs)",
pending.ccType, resolvedTargetName or "Unknown", totalDebuffs)
)
end
@@ -2453,29 +2549,37 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
if resolvedTargetName and pending.ccType then
CleveRoids.RemoveCCImmunity(resolvedTargetName, pending.ccType)
end
if CleveRoids.debug then
if debug then
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cff00ff00[CC Verified]|r %s landed on %s",
_string_format("|cff00ff00[CC Verified]|r %s landed on %s",
pending.ccType, resolvedTargetName or "Unknown")
)
end
end
table.insert(toRemove, i)
-- Item processed, don't copy to output
else
-- Item not ready, keep it
writeIdx = writeIdx + 1
if writeIdx ~= i then
pendingList[writeIdx] = pending
end
end
end
-- Remove processed entries
for i = table.getn(toRemove), 1, -1 do
table.remove(lib.pendingCCDebuffs, toRemove[i])
-- PERFORMANCE: Clear remaining slots
for i = writeIdx + 1, _getn(pendingList) do
pendingList[i] = nil
end
end
-- Process pending shared debuffs for immunity detection
if lib.pendingSharedDebuffs then
local toRemove = {}
for i, pending in ipairs(lib.pendingSharedDebuffs) do
local elapsed = GetTime() - pending.timestamp
if hasShared then
local writeIdx = 0
local pendingList = lib.pendingSharedDebuffs
for i = 1, _getn(pendingList) do
local pending = pendingList[i]
local elapsed = currentTime - pending.timestamp
-- Check after 0.2s delay (enough time for server sync)
if elapsed >= 0.2 then
@@ -2483,21 +2587,21 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
local totalDebuffs = 0
-- Skip verification if target is dead (debuffs are removed on death)
if CleveRoids.hasSuperwow and pending.targetGUID then
if UnitIsDead(pending.targetGUID) then
if hasSuperwow and pending.targetGUID then
if _UnitIsDead(pending.targetGUID) then
-- Target died - can't verify immunity, assume debuff landed
debuffVerified = true
if CleveRoids.debug then
local spellNameDebug = SpellInfo(pending.spellID) or "Shared Debuff"
if debug then
local spellNameDebug = _SpellInfo(pending.spellID) or "Shared Debuff"
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cffff6600[Shared Verify Skip]|r Target %s is dead - skipping immunity check for %s",
_string_format("|cffff6600[Shared Verify Skip]|r Target %s is dead - skipping immunity check for %s",
pending.targetName or "Unknown", spellNameDebug)
)
end
else
-- Target is alive - check if debuff exists
for slot = 1, 48 do
local _, _, _, debuffSpellID = UnitDebuff(pending.targetGUID, slot)
local _, _, _, debuffSpellID = _UnitDebuff(pending.targetGUID, slot)
if not debuffSpellID then
if slot <= 16 then break end -- Regular debuffs are dense, overflow continues on nil
else
@@ -2517,10 +2621,10 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
-- Process result
if debuffVerified then
-- Debuff landed - add to tracking
if CleveRoids.debug then
local spellNameDebug = SpellInfo(pending.spellID) or "Unknown"
if debug then
local spellNameDebug = _SpellInfo(pending.spellID) or "Unknown"
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cff88ff88[Shared Verified]|r %s (ID:%d) verified on %s",
_string_format("|cff88ff88[Shared Verified]|r %s (ID:%d) verified on %s",
spellNameDebug, pending.spellID, pending.targetName or "Unknown")
)
end
@@ -2543,39 +2647,43 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
end
CleveRoids_ImmunityData[pending.school][pending.targetName] = true
if CleveRoids.debug then
local spellNameDebug = SpellInfo(pending.spellID) or "Unknown"
if debug then
local spellNameDebug = _SpellInfo(pending.spellID) or "Unknown"
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cffff6600[Shared Immunity]|r %s is immune to %s (%s) - only %d debuffs on target",
_string_format("|cffff6600[Shared Immunity]|r %s is immune to %s (%s) - only %d debuffs on target",
pending.targetName, pending.school, spellNameDebug, totalDebuffs)
)
end
end
else
-- Many debuffs = likely pushed off at debuff cap
if CleveRoids.debug then
local spellNameDebug = SpellInfo(pending.spellID) or "Unknown"
if debug then
local spellNameDebug = _SpellInfo(pending.spellID) or "Unknown"
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cffff6600[Shared Debuff Cap]|r %s not found on %s - likely pushed off (%d debuffs)",
_string_format("|cffff6600[Shared Debuff Cap]|r %s not found on %s - likely pushed off (%d debuffs)",
spellNameDebug, pending.targetName or "Unknown", totalDebuffs)
)
end
end
end
table.insert(toRemove, i)
-- Item processed, don't copy to output
else
-- Item not ready, keep it
writeIdx = writeIdx + 1
if writeIdx ~= i then
pendingList[writeIdx] = pending
end
end
end
-- Remove processed entries
for i = table.getn(toRemove), 1, -1 do
table.remove(lib.pendingSharedDebuffs, toRemove[i])
-- PERFORMANCE: Clear remaining slots
for i = writeIdx + 1, _getn(pendingList) do
pendingList[i] = nil
end
end
-- Clean up old rank refresh overrides (older than 2 seconds)
if lib.rankRefreshOverrides then
local currentTime = GetTime()
if hasOverrides then
for spellName, override in pairs(lib.rankRefreshOverrides) do
if currentTime - override.timestamp > 2.0 then
lib.rankRefreshOverrides[spellName] = nil
@@ -3452,6 +3560,19 @@ evLearn:SetScript("OnEvent", function()
if remaining > 0 then
-- Refresh: reset start time to now
rec.start = GetTime()
-- Sync refresh to pfUI
local targetName = lib.guidToName[targetGUID]
if pfUI and pfUI.api and pfUI.api.libdebuff and targetName then
local pflib = pfUI.api.libdebuff
local spellName = SpellInfo(flameShockID)
if spellName and pflib.AddEffect then
local baseName = string.gsub(spellName, "%s*%(Rank %d+%)", "")
local targetLevel = UnitLevel(targetGUID) or UnitLevel("target") or 1
pflib:AddEffect(targetName, targetLevel, baseName, rec.duration, "player")
end
end
if CleveRoids.debug then
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cff0070dd[Molten Blast]|r Refreshed Flame Shock to %.1fs on %s",