diff --git a/ComboPointTracker.lua b/ComboPointTracker.lua index 7dafc61..dcd043a 100644 --- a/ComboPointTracker.lua +++ b/ComboPointTracker.lua @@ -88,6 +88,28 @@ CleveRoids.RakeSpellIDs = { [9904] = true, -- Rank 4 } +-- Pounce Bleed spell IDs (for immunity detection - bleed portion of Pounce) +-- Note: Pounce also applies a stun, but these are the bleed debuff IDs +CleveRoids.PounceBleedSpellIDs = { + [9005] = true, -- Rank 1 + [9823] = true, -- Rank 2 + [9827] = true, -- Rank 3 +} + +-- Combined table for all bleed spells that need immunity detection +-- Used when checking if a cast bleed failed to apply (indicates bleed immunity) +CleveRoids.BleedSpellIDs = { + -- Rake + [1822] = true, -- Rank 1 + [1823] = true, -- Rank 2 + [1824] = true, -- Rank 3 + [9904] = true, -- Rank 4 + -- Pounce Bleed + [9005] = true, -- Rank 1 + [9823] = true, -- Rank 2 + [9827] = true, -- Rank 3 +} + -- ============================================================================= -- SHAMAN: Molten Blast → Flame Shock Refresh (TWoW Custom) -- When Molten Blast hits, it refreshes Flame Shock duration on the target diff --git a/Conditionals.lua b/Conditionals.lua index 290a246..2941535 100644 --- a/Conditionals.lua +++ b/Conditionals.lua @@ -3386,9 +3386,25 @@ CleveRoids.Keywords = { end, nodebuff = function(conditionals) - return NegatedMulti(conditionals.nodebuff, function(v) - return not CleveRoids.ValidateUnitDebuff(conditionals.target, v) + if CleveRoids.debug then + local vals = conditionals.nodebuff + local valStr = type(vals) == "table" and table.concat(vals, ", ") or tostring(vals) + DEFAULT_CHAT_FRAME:AddMessage("|cff00ffff[nodebuff]|r Checking: " .. valStr .. " on " .. (conditionals.target or "target")) + end + local result = NegatedMulti(conditionals.nodebuff, function(v) + local hasDebuff = CleveRoids.ValidateUnitDebuff(conditionals.target, v) + if CleveRoids.debug then + local vName = type(v) == "table" and (v.name or "table") or tostring(v) + DEFAULT_CHAT_FRAME:AddMessage(string.format( + "|cff00ffff[nodebuff]|r ValidateUnitDebuff(%s) = %s, returning %s", + vName, tostring(hasDebuff), tostring(not hasDebuff))) + end + return not hasDebuff end, conditionals, "nodebuff") + if CleveRoids.debug then + DEFAULT_CHAT_FRAME:AddMessage("|cff00ffff[nodebuff]|r Final result: " .. tostring(result)) + end + return result end, mybuff = function(conditionals) @@ -4275,11 +4291,26 @@ CleveRoids.Keywords = { checkValue = conditionals.action end + if CleveRoids.debug then + DEFAULT_CHAT_FRAME:AddMessage(string.format( + "|cffff9900[noimmune]|r Checking: %s (action=%s) on %s", + tostring(checkValue), tostring(conditionals.action), conditionals.target or "target")) + end + if not checkValue then + if CleveRoids.debug then + DEFAULT_CHAT_FRAME:AddMessage("|cffff9900[noimmune]|r No checkValue, returning true") + end return true -- If we can't determine spell/school, assume not immune end - return not CleveRoids.CheckImmunity(conditionals.target or "target", checkValue) + local isImmune = CleveRoids.CheckImmunity(conditionals.target or "target", checkValue) + if CleveRoids.debug then + DEFAULT_CHAT_FRAME:AddMessage(string.format( + "|cffff9900[noimmune]|r CheckImmunity(%s) = %s, returning %s", + checkValue, tostring(isImmune), tostring(not isImmune))) + end + return not isImmune end, -- SP_SwingTimer integration conditionals diff --git a/Core.lua b/Core.lua index f892f30..1ff55b1 100644 --- a/Core.lua +++ b/Core.lua @@ -2235,7 +2235,9 @@ function CleveRoids.DoWithConditionals(msg, hook, fixEmptyTargetFunc, targetBefo else local castMsg = msg -- FLEXIBLY check for any rank text like "(Rank 9)" before adding the highest rank - if action == CastSpellByName and not string.find(msg, "%(.*%)") then + -- Use specific "(Rank" check instead of any parentheses, so spells like + -- "Faerie Fire (Feral)" still get their rank appended automatically + if action == CastSpellByName and not string.find(msg, "%(Rank") then local sp = CleveRoids.GetSpell(msg) local rank = sp and (sp.rank or (sp.highest and sp.highest.rank)) if rank and rank ~= "" then @@ -3145,7 +3147,9 @@ function CleveRoids.DoCastSequence(sequence) local function cast_by_name(msg) msg = msg or "" - if not string.find(msg, "%(%s*.-%s*%)%s*$") then + -- Check specifically for "(Rank" to allow spells like "Faerie Fire (Feral)" + -- to still get their rank appended automatically + if not string.find(msg, "%(Rank") then local sp = CleveRoids.GetSpell(msg) local r = (sp and sp.rank) or (sp and sp.highest and sp.highest.rank) if r and r ~= "" then msg = msg .. "(" .. r .. ")" end diff --git a/Utility.lua b/Utility.lua index 7e74d53..8946174 100644 --- a/Utility.lua +++ b/Utility.lua @@ -2447,36 +2447,74 @@ ev:SetScript("OnEvent", function() end end - -- DRUID CARNAGE TALENT: Save Rake cast duration for later refresh by Ferocious Bite - -- Also includes debuff cap verification (Credits: Avitasia / Cursive addon) - if CleveRoids.RakeSpellIDs and CleveRoids.RakeSpellIDs[spellID] then - -- Check if mob is in bleed whitelist (high-debuff scenarios) + -- BLEED IMMUNITY DETECTION & CARNAGE TALENT SUPPORT + -- Checks if bleed debuff (Rake, Pounce) was actually applied after cast + -- If missing with few debuffs on target → bleed immunity (auto-learn) + -- If missing with many debuffs → debuff cap (Carnage won't track) + -- Credits: Avitasia / Cursive addon (debuff cap logic) + local isBleedSpell = CleveRoids.BleedSpellIDs and CleveRoids.BleedSpellIDs[spellID] + local isRakeSpell = CleveRoids.RakeSpellIDs and CleveRoids.RakeSpellIDs[spellID] + + if isBleedSpell then + -- Check if mob is in bleed whitelist (high-debuff scenarios, skip verification) local isWhitelisted = CleveRoids.MobsThatBleed and CleveRoids.MobsThatBleed[targetGUID] - local rakeVerified = true + local bleedVerified = true + local totalDebuffs = 0 if not isWhitelisted and CleveRoids.hasSuperwow then - -- Verify Rake is actually on the target (may have been pushed off at debuff cap) - rakeVerified = false + -- Verify bleed is actually on the target + bleedVerified = false for slot = 1, 48 do local _, _, _, debuffSpellID = UnitDebuff(targetGUID, slot) if not debuffSpellID then if slot <= 16 then break end -- Regular debuffs are dense, overflow continues on nil - elseif debuffSpellID == spellID then - rakeVerified = true - break + else + totalDebuffs = totalDebuffs + 1 + if debuffSpellID == spellID then + bleedVerified = true + -- Don't break - continue counting total debuffs for immunity vs cap detection + end end end - if not rakeVerified and CleveRoids.debug then - DEFAULT_CHAT_FRAME:AddMessage( - string.format("|cffff6600[Rake Debuff Cap]|r Rake not found on %s - likely pushed off at debuff cap", - targetName or "Unknown") - ) + -- BLEED IMMUNITY DETECTION + -- If bleed is missing and target has few debuffs, it's likely immunity (not debuff cap) + if not bleedVerified then + local DEBUFF_CAP_THRESHOLD = 12 -- If fewer than this, likely immunity not cap + + if totalDebuffs < DEBUFF_CAP_THRESHOLD then + -- Few debuffs = likely bleed immunity, not debuff cap + -- Record immunity for this target + if targetName and targetName ~= "" then + -- Use delayed call to ensure RecordImmunity is available + -- (it's defined later in the file, but should be set by load time) + if CleveRoids.RecordImmunity then + local spellNameForImmunity = SpellInfo(spellID) or "Bleed" + CleveRoids.RecordImmunity(targetName, spellNameForImmunity, nil, spellID) + + if CleveRoids.debug then + DEFAULT_CHAT_FRAME:AddMessage( + string.format("|cffff6600[Bleed Immunity]|r %s is immune to bleed (%s) - only %d debuffs on target", + targetName, spellNameForImmunity, totalDebuffs) + ) + end + end + end + else + -- Many debuffs = likely pushed off at debuff cap + if CleveRoids.debug then + local spellNameDebug = SpellInfo(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)", + spellNameDebug, targetName or "Unknown", totalDebuffs) + ) + end + end end end - -- Only save Rake cast data if verified (or can't verify) - if rakeVerified and CleveRoids.lastRakeCast then + -- CARNAGE TALENT: Only save Rake cast data if verified (for Ferocious Bite refresh) + if isRakeSpell and bleedVerified and CleveRoids.lastRakeCast then CleveRoids.lastRakeCast.spellID = spellID CleveRoids.lastRakeCast.duration = duration CleveRoids.lastRakeCast.targetGUID = targetGUID @@ -3702,6 +3740,8 @@ local SPLIT_DAMAGE_SPELLS = { local KNOWN_NON_DAMAGING_SPELLS = { -- Druid ["Faerie Fire"] = "arcane", + ["Faerie Fire (Feral)"] = "arcane", + ["Faerie Fire (Bear)"] = "arcane", ["Moonfire"] = "arcane", -- Initial hit deals damage, but debuff is arcane ["Insect Swarm"] = "nature", ["Abolish Poison"] = "nature", @@ -4139,6 +4179,9 @@ local function RecordImmunity(npcName, spellName, conditionalBuff, spellID) end end +-- Expose RecordImmunity globally for use by debuff verification (bleed immunity detection) +CleveRoids.RecordImmunity = RecordImmunity + -- Combat log parser for immunity detection -- Handles both RAW_COMBATLOG (arg1=formatted, arg2=raw) and CHAT_MSG events (arg1=formatted only) local function ParseImmunityCombatLog()