update immunity system to include acknowledging reflection and invulnerability spells, fix firstaction and nofirstaction slashcommands

This commit is contained in:
Jrc13245
2026-01-15 17:11:54 -05:00
parent 612873260d
commit f0e2f6f5d9
3 changed files with 240 additions and 43 deletions
+55 -39
View File
@@ -1278,56 +1278,72 @@ function CleveRoids.ExecuteMacroBody(body,inline)
end
for k,v in pairs(lines) do
-- Check both macro stop flags before each line
local trimmed = CleveRoids.Trim(v)
local cmdHandled = false
-- IMPORTANT: Check for /nofirstaction BEFORE the stop flag check
-- This allows /nofirstaction to clear the stopMacroFlag set by /firstaction
local _, _, nofirstactionArgs = string.find(trimmed, "^/nofirstaction%s*(.*)")
if nofirstactionArgs then
CleveRoids.DoNoFirstAction(nofirstactionArgs)
-- Also clear stopMacroFlag if it was set by firstaction mechanism
-- (but NOT if it was set by explicit /stopmacro)
if CleveRoids.stopOnCastFlag == false and CleveRoids.stopMacroFlag then
-- stopOnCastFlag is false (just cleared by DoNoFirstAction), stopMacroFlag is true
-- This means stopMacroFlag was set by the firstaction mechanism, clear it
CleveRoids.stopMacroFlag = false
if CleveRoids.macroRefDebug then
CleveRoids.Print("|cff00ff00[MacroRef]|r /nofirstaction cleared stopMacroFlag - resuming macro")
end
end
if CleveRoids.macroRefDebug then
CleveRoids.Print("|cff88ff88[MacroRef]|r Executing line " .. k .. ": " .. string.sub(v, 1, 60))
end
cmdHandled = true
end
-- Check both macro stop flags before each line (but skip if we just handled /nofirstaction)
-- stopMacroFlag: stop this AND parent macros
-- skipMacroFlag: stop only this macro (parent continues)
if CleveRoids.stopMacroFlag or CleveRoids.skipMacroFlag then
if not cmdHandled and (CleveRoids.stopMacroFlag or CleveRoids.skipMacroFlag) then
if CleveRoids.macroRefDebug then
local reason = CleveRoids.stopMacroFlag and "/stopmacro" or "/skipmacro"
CleveRoids.Print("|cffff8800[MacroRef]|r Stopped at line " .. k .. " due to " .. reason)
end
break
end
if CleveRoids.macroRefDebug then
CleveRoids.Print("|cff88ff88[MacroRef]|r Executing line " .. k .. ": " .. string.sub(v, 1, 60))
end
-- IMPORTANT: Handle macro control commands directly to bypass Blizzard's built-in /stopmacro
-- Blizzard intercepts bare /stopmacro before it reaches our SlashCmdList handler
local trimmed = CleveRoids.Trim(v)
local cmdHandled = false
-- Check for /stopmacro (with or without conditionals)
local _, _, stopmacroArgs = string.find(trimmed, "^/stopmacro%s*(.*)")
if stopmacroArgs then
CleveRoids.DoStopMacro(stopmacroArgs)
cmdHandled = true
end
-- Check for /skipmacro (with or without conditionals)
if not cmdHandled then
local _, _, skipmacroArgs = string.find(trimmed, "^/skipmacro%s*(.*)")
if skipmacroArgs then
CleveRoids.DoSkipMacro(skipmacroArgs)
if CleveRoids.macroRefDebug then
CleveRoids.Print("|cff88ff88[MacroRef]|r Executing line " .. k .. ": " .. string.sub(v, 1, 60))
end
-- IMPORTANT: Handle macro control commands directly to bypass Blizzard's built-in /stopmacro
-- Blizzard intercepts bare /stopmacro before it reaches our SlashCmdList handler
-- Check for /stopmacro (with or without conditionals)
local _, _, stopmacroArgs = string.find(trimmed, "^/stopmacro%s*(.*)")
if stopmacroArgs then
CleveRoids.DoStopMacro(stopmacroArgs)
cmdHandled = true
end
end
-- Check for /firstaction (with or without conditionals)
if not cmdHandled then
local _, _, firstactionArgs = string.find(trimmed, "^/firstaction%s*(.*)")
if firstactionArgs then
CleveRoids.DoFirstAction(firstactionArgs)
cmdHandled = true
-- Check for /skipmacro (with or without conditionals)
if not cmdHandled then
local _, _, skipmacroArgs = string.find(trimmed, "^/skipmacro%s*(.*)")
if skipmacroArgs then
CleveRoids.DoSkipMacro(skipmacroArgs)
cmdHandled = true
end
end
end
-- Check for /nofirstaction (with or without conditionals)
if not cmdHandled then
local _, _, nofirstactionArgs = string.find(trimmed, "^/nofirstaction%s*(.*)")
if nofirstactionArgs then
CleveRoids.DoNoFirstAction(nofirstactionArgs)
cmdHandled = true
-- Check for /firstaction (with or without conditionals)
if not cmdHandled then
local _, _, firstactionArgs = string.find(trimmed, "^/firstaction%s*(.*)")
if firstactionArgs then
CleveRoids.DoFirstAction(firstactionArgs)
cmdHandled = true
end
end
end
@@ -3302,7 +3318,7 @@ function CleveRoids.DoStopMacro(msg)
-- PERFORMANCE: Use numeric iteration to avoid pairs() iterator allocation
local parts = CleveRoids.splitStringIgnoringQuotes(CleveRoids.Trim(msg))
for i = 1, table.getn(parts) do
if CleveRoids.DoWithConditionals(msg, nil, nil, not CleveRoids.hasSuperwow, "STOPMACRO") then
if CleveRoids.DoWithConditionals(parts[i], nil, nil, not CleveRoids.hasSuperwow, "STOPMACRO") then
return true
end
end
@@ -3314,7 +3330,7 @@ function CleveRoids.DoSkipMacro(msg)
-- PERFORMANCE: Use numeric iteration to avoid pairs() iterator allocation
local parts = CleveRoids.splitStringIgnoringQuotes(CleveRoids.Trim(msg))
for i = 1, table.getn(parts) do
if CleveRoids.DoWithConditionals(msg, nil, nil, not CleveRoids.hasSuperwow, "SKIPMACRO") then
if CleveRoids.DoWithConditionals(parts[i], nil, nil, not CleveRoids.hasSuperwow, "SKIPMACRO") then
return true
end
end
@@ -3335,7 +3351,7 @@ function CleveRoids.DoFirstAction(msg)
-- Has conditionals - use DoWithConditionals to evaluate them
local parts = CleveRoids.splitStringIgnoringQuotes(CleveRoids.Trim(msg))
for i = 1, table.getn(parts) do
if CleveRoids.DoWithConditionals(msg, nil, nil, not CleveRoids.hasSuperwow, "FIRSTACTION") then
if CleveRoids.DoWithConditionals(parts[i], nil, nil, not CleveRoids.hasSuperwow, "FIRSTACTION") then
return true
end
end
@@ -3362,7 +3378,7 @@ function CleveRoids.DoNoFirstAction(msg)
-- Has conditionals - use DoWithConditionals to evaluate them
local parts = CleveRoids.splitStringIgnoringQuotes(CleveRoids.Trim(msg))
for i = 1, table.getn(parts) do
if CleveRoids.DoWithConditionals(msg, nil, nil, not CleveRoids.hasSuperwow, "NOFIRSTACTION") then
if CleveRoids.DoWithConditionals(parts[i], nil, nil, not CleveRoids.hasSuperwow, "NOFIRSTACTION") then
return true
end
end
+2 -1
View File
@@ -268,7 +268,8 @@ end
-- Immunity data version - increment this when changing immunity data format
-- This will cause all immunity data to be reset on addon update
CleveRoids.IMMUNITY_DATA_VERSION = 2
-- v3: Fixed Master Strike false physical immunity recording (split CC spell handling)
CleveRoids.IMMUNITY_DATA_VERSION = 3
-- Call on next frame to ensure everything is loaded
local initFrame = CreateFrame("Frame")
+183 -3
View File
@@ -2723,7 +2723,20 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
end
local DEBUFF_CAP_THRESHOLD = 47 -- Max is 48 (16 visible + 32 overflow)
if totalDebuffs < DEBUFF_CAP_THRESHOLD then
-- SPLIT CC SPELLS: Skip immunity recording for spells with physical damage + resistable CC
-- (e.g., Master Strike) - physical damage lands but CC can be resisted independently
local isSplitCCSpell = SPLIT_CC_SPELLS[pending.spellID]
if isSplitCCSpell then
if debug then
local spellNameDebug = pending.spellName or (_SpellInfo(pending.spellID) or "Unknown")
DEFAULT_CHAT_FRAME:AddMessage(
_string_format("|cff00aaff[Split CC Skip]|r %s CC resisted on %s - skipping CC immunity recording",
spellNameDebug, resolvedTargetName or "Unknown")
)
end
-- Skip immunity recording - damage landed, only CC was resisted
elseif totalDebuffs < DEBUFF_CAP_THRESHOLD then
-- Few debuffs = likely CC immunity
if resolvedTargetName and resolvedTargetName ~= "" and pending.ccType then
CleveRoids.RecordCCImmunity(resolvedTargetName, pending.ccType, nil, pending.spellName)
@@ -2863,7 +2876,20 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
else
-- Debuff didn't land - check if it's immunity or debuff cap
local DEBUFF_CAP_THRESHOLD = 47 -- Max is 48 (16 visible + 32 overflow)
if totalDebuffs < DEBUFF_CAP_THRESHOLD then
-- SPLIT CC SPELLS: Skip immunity recording for spells with physical damage + resistable CC
-- (e.g., Master Strike) - the physical damage lands but CC can be resisted independently
local isSplitCCSpell = SPLIT_CC_SPELLS[pending.spellID] or SPLIT_CC_SPELLS[pending.castSpellID]
if isSplitCCSpell then
if debug then
local spellNameDebug = _SpellInfo(pending.castSpellID or pending.spellID) or "Unknown"
DEFAULT_CHAT_FRAME:AddMessage(
_string_format("|cff00aaff[Split CC Skip]|r %s CC resisted on %s - skipping immunity recording (physical damage landed)",
spellNameDebug, pending.targetName or "Unknown")
)
end
-- Skip immunity recording - damage landed, only CC was resisted
elseif totalDebuffs < DEBUFF_CAP_THRESHOLD then
-- Few debuffs = likely immunity
if pending.targetName and pending.targetName ~= "" and pending.school then
-- Record immunity for this spell's school
@@ -4873,6 +4899,33 @@ local SPLIT_DAMAGE_SPELLS = {
["Garrote"] = { initial = "physical", debuff = "bleed" },
}
-- Spells with physical damage + resistable CC effect (weapon-dependent)
-- These spells deal physical damage that ALWAYS lands (unless dodged/parried/blocked),
-- but apply a CC effect that can be resisted independently.
-- When the CC is resisted, we should NOT record physical immunity.
--
-- Master Strike (Warrior): 35% weapon damage + weapon-dependent CC
-- Mace: Disorient (3s) Sword: Disarm (3s) Axe: Immobilize (4s)
-- Polearm: Dismount Fist: Knockdown (2s) Dagger: Silence (3s)
-- Staff: Self-buff (parry, no CC)
local SPLIT_CC_SPELLS = {
[54016] = true, -- Master Strike (Mace) - Disorient
[54017] = true, -- Master Strike (Sword) - Disarm
[54018] = true, -- Master Strike (Axe) - Immobilize
[54019] = true, -- Master Strike (Polearm) - Dismount
[54020] = true, -- Master Strike (Fist) - Knockdown
[54021] = true, -- Master Strike (Staff) - Parry buff
[54022] = true, -- Master Strike (Dagger) - Silence
[54023] = true, -- Master Strike (Base)
[54024] = true, -- Master Strike (Level 0 variant)
}
-- Name-based lookup for split CC spells (for combat log parsing where only name is available)
-- All weapon variants share the same display name, so we need to check by name too
local SPLIT_CC_SPELL_NAMES = {
["Master Strike"] = true,
}
-- Known non-damaging spells that won't be learned via SPELL_DAMAGE_EVENT
-- These need explicit school mapping to avoid pattern matching errors
-- (e.g., "Faerie Fire" contains "fire" but is actually arcane)
@@ -5277,6 +5330,94 @@ local function GetUnitBuffs(unit)
return buffs
end
-- Spells with INVULNERABILITY mechanic (mechanic 25) from BuffLib SpellData DBC
-- These grant temporary immunity and should not trigger permanent immunity recording
-- Source: BuffLib/SpellData.lua - extracted from DBC files
local INVULNERABILITY_SPELL_IDS = {
-- Paladin
[498] = true, -- Divine Protection (Rank 1)
[642] = true, -- Divine Shield (Rank 1)
[1020] = true, -- Divine Shield (Rank 2)
[1022] = true, -- Blessing of Protection (Rank 1)
[5573] = true, -- Divine Protection (Rank 2)
[5599] = true, -- Blessing of Protection (Rank 2)
[10278] = true, -- Blessing of Protection (Rank 3)
[25771] = true, -- Forbearance (debuff after immunity)
-- Old/Unused Paladin
[1052] = true, -- zzOLDBlessing of Righteousness
[5601] = true, -- zzOLDBlessing of Righteousness
[5602] = true, -- zzOLDBlessing of Righteousness
[10280] = true, -- zzOLDBlessing of Righteousness
[10281] = true, -- zzOLDBlessing of Righteousness
-- Rogue
[6770] = true, -- Sap (invuln during effect)
-- NPC/Misc
[7992] = true, -- Slowing Poison
[11638] = true, -- Radiation Poisoning
[14897] = true, -- Slowing Poison
[16603] = true, -- Demonfork
[16791] = true, -- Furious Anger
[17407] = true, -- Wound
[18208] = true, -- Poison
[23230] = true, -- Blood Fury
[24005] = true, -- Food
[24707] = true, -- Food
[24865] = true, -- Sanctified Orb
[26263] = true, -- Dim Sum
[28522] = true, -- Icebolt
[29055] = true, -- Refreshing Red Apple
[29325] = true, -- Acid Volley
[29330] = true, -- Sapphiron's Wing Buffet Despawn
-- Spell Reflection (no DBC mechanic, but grants immunity to reflected schools)
[9941] = true, -- Spell Reflection
[9943] = true, -- Spell Reflection
[10074] = true, -- Spell Reflection
[11818] = true, -- Spell Reflection
[21118] = true, -- Spell Reflection
-- School-specific Reflectors (Engineering items)
[23097] = true, -- Fire Reflector
[23131] = true, -- Frost Reflector
[23132] = true, -- Shadow Reflector
[23178] = true, -- Nature Reflector
[23216] = true, -- Arcane Reflector
-- Multi-school Reflect (NPC abilities)
[13022] = true, -- Fire and Arcane Reflect
[19595] = true, -- Shadow and Frost Reflect
-- Generic Reflection buffs
[3651] = true, -- Shield of Reflection
[9906] = true, -- Reflection
[10831] = true, -- Reflection Field
[17106] = true, -- Reflection
[17107] = true, -- Reflection
[17108] = true, -- Reflection
[20223] = true, -- Magic Reflection
[20619] = true, -- Magic Reflection
[22067] = true, -- Reflection
[23920] = true, -- Shield Reflection
[23921] = true, -- Shield Reflection
[27564] = true, -- Reflection
}
-- Check if a unit has any immunity-granting buff active
-- Uses only spell IDs from DBC mechanic 25 (INVULNERABILITY) for reliability
-- Returns the buff name if found, nil otherwise
local function HasImmunityGrantingBuff(unit)
if not CleveRoids.hasSuperwow then return nil end
if not UnitExists(unit) then return nil end
for i = 1, 32 do
local texture, stacks, spellID = UnitBuff(unit, i)
if not texture then break end
if spellID and INVULNERABILITY_SPELL_IDS[spellID] then
local buffName = SpellInfo(spellID) or ("SpellID:" .. spellID)
return buffName
end
end
return nil
end
-- ============================================================================
-- CC IMMUNITY TRACKING
-- ============================================================================
@@ -5828,6 +5969,18 @@ local function ParseImmunityCombatLog()
-- Try to get spell ID for more accurate school detection
local spellID = CleveRoids.GetSpellIdForName and CleveRoids.GetSpellIdForName(spellName)
-- SPLIT CC SPELLS: Skip immunity recording for spells with physical damage + resistable CC
-- (e.g., Master Strike) - physical damage lands but CC can be resisted independently
-- Check both spell ID and spell name (all weapon variants share the same display name)
local baseName = string.gsub(spellName, "%s*%(.-%)%s*$", "")
if (spellID and SPLIT_CC_SPELLS[spellID]) or SPLIT_CC_SPELL_NAMES[baseName] then
if CleveRoids.debug then
CleveRoids.Print("|cff00aaff[Split CC Skip]|r " .. spellName .. " CC immune on " .. targetName .. " - skipping immunity recording (physical damage landed)")
end
CancelPendingVerification(targetName, spellName)
return
end
-- Check if this is a CC spell - if so, record CC immunity instead
local ccType = spellID and GetSpellCCType(spellID)
if ccType then
@@ -5841,9 +5994,36 @@ local function ParseImmunityCombatLog()
end
end
-- No single buff detected, record as permanent immunity
-- Check for immunity-granting buffs before recording permanent immunity
-- If target has a known immunity buff (Divine Shield, Ice Block, etc.), skip recording
local immunityBuff = nil
if UnitExists("target") and UnitName("target") == targetName then
immunityBuff = HasImmunityGrantingBuff("target")
end
if immunityBuff then
if CleveRoids.debug then
CleveRoids.Print("|cff00aaff[Temporary Immunity Skip]|r " .. targetName .. " has " .. immunityBuff .. " active - skipping permanent immunity recording for " .. spellName)
end
CancelPendingVerification(targetName, spellName)
return
end
-- No immunity buff detected, record as permanent immunity
local spellID = CleveRoids.GetSpellIdForName and CleveRoids.GetSpellIdForName(spellName)
-- SPLIT CC SPELLS: Skip immunity recording for spells with physical damage + resistable CC
-- (e.g., Master Strike) - physical damage lands but CC can be resisted independently
-- Check both spell ID and spell name (all weapon variants share the same display name)
local baseName = string.gsub(spellName, "%s*%(.-%)%s*$", "")
if (spellID and SPLIT_CC_SPELLS[spellID]) or SPLIT_CC_SPELL_NAMES[baseName] then
if CleveRoids.debug then
CleveRoids.Print("|cff00aaff[Split CC Skip]|r " .. spellName .. " CC immune on " .. targetName .. " - skipping immunity recording (physical damage landed)")
end
CancelPendingVerification(targetName, spellName)
return
end
-- Check if this is a CC spell - if so, record CC immunity instead
local ccType = spellID and GetSpellCCType(spellID)
if ccType then