diff --git a/Conditionals.lua b/Conditionals.lua index 5cb1ced..29a45fc 100644 --- a/Conditionals.lua +++ b/Conditionals.lua @@ -7492,7 +7492,7 @@ CleveRoids.Keywords = { if not CleveRoids.queuedSpell then return false end -- If no specific spell name provided, check if ANY spell is queued - if not conditionals.queuedspell or table.getn(conditionals.queuedspell) == 0 then + if not conditionals.queuedspell or (type(conditionals.queuedspell) == "table" and table.getn(conditionals.queuedspell) == 0) then return true end @@ -7509,7 +7509,7 @@ CleveRoids.Keywords = { if not CleveRoids.hasNampower then return false end -- If no specific spell name, check if NO spell is queued - if not conditionals.noqueuedspell or table.getn(conditionals.noqueuedspell) == 0 then + if not conditionals.noqueuedspell or (type(conditionals.noqueuedspell) == "table" and table.getn(conditionals.noqueuedspell) == 0) then return CleveRoids.queuedSpell == nil end diff --git a/Init.lua b/Init.lua index c572b11..1a62216 100644 --- a/Init.lua +++ b/Init.lua @@ -285,7 +285,8 @@ end -- This will cause all immunity data to be reset on addon update -- v3: Fixed Master Strike false physical immunity recording (split CC spell handling) -- v4: Fixed false immunity recording when target dies with spells in-flight (dead = IMMUNE) -CleveRoids.IMMUNITY_DATA_VERSION = 4 +-- v5: Fixed false physical immunity from unknown spell schools (now uses DBC lookup; unknown defaults to nil not "physical") +CleveRoids.IMMUNITY_DATA_VERSION = 5 -- Call on next frame to ensure everything is loaded local initFrame = CreateFrame("Frame") diff --git a/Utility.lua b/Utility.lua index c3513dc..a45d74b 100644 --- a/Utility.lua +++ b/Utility.lua @@ -7049,12 +7049,15 @@ local spellSchoolCache = {} -- spellID: Optional spell ID for more accurate lookups -- Returns: School name (fire, frost, nature, shadow, arcane, holy, physical, bleed) or nil -- Priority: --- 1. Learned from Nampower damage events (most accurate) --- 2. Cached lookups --- 3. Split damage spell table (e.g., Rake = bleed debuff) --- 4. Known non-damaging spells table (e.g., Faerie Fire = arcane) --- 5. Tooltip scanning (player's spellbook only) --- 6. Name pattern matching (fallback, least accurate) +-- 0. Split damage spell table (e.g., Rake = bleed debuff) +-- 1. Learned from Nampower damage events (most accurate for bleed detection) +-- 2. Name-to-ID resolution + learned mapping +-- 3. DBC spell school via GetSpellRecField (authoritative for non-bleed spells) +-- 4. Cached lookups +-- 6. Known non-damaging spells table (e.g., Faerie Fire = arcane) +-- 7. Tooltip scanning (player's spellbook only) +-- 8. Name pattern matching (fallback, least accurate) +-- Returns nil if school cannot be determined (not cached) local function GetSpellSchool(spellName, spellID) if not spellName and not spellID then return nil end @@ -7093,6 +7096,26 @@ local function GetSpellSchool(spellName, spellID) end end + -- PRIORITY 3: DBC spell school lookup via Nampower (authoritative for non-bleed spells) + -- Note: DBC school field returns 0 (physical) for bleed spells too, so learned mapping + -- from damage events (priority 1-2) takes precedence for bleed detection + if spellID and GetSpellRecField then + local dbcSchool = GetSpellRecField(spellID, "school") + if dbcSchool and SCHOOL_NAMES[dbcSchool] then + local school = SCHOOL_NAMES[dbcSchool] + -- Don't cache physical from DBC - it might be bleed (learned later from damage events) + if school ~= "physical" then + if not baseName and spellName then + baseName = string.gsub(spellName, "%s*%(.-%)%s*$", "") + end + if baseName then + spellSchoolCache[baseName] = school + end + return school + end + end + end + -- If we only have spellID but no name, try to get name from GetSpellRecField if spellID and not spellName and GetSpellRecField then spellName = GetSpellRecField(spellID, "name") @@ -7105,14 +7128,14 @@ local function GetSpellSchool(spellName, spellID) baseName = string.gsub(spellName, "%s*%(.-%)%s*$", "") end - -- PRIORITY 3: Check cache + -- PRIORITY 4: Check cache if spellSchoolCache[baseName] then return spellSchoolCache[baseName] end - -- PRIORITY 4: SPLIT_DAMAGE_SPELLS already checked at Priority 0 + -- PRIORITY 5: SPLIT_DAMAGE_SPELLS already checked at Priority 0 - -- PRIORITY 5: Check known non-damaging spells + -- PRIORITY 6: Check known non-damaging spells -- These won't be learned via damage events and need explicit mapping if KNOWN_NON_DAMAGING_SPELLS[baseName] then local school = KNOWN_NON_DAMAGING_SPELLS[baseName] @@ -7120,7 +7143,7 @@ local function GetSpellSchool(spellName, spellID) return school end - -- PRIORITY 6: Try to find spell in player's spellbook and scan tooltip + -- PRIORITY 7: Try to find spell in player's spellbook and scan tooltip local school = nil local spell = CleveRoids.GetSpell(baseName) @@ -7166,7 +7189,7 @@ local function GetSpellSchool(spellName, spellID) end end - -- PRIORITY 7: Fallback pattern matching for common spell name patterns + -- PRIORITY 8: Fallback pattern matching for common spell name patterns -- NOTE: This is the lowest priority fallback - be specific to avoid false positives -- (e.g., "Faerie Fire" should not match as "fire" school) if not school then @@ -7218,14 +7241,16 @@ local function GetSpellSchool(spellName, spellID) string.find(lower, "judgment") or string.find(lower, "hammer of wrath") then school = "holy" - -- Default to physical for melee attacks and unknown spells - else - school = "physical" + -- No default fallback - return nil for unknown schools + -- RecordImmunity() will classify nil as "unknown" rather than + -- incorrectly attributing physical immunity end end - -- Cache the result - spellSchoolCache[baseName] = school + -- Cache the result (only cache if we actually determined a school) + if school then + spellSchoolCache[baseName] = school + end return school end