From bec19c30105039f2f11e953a075590c5645b3335 Mon Sep 17 00:00:00 2001 From: Jrc13245 Date: Tue, 6 Jan 2026 20:00:45 -0500 Subject: [PATCH] fix refreshing existing buuffs and pushing updates to pfui --- Utility.lua | 321 ++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 261 insertions(+), 60 deletions(-) diff --git a/Utility.lua b/Utility.lua index 51de6a8..c164964 100644 --- a/Utility.lua +++ b/Utility.lua @@ -1388,8 +1388,9 @@ 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 + -- This allows pfUI to show accurate timers for ALL tracked debuffs (player + other casters) + -- For shared debuffs like Sunder Armor, we want pfUI to show timers regardless of who cast it + if pfUI and pfUI.api and pfUI.api.libdebuff and unitName then local pflib = pfUI.api.libdebuff local spellName = SpellInfo(spellID) @@ -1406,12 +1407,14 @@ function lib:AddEffect(guid, unitName, spellID, duration, stacks, caster) end -- Add the effect to pfUI's tracking + -- Use "player" as caster for pfUI compatibility (it expects this format) pflib:AddEffect(unitName, targetLevel, baseName, duration, "player") if CleveRoids.debug then + local casterStr = (caster == "player") and "player" or "other" DEFAULT_CHAT_FRAME:AddMessage( - string.format("|cff00ff00[pfUI Inject]|r %s (%ds) on %s (level %d)", - baseName, duration, unitName, targetLevel) + string.format("|cff00ff00[pfUI Inject]|r %s (%ds) on %s (level %d) [caster: %s]", + baseName, duration, unitName, targetLevel, casterStr) ) end end @@ -1634,22 +1637,58 @@ local function SeedUnit(unit) else local duration = lib:GetDuration(spellID) if duration > 0 then - -- SHARED DEBUFFS: Only seed if not already tracked - -- If already tracked via UNIT_CASTEVENT, just update stacks (preserve timer) + -- SHARED DEBUFFS: Check if already tracked and handle refresh detection + -- Player's casts are tracked via UNIT_CASTEVENT, but OTHER players' casts + -- only trigger UNIT_AURA events. Detect refreshes via stack count changes. local existing = lib.objects[guid] and lib.objects[guid][spellID] if existing and existing.start and existing.duration then - -- Already tracked - only update stacks if different (don't reset timer!) + -- Already tracked - check if debuff was refreshed (by player or others) if existing.stacks ~= stacks then + local oldStacks = existing.stacks or 0 existing.stacks = stacks - if CleveRoids.debug then - local spellName = SpellInfo(spellID) or "Unknown" - DEFAULT_CHAT_FRAME:AddMessage( - string.format("|cffaaff00[DEBUG SeedUnit Debuff]|r %s (ID:%d) updated stacks to %d (timer preserved)", - spellName, spellID, stacks or 0) - ) + + -- If stacks INCREASED, debuff was refreshed - reset timer to full duration + -- This catches refreshes from other players (we can't detect via UNIT_CASTEVENT) + if (stacks or 0) > oldStacks then + existing.start = GetTime() + existing.duration = duration + + -- PFUI INTEGRATION: Inject refreshed timer into pfUI + if pfUI and pfUI.api and pfUI.api.libdebuff and unitName then + local pflib = pfUI.api.libdebuff + local spellName = SpellInfo(spellID) + if spellName and pflib.AddEffect then + local targetLevel = UnitLevel(unit) or 1 + local baseName = string.gsub(spellName, "%s*%(Rank %d+%)", "") + if pflib.debuffs then + pflib.debuffs[baseName] = duration + end + pflib:AddEffect(unitName, targetLevel, baseName, duration, "player") + end + end + + if CleveRoids.debug then + local spellName = SpellInfo(spellID) or "Unknown" + DEFAULT_CHAT_FRAME:AddMessage( + string.format("|cffaaff00[DEBUG SeedUnit Debuff]|r %s (ID:%d) stack increased %d->%d, timer RESET to %ds (refresh detected)", + spellName, spellID, oldStacks, stacks or 0, duration) + ) + end + else + -- Stacks decreased (shouldn't happen normally) - just update stacks, preserve timer + if CleveRoids.debug then + local spellName = SpellInfo(spellID) or "Unknown" + DEFAULT_CHAT_FRAME:AddMessage( + string.format("|cffaaff00[DEBUG SeedUnit Debuff]|r %s (ID:%d) updated stacks to %d (timer preserved)", + spellName, spellID, stacks or 0) + ) + end end end + -- NOTE: When stacks stay the same (e.g., already at 5/5 Sunder), we can't detect + -- refreshes from other players. This is a fundamental limitation without server-side + -- duration data. The timer continues from the last known refresh. else -- First time seeing this debuff - add with full duration -- Check for combo spell duration @@ -1689,20 +1728,51 @@ local function SeedUnit(unit) else local duration = lib:GetDuration(spellID) if duration > 0 then - -- SHARED DEBUFFS: Only seed if not already tracked - -- If already tracked via UNIT_CASTEVENT, just update stacks (preserve timer) + -- SHARED DEBUFFS (overflow): Check if already tracked and handle refresh detection + -- Same logic as regular debuff slots - detect refreshes via stack count changes. local existing = lib.objects[guid] and lib.objects[guid][spellID] if existing and existing.start and existing.duration then - -- Already tracked - only update stacks if different (don't reset timer!) + -- Already tracked - check if debuff was refreshed (by player or others) if existing.stacks ~= stacks then + local oldStacks = existing.stacks or 0 existing.stacks = stacks - if CleveRoids.debug then - local spellName = SpellInfo(spellID) or "Unknown" - DEFAULT_CHAT_FRAME:AddMessage( - string.format("|cffaaff00[DEBUG SeedUnit Buff]|r %s (ID:%d) updated stacks to %d (timer preserved)", - spellName, spellID, stacks or 0) - ) + + -- If stacks INCREASED, debuff was refreshed - reset timer to full duration + if (stacks or 0) > oldStacks then + existing.start = GetTime() + existing.duration = duration + + -- PFUI INTEGRATION: Inject refreshed timer into pfUI + if pfUI and pfUI.api and pfUI.api.libdebuff and unitName then + local pflib = pfUI.api.libdebuff + local spellName = SpellInfo(spellID) + if spellName and pflib.AddEffect then + local targetLevel = UnitLevel(unit) or 1 + local baseName = string.gsub(spellName, "%s*%(Rank %d+%)", "") + if pflib.debuffs then + pflib.debuffs[baseName] = duration + end + pflib:AddEffect(unitName, targetLevel, baseName, duration, "player") + end + end + + if CleveRoids.debug then + local spellName = SpellInfo(spellID) or "Unknown" + DEFAULT_CHAT_FRAME:AddMessage( + string.format("|cffaaff00[DEBUG SeedUnit Buff]|r %s (ID:%d) stack increased %d->%d, timer RESET to %ds (refresh detected)", + spellName, spellID, oldStacks, stacks or 0, duration) + ) + end + else + -- Stacks decreased (shouldn't happen normally) - just update stacks, preserve timer + if CleveRoids.debug then + local spellName = SpellInfo(spellID) or "Unknown" + DEFAULT_CHAT_FRAME:AddMessage( + string.format("|cffaaff00[DEBUG SeedUnit Buff]|r %s (ID:%d) updated stacks to %d (timer preserved)", + spellName, spellID, stacks or 0) + ) + end end end else @@ -2156,6 +2226,24 @@ delayedTrackingFrame:SetScript("OnUpdate", function() local hasShared = lib.pendingSharedDebuffs and lib.pendingSharedDebuffs[1] local hasOverrides = lib.rankRefreshOverrides and _next(lib.rankRefreshOverrides) + -- DEBUG: Track pending queue state - check EVERY time to diagnose issue + if CleveRoids.debug then + local sharedCount = lib.pendingSharedDebuffs and _getn(lib.pendingSharedDebuffs) or 0 + local personalCount = lib.pendingPersonalDebuffs and _getn(lib.pendingPersonalDebuffs) or 0 + -- Log if any shared pending exists, regardless of hasShared check + if sharedCount > 0 then + -- Only log occasionally to avoid spam (every ~0.5s) + if not lib._lastPendingQueueLog or (currentTime - lib._lastPendingQueueLog) > 0.5 then + lib._lastPendingQueueLog = currentTime + local firstItem = lib.pendingSharedDebuffs[1] + DEFAULT_CHAT_FRAME:AddMessage( + _string_format("|cffaaaaaa[Pending Queue]|r shared:%d (hasShared:%s, [1]:%s) personal:%d", + sharedCount, hasShared and "TRUE" or "FALSE", firstItem and "exists" or "NIL", personalCount) + ) + end + end + end + if not (hasJudgements or hasPersonal or hasCC or hasShared or hasOverrides) then return end @@ -2576,11 +2664,25 @@ delayedTrackingFrame:SetScript("OnUpdate", function() if hasShared then local writeIdx = 0 local pendingList = lib.pendingSharedDebuffs + local pendingCount = _getn(pendingList) - for i = 1, _getn(pendingList) do + if debug then + DEFAULT_CHAT_FRAME:AddMessage( + _string_format("|cff00aaff[Shared Process]|r Processing %d pending shared debuffs", pendingCount) + ) + end + + for i = 1, pendingCount do local pending = pendingList[i] local elapsed = currentTime - pending.timestamp + if debug then + local spellName = pending.spellID and _SpellInfo(pending.spellID) or "Unknown" + DEFAULT_CHAT_FRAME:AddMessage( + _string_format("|cff00aaff[Shared Check]|r %s elapsed:%.2fs (need 0.2s)", spellName, elapsed) + ) + end + -- Check after 0.2s delay (enough time for server sync) if elapsed >= 0.2 then local debuffVerified = false @@ -2950,7 +3052,16 @@ ev:SetScript("OnEvent", function() if currentTargetGUID == targetGUID then targetName = UnitName("target") lib.guidToName[targetGUID] = targetName - else + end + -- Try SuperWoW GUID-based name lookup if still not resolved + if not targetName and CleveRoids.hasSuperwow then + targetName = UnitName(targetGUID) + if targetName and targetName ~= "Unknown" then + lib.guidToName[targetGUID] = targetName + end + end + -- Final fallback + if not targetName then targetName = "Unknown" end end @@ -3113,25 +3224,46 @@ ev:SetScript("OnEvent", function() end if trackingSpellID then - -- Schedule shared debuff for delayed verification - table.insert(lib.pendingSharedDebuffs, { - timestamp = GetTime(), - targetGUID = targetGUID, - targetName = targetName, - spellID = trackingSpellID, - castSpellID = spellID, -- Original cast spell ID (for dodge/parry matching) - duration = trackingDuration, - stacks = newStacks, - school = spellSchool, - isRankPreserve = (trackingSpellID ~= spellID) - }) + -- REFRESH DETECTION: If newStacks > 1, debuff already existed on target + -- Skip pending queue for refreshes - directly reset the timer + -- The pending queue is only for NEW applications where immunity detection matters + local isRefresh = (newStacks > 1) - if CleveRoids.debug then - local spellName = SpellInfo(trackingSpellID) or "Unknown" - DEFAULT_CHAT_FRAME:AddMessage( - string.format("|cffaaff00[Pending Shared]|r Scheduled %s (ID:%d, school:%s) for tracking on %s", - spellName, trackingSpellID, spellSchool or "unknown", targetName or "Unknown") - ) + if isRefresh then + -- Direct timer reset for refreshes (debuff already verified to exist) + lib:AddEffect(targetGUID, targetName, trackingSpellID, trackingDuration, newStacks, "player") + + if CleveRoids.debug then + local spellName = SpellInfo(trackingSpellID) or "Unknown" + DEFAULT_CHAT_FRAME:AddMessage( + string.format("|cff00ff00[Shared Refresh]|r %s (ID:%d) on %s - timer reset to %ds (stacks:%d)", + spellName, trackingSpellID, targetName or "Unknown", trackingDuration, newStacks) + ) + end + else + -- NEW APPLICATION: Schedule for delayed verification (immunity detection) + table.insert(lib.pendingSharedDebuffs, { + timestamp = GetTime(), + targetGUID = targetGUID, + targetName = targetName, + spellID = trackingSpellID, + castSpellID = spellID, -- Original cast spell ID (for dodge/parry matching) + duration = trackingDuration, + stacks = newStacks, + school = spellSchool, + isRankPreserve = (trackingSpellID ~= spellID) + }) + + if CleveRoids.debug then + local spellName = SpellInfo(trackingSpellID) or "Unknown" + local queueLen = table.getn(lib.pendingSharedDebuffs) + local firstItem = lib.pendingSharedDebuffs[1] + DEFAULT_CHAT_FRAME:AddMessage( + string.format("|cffaaff00[Pending Shared]|r Scheduled %s (ID:%d, school:%s) for tracking on %s [queueLen:%d, [1]:%s]", + spellName, trackingSpellID, spellSchool or "unknown", targetName or "Unknown", + queueLen, firstItem and "exists" or "NIL") + ) + end end end end @@ -3240,6 +3372,73 @@ ev:SetScript("OnEvent", function() } end end + + -- SHARED DEBUFFS FROM OTHER PLAYERS: Track when other players cast shared debuffs + -- This ensures Sunder Armor, Faerie Fire, etc. are tracked when ANY player casts them + -- Personal debuffs are still only tracked from the player (we only care about our own) + local _, playerGUID = UnitExists("player") + if casterGUID ~= playerGUID and targetGUID then + -- Check if this is a shared debuff we should track + if lib.sharedDebuffs[spellID] then + local duration = lib:GetDuration(spellID) + if duration and duration > 0 then + -- Get target name from cache or current target + local targetName = lib.guidToName[targetGUID] + if not targetName then + local _, currentTargetGUID = UnitExists("target") + currentTargetGUID = CleveRoids.NormalizeGUID(currentTargetGUID) + if currentTargetGUID == targetGUID then + targetName = UnitName("target") + lib.guidToName[targetGUID] = targetName + end + end + + -- For stacking debuffs, predict new stack count + local newStacks = 1 + local _, currentTargetGUID = UnitExists("target") + currentTargetGUID = CleveRoids.NormalizeGUID(currentTargetGUID) + if currentTargetGUID == targetGUID and CleveRoids.hasSuperwow then + -- Scan debuff slots to find current stacks + for i = 1, 16 do + local _, existingStacks, _, existingSpellID = UnitDebuff("target", i) + if existingSpellID == spellID then + newStacks = (existingStacks or 0) + 1 + break + end + end + + -- If not found in debuff slots, check buff slots (overflow) + if newStacks == 1 then + for i = 1, 32 do + local _, existingStacks, existingSpellID = UnitBuff("target", i) + if existingSpellID == spellID then + newStacks = (existingStacks or 0) + 1 + break + end + end + end + + -- Cap at maximum stacks + if newStacks > 5 then + newStacks = 5 + end + end + + -- Directly add the effect (other players' casts don't need immunity verification) + -- Reset timer to full duration since the debuff was just refreshed + lib:AddEffect(targetGUID, targetName, spellID, duration, newStacks, casterGUID) + + if CleveRoids.debug then + local spellName = SpellInfo(spellID) or "Unknown" + local casterName = lib.guidToName[casterGUID] or "Other Player" + DEFAULT_CHAT_FRAME:AddMessage( + string.format("|cff00aaff[Other Cast]|r %s cast %s (ID:%d) on %s - timer reset to %ds", + casterName, spellName, spellID, targetName or "Unknown", duration) + ) + end + end + end + end end end end) @@ -3358,16 +3557,16 @@ evLearn:SetScript("OnEvent", function() end end - -- PENDING CC DEBUFFS: Cancel pending CC tracking if spell was dodged/parried/blocked/missed/resisted - -- NOTE: We cancel on avoidance AND resist (both are NOT immunity) + -- PENDING CC/SHARED DEBUFFS: Cancel pending tracking if spell was dodged/parried/blocked/missed/resisted/immune + -- NOTE: We cancel on ALL failure types: -- - Physical avoidance (dodge/parry/block/miss): Combat mechanic -- - Resist: RNG-based spell resistance - -- - Immune: TRUE immunity - handled by combat log parser (ParseImmunityCombatLog) - local isAvoidanceOrResist = find(message, "dodged") or find(message, "parried") or - find(message, "blocked") or find(message, "missed") or - find(message, "resist") + -- - Immune: TRUE immunity (ParseImmunityCombatLog also records for [immune] conditional) + local isSpellFailure = find(message, "dodged") or find(message, "parried") or + find(message, "blocked") or find(message, "missed") or + find(message, "resist") or find(message, "immune") - if isAvoidanceOrResist and lib.pendingCCDebuffs then + if isSpellFailure and lib.pendingCCDebuffs then local messageSpellName = string.gsub(spellName, "%s*%(%s*Rank%s+%d+%s*%)", "") local toRemove = {} @@ -3378,13 +3577,14 @@ evLearn:SetScript("OnEvent", function() if lower(pendingSpellName) == lower(messageSpellName) then -- Found the pending CC that was avoided/resisted - cancel it (don't record immunity) if CleveRoids.debug then - local avoidType = find(message, "dodged") and "dodged" or + local failType = find(message, "dodged") and "dodged" or find(message, "parried") and "parried" or find(message, "blocked") and "blocked" or - find(message, "resist") and "resisted" or "missed" + find(message, "resist") and "resisted" or + find(message, "immune") and "immune" or "missed" DEFAULT_CHAT_FRAME:AddMessage( - string.format("|cffff0000[CC Track]|r Cancelled %s (%s) - %s by %s (not immunity)", - pendingSpellName, pending.ccType or "CC", avoidType, targetName or "Unknown") + string.format("|cffff0000[CC Track]|r Cancelled %s (%s) - %s by %s", + pendingSpellName, pending.ccType or "CC", failType, targetName or "Unknown") ) end table.insert(toRemove, i) @@ -3398,8 +3598,8 @@ evLearn:SetScript("OnEvent", function() end end - -- SHARED DEBUFFS: Cancel pending tracking if spell was dodged/parried/blocked/missed/resisted - if isAvoidanceOrResist and lib.pendingSharedDebuffs then + -- SHARED DEBUFFS: Cancel pending tracking if spell was dodged/parried/blocked/missed/resisted/immune + if isSpellFailure and lib.pendingSharedDebuffs then local messageSpellName = string.gsub(spellName, "%s*%(%s*Rank%s+%d+%s*%)", "") local toRemove = {} @@ -3422,16 +3622,17 @@ evLearn:SetScript("OnEvent", function() end if matchesTracking or matchesCast then - -- Found the pending shared debuff that was avoided/resisted - cancel it (don't record immunity) + -- Found the pending shared debuff that failed - cancel tracking if CleveRoids.debug then local displayName = matchesCast and castSpellName or pendingSpellName - local avoidType = find(message, "dodged") and "dodged" or + local failType = find(message, "dodged") and "dodged" or find(message, "parried") and "parried" or find(message, "blocked") and "blocked" or - find(message, "resist") and "resisted" or "missed" + find(message, "resist") and "resisted" or + find(message, "immune") and "immune" or "missed" DEFAULT_CHAT_FRAME:AddMessage( - string.format("|cffff0000[Shared Track]|r Cancelled %s (school:%s) - %s by %s (not immunity)", - displayName or "Unknown", pending.school or "unknown", avoidType, targetName or "Unknown") + string.format("|cffff0000[Shared Track]|r Cancelled %s (school:%s) - %s by %s", + displayName or "Unknown", pending.school or "unknown", failType, targetName or "Unknown") ) end table.insert(toRemove, i)