fix pounce immunity detection, stale carnage duration override bug, and immune conditionals

This commit is contained in:
Jrc13245
2025-12-29 22:35:34 -05:00
parent 524cc1aabd
commit 3ccaec5e32
3 changed files with 236 additions and 57 deletions
+7
View File
@@ -101,6 +101,13 @@ CleveRoids.PounceBleedSpellIDs = {
-- Used when checking if a cast bleed failed to apply (indicates bleed immunity)
-- NOTE: These are the DEBUFF spell IDs (what appears on target), not cast spell IDs
CleveRoids.BleedSpellIDs = {
-- Rip (bleed DoT from Cat Form finisher)
[1079] = true, -- Rank 1
[9492] = true, -- Rank 2
[9493] = true, -- Rank 3
[9752] = true, -- Rank 4
[9894] = true, -- Rank 5
[9896] = true, -- Rank 6
-- Rake (cast ID = debuff ID for Rake)
[1822] = true, -- Rank 1
[1823] = true, -- Rank 2
+14
View File
@@ -5081,6 +5081,20 @@ function CleveRoids.ResolveMultiscanTarget(conditionals, specifiedUnit)
if not guid or seenGuids[guid] then return end
seenGuids[guid] = true
-- Cache GUID -> name mapping for immunity checks
-- This is needed because immune enemies may never have debuffs tracked,
-- but we still need their name for immunity lookups
local unitName = UnitName(unit)
if unitName and unitName ~= "" and unitName ~= "Unknown" then
local lib = CleveRoids.libdebuff
if lib and lib.guidToName then
local normalizedGuid = CleveRoids.NormalizeGUID(guid)
if normalizedGuid then
lib.guidToName[normalizedGuid] = unitName
end
end
end
local score = CleveRoids.GetMultiscanScore(unit, priorityType, currentTargetGuid, specifiedUnitGuid)
if not score then return end
+215 -57
View File
@@ -1788,8 +1788,11 @@ lib.pendingPersonalDebuffs = lib.pendingPersonalDebuffs or {}
lib.pendingCCDebuffs = lib.pendingCCDebuffs or {}
-- Spells with HIDDEN CC debuffs - these apply CC effects but don't show visible debuffs
-- Skip debuff-based immunity verification for these (only trust combat log "immune" messages)
-- Pounce stun is hidden - the target is stunned but no debuff icon appears
-- These spells use extended verification (0.4s instead of 0.2s) to allow time for:
-- 1. "afflicted by" combat log messages to arrive and confirm success
-- 2. Mechanic-based ValidateUnitCC check (if the stun is internally tracked)
-- If neither confirms the CC landed, immunity is recorded
lib.hiddenCCSpells = {
[9005] = true, -- Pounce (Rank 1)
[9823] = true, -- Pounce (Rank 2)
@@ -1818,13 +1821,25 @@ lib.trackedAfflictions = {
-- Function to apply Carnage refresh (exposed for ComboPointTracker to call on proc detection)
function lib.ApplyCarnageRefresh(targetGUID, targetName, biteSpellID)
if CleveRoids.debug then
-- Compare Carnage GUID with current target GUID
local _, currentTargetGUID = UnitExists("target")
currentTargetGUID = CleveRoids.NormalizeGUID(currentTargetGUID)
local guidMatch = (targetGUID == currentTargetGUID) and "MATCH" or "MISMATCH"
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cffff00ff[Carnage]|r ApplyCarnageRefresh called for %s", targetName or "Unknown")
string.format("|cffff00ff[Carnage]|r ApplyCarnageRefresh called for %s (GUID:%s, current:%s, %s)",
targetName or "Unknown", tostring(targetGUID), tostring(currentTargetGUID), guidMatch)
)
end
-- Only refresh debuffs if they're currently active on the target
if not lib.objects[targetGUID] then return end
if not lib.objects[targetGUID] then
if CleveRoids.debug then
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cffff6600[Carnage]|r No tracking data for GUID %s", tostring(targetGUID))
)
end
return
end
-- Try to refresh Rip
if CleveRoids.lastRipCast and CleveRoids.lastRipCast.duration and CleveRoids.lastRipCast.spellID and
@@ -1858,14 +1873,40 @@ function lib.ApplyCarnageRefresh(targetGUID, targetName, biteSpellID)
}
-- Update CleveRoids internal tracking
if lib.objects[targetGUID][ripSpellID] then
if lib.objects[targetGUID] and lib.objects[targetGUID][ripSpellID] then
local oldCaster = lib.objects[targetGUID][ripSpellID].caster
lib.objects[targetGUID][ripSpellID].duration = ripDuration
lib.objects[targetGUID][ripSpellID].start = GetTime()
lib.objects[targetGUID][ripSpellID].expiry = GetTime() + ripDuration
-- Ensure caster is preserved (required for personal debuff tracking)
if not lib.objects[targetGUID][ripSpellID].caster then
lib.objects[targetGUID][ripSpellID].caster = "player"
end
if CleveRoids.debug then
-- Verify the record was actually saved
local verifyRec = lib.objects[targetGUID] and lib.objects[targetGUID][ripSpellID]
if verifyRec then
local verifyRemaining = verifyRec.duration + verifyRec.start - GetTime()
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cffff00ff[Carnage]|r Updated CleveRoids tracking for Rip (GUID:%s, caster:%s->%s)",
tostring(targetGUID), tostring(oldCaster), tostring(verifyRec.caster))
)
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cffff00ff[Carnage VERIFY]|r Rip record: dur=%s, start=%s, remaining=%.1fs",
tostring(verifyRec.duration), tostring(verifyRec.start), verifyRemaining)
)
else
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cffff0000[Carnage ERROR]|r Rip record MISSING immediately after update!")
)
end
end
else
if CleveRoids.debug then
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cffff00ff[Carnage]|r Updated CleveRoids tracking for Rip")
string.format("|cffff6600[Carnage]|r WARNING: Rip record not found! GUID:%s, objects[GUID]:%s",
tostring(targetGUID), tostring(lib.objects[targetGUID]))
)
end
end
@@ -1945,10 +1986,42 @@ function lib.ApplyCarnageRefresh(targetGUID, targetName, biteSpellID)
}
-- Update CleveRoids internal tracking
if lib.objects[targetGUID][rakeSpellID] then
if lib.objects[targetGUID] and lib.objects[targetGUID][rakeSpellID] then
local oldCaster = lib.objects[targetGUID][rakeSpellID].caster
lib.objects[targetGUID][rakeSpellID].duration = rakeDuration
lib.objects[targetGUID][rakeSpellID].start = GetTime()
lib.objects[targetGUID][rakeSpellID].expiry = GetTime() + rakeDuration
-- Ensure caster is preserved (required for personal debuff tracking)
if not lib.objects[targetGUID][rakeSpellID].caster then
lib.objects[targetGUID][rakeSpellID].caster = "player"
end
if CleveRoids.debug then
-- Verify the record was actually saved
local verifyRec = lib.objects[targetGUID] and lib.objects[targetGUID][rakeSpellID]
if verifyRec then
local verifyRemaining = verifyRec.duration + verifyRec.start - GetTime()
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cffff00ff[Carnage]|r Updated CleveRoids tracking for Rake (GUID:%s, caster:%s->%s)",
tostring(targetGUID), tostring(oldCaster), tostring(verifyRec.caster))
)
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cffff00ff[Carnage VERIFY]|r Rake record: dur=%s, remaining=%.1fs",
tostring(verifyRec.duration), verifyRemaining)
)
else
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cffff0000[Carnage ERROR]|r Rake record MISSING immediately after update!")
)
end
end
else
if CleveRoids.debug then
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cffff6600[Carnage]|r WARNING: Rake record not found! GUID:%s",
tostring(targetGUID))
)
end
end
-- DON'T call pfUI's AddEffect - just update the existing entry directly
@@ -2056,6 +2129,18 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
-- Process pending personal debuffs
if lib.pendingPersonalDebuffs then
local pendingCount = table.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
DEFAULT_CHAT_FRAME:AddMessage(
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
@@ -2142,6 +2227,16 @@ 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)
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cff88ff88[Pending Process]|r Adding %s (ID:%d, %ds) GUID:%s",
spellNameDebug, pending.spellID, pending.duration or 0, guidStr)
)
end
lib:AddEffect(pending.targetGUID, pending.targetName, pending.spellID, pending.duration, 0, "player")
-- Remove any existing bleed immunity record for this NPC since the bleed landed
@@ -2205,15 +2300,23 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
for i, pending in ipairs(lib.pendingCCDebuffs) do
local elapsed = GetTime() - pending.timestamp
-- Check after 0.2 second delay (aligned with bleed verification for consistency)
if elapsed >= 0.2 then
-- 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
local verifyDelay = pending.isHiddenCC and 0.4 or 0.2
if elapsed >= verifyDelay then
local ccVerified = false
local totalDebuffs = 0
-- Check if already verified via "afflicted by" combat log message
-- This happens for hidden CC spells like Pounce when the message arrives first
if pending.verifiedByAffliction then
ccVerified = true
end
-- 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 CleveRoids.hasSuperwow and pending.targetGUID then
if not ccVerified and CleveRoids.hasSuperwow and pending.targetGUID then
-- Skip verification if target is dead (debuffs are removed on death)
if UnitIsDead(pending.targetGUID) then
ccVerified = true -- Assume CC landed, can't verify on dead target
@@ -2554,44 +2657,39 @@ ev:SetScript("OnEvent", function()
end
if ccType then
-- Skip debuff-based verification for spells with hidden CC effects (e.g., Pounce stun)
-- These spells apply CC but don't show visible debuffs, so verification always fails
-- Only trust combat log "immune" messages for these spells
if lib.hiddenCCSpells and lib.hiddenCCSpells[spellID] then
if CleveRoids.debug then
local spellName = SpellInfo(spellID) or "Unknown"
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cffff6600[CC Skip]|r %s (%s) has hidden debuff - skipping verification",
spellName, ccType)
)
-- Track ALL CC spells for immunity verification, including hidden CC (e.g., Pounce stun)
-- Hidden CC spells don't show visible debuffs, but:
-- 1. They may still be detectable via mechanic-based ValidateUnitCC check
-- 2. Combat log "afflicted by" messages confirm successful CC
-- 3. If neither detection method finds the CC, we record immunity
local isHiddenCC = lib.hiddenCCSpells and lib.hiddenCCSpells[spellID]
local spellName = SpellInfo(spellID)
-- Get target name from cache or current target
local ccTargetName = lib.guidToName[targetGUID]
if not ccTargetName then
local _, currentTargetGUID = UnitExists("target")
currentTargetGUID = CleveRoids.NormalizeGUID(currentTargetGUID)
if currentTargetGUID == targetGUID then
ccTargetName = UnitName("target")
lib.guidToName[targetGUID] = ccTargetName
end
else
local spellName = SpellInfo(spellID)
-- Get target name from cache or current target
local ccTargetName = lib.guidToName[targetGUID]
if not ccTargetName then
local _, currentTargetGUID = UnitExists("target")
currentTargetGUID = CleveRoids.NormalizeGUID(currentTargetGUID)
if currentTargetGUID == targetGUID then
ccTargetName = UnitName("target")
lib.guidToName[targetGUID] = ccTargetName
end
end
table.insert(lib.pendingCCDebuffs, {
timestamp = GetTime(),
targetGUID = targetGUID,
targetName = ccTargetName,
spellID = spellID,
spellName = spellName,
ccType = ccType,
})
end
table.insert(lib.pendingCCDebuffs, {
timestamp = GetTime(),
targetGUID = targetGUID,
targetName = ccTargetName,
spellID = spellID,
spellName = spellName,
ccType = ccType,
isHiddenCC = isHiddenCC, -- Flag for hidden CC spells
})
if CleveRoids.debug then
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cff00ff00[CC Track]|r Tracking %s (%s) on %s for immunity verification",
spellName or "Unknown", ccType, ccTargetName or "Unknown")
)
end
if CleveRoids.debug then
local hiddenStr = isHiddenCC and " (hidden CC)" or ""
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cff00ff00[CC Track]|r Tracking %s (%s) on %s for immunity verification%s",
spellName or "Unknown", ccType, ccTargetName or "Unknown", hiddenStr)
)
end
end
@@ -2814,6 +2912,17 @@ ev:SetScript("OnEvent", function()
-- DRUID CARNAGE TALENT: Save Rip cast duration for later refresh by Ferocious Bite
if CleveRoids.RipSpellIDs and CleveRoids.RipSpellIDs[spellID] then
-- Clear any stale Carnage duration override for this spell
-- New cast should use its own duration, not old Carnage refresh duration
if CleveRoids.carnageDurationOverrides and CleveRoids.carnageDurationOverrides[spellID] then
CleveRoids.carnageDurationOverrides[spellID] = nil
if CleveRoids.debug then
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cff888888[Carnage]|r Cleared stale Rip override (new cast replaces)")
)
end
end
if CleveRoids.lastRipCast then
CleveRoids.lastRipCast.spellID = spellID
CleveRoids.lastRipCast.duration = duration
@@ -2837,18 +2946,31 @@ ev:SetScript("OnEvent", function()
-- CARNAGE TALENT: Save Rake cast data for potential Ferocious Bite refresh
-- Verification that the bleed landed happens in the delayed tracking system
local isRakeSpell = CleveRoids.RakeSpellIDs and CleveRoids.RakeSpellIDs[spellID]
if isRakeSpell and CleveRoids.lastRakeCast then
CleveRoids.lastRakeCast.spellID = spellID
CleveRoids.lastRakeCast.duration = duration
CleveRoids.lastRakeCast.targetGUID = targetGUID
CleveRoids.lastRakeCast.comboPoints = comboPoints or 0
CleveRoids.lastRakeCast.timestamp = GetTime()
CleveRoids.lastRakeCast.pending = true -- Mark as pending verification
if CleveRoids.debug then
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cff00ff00[Carnage]|r Saved Rake cast (pending verification): %ds duration (%d CP) on target %s",
duration, comboPoints or 0, targetName or "Unknown")
)
if isRakeSpell then
-- Clear any stale Carnage duration override for this spell
-- New cast should use its own duration, not old Carnage refresh duration
if CleveRoids.carnageDurationOverrides and CleveRoids.carnageDurationOverrides[spellID] then
CleveRoids.carnageDurationOverrides[spellID] = nil
if CleveRoids.debug then
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cff888888[Carnage]|r Cleared stale Rake override (new cast replaces)")
)
end
end
if CleveRoids.lastRakeCast then
CleveRoids.lastRakeCast.spellID = spellID
CleveRoids.lastRakeCast.duration = duration
CleveRoids.lastRakeCast.targetGUID = targetGUID
CleveRoids.lastRakeCast.comboPoints = comboPoints or 0
CleveRoids.lastRakeCast.timestamp = GetTime()
CleveRoids.lastRakeCast.pending = true -- Mark as pending verification
if CleveRoids.debug then
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cff00ff00[Carnage]|r Saved Rake cast (pending verification): %ds duration (%d CP) on target %s",
duration, comboPoints or 0, targetName or "Unknown")
)
end
end
end
@@ -4649,6 +4771,14 @@ local function CheckCCImmunity(unitId, ccType)
end
local targetName = UnitName(unitId)
-- Fallback to GUID->name cache if UnitName fails
-- This happens when multiscan passes a GUID that isn't the current target
if not targetName or targetName == "" or targetName == "Unknown" then
local normalizedGuid = CleveRoids.NormalizeGUID(unitId)
if normalizedGuid and lib and lib.guidToName then
targetName = lib.guidToName[normalizedGuid]
end
end
if not targetName or targetName == "" then
return false
end
@@ -5016,6 +5146,26 @@ local function ParseAfflictedCombatLog()
end
RemoveCCImmunity(targetName, affliction.value)
-- Mark any pending CC verification for this target/spell as verified
-- This prevents false immunity recordings when "afflicted by" message arrives
-- before the verification delay completes (especially for hidden CC spells)
if lib.pendingCCDebuffs then
for _, pending in ipairs(lib.pendingCCDebuffs) do
if pending.targetName == targetName and
pending.ccType == affliction.value and
not pending.verifiedByAffliction then
pending.verifiedByAffliction = true
if CleveRoids.debug then
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cff00aaff[CC Verified Early]|r %s on %s confirmed via 'afflicted by' message",
pending.ccType, targetName)
)
end
break -- Only mark one pending entry
end
end
end
elseif affliction.type == "school" then
-- School/bleed effect landed - remove any false school immunity
if CleveRoids.debug then
@@ -5109,6 +5259,14 @@ function CleveRoids.CheckImmunity(unitId, spellOrSchool)
end
local targetName = UnitName(unitId)
-- Fallback to GUID->name cache if UnitName fails
-- This happens when multiscan passes a GUID that isn't the current target
if not targetName or targetName == "" or targetName == "Unknown" then
local normalizedGuid = CleveRoids.NormalizeGUID(unitId)
if normalizedGuid and lib and lib.guidToName then
targetName = lib.guidToName[normalizedGuid]
end
end
if not targetName or targetName == "" then
return false
end