mirror of
https://github.com/brues-code/SuperCleveRoidMacros.git
synced 2026-09-16 03:38:00 +00:00
fix target overflowframe and possible immunity system fix
This commit is contained in:
+8
-8
@@ -2922,7 +2922,7 @@ function CleveRoids.CountEnemiesMatching(checkFunc)
|
||||
local checked = {}
|
||||
|
||||
-- 1. Current target (use "target" token for best API compatibility)
|
||||
if UnitExists("target") and UnitCanAttack("player", "target") and not UnitIsDeadOrGhost("target") then
|
||||
if UnitExists("target") and UnitCanAttack("player", "target") and not CleveRoids.IsUnitDeadOrGhost("target") then
|
||||
local guid = CleveRoids.GetGUID("target")
|
||||
if guid then
|
||||
checked[guid] = true
|
||||
@@ -2937,7 +2937,7 @@ function CleveRoids.CountEnemiesMatching(checkFunc)
|
||||
local function tryUnit(unit)
|
||||
if not UnitExists(unit) then return end
|
||||
if not UnitCanAttack("player", unit) then return end
|
||||
if UnitIsDeadOrGhost(unit) then return end
|
||||
if CleveRoids.IsUnitDeadOrGhost(unit) then return end
|
||||
local guid = CleveRoids.GetGUID(unit)
|
||||
if not guid or checked[guid] then return end
|
||||
checked[guid] = true
|
||||
@@ -5535,7 +5535,7 @@ end
|
||||
--- @return boolean
|
||||
function CleveRoids.IsUnitInMeleeRange(unit, cleaveRange)
|
||||
if not UnitExists(unit) then return false end
|
||||
if UnitIsDead(unit) then return false end
|
||||
if CleveRoids.IsUnitDead(unit) then return false end
|
||||
|
||||
local threshold = cleaveRange and 5 or 2
|
||||
|
||||
@@ -5924,22 +5924,22 @@ CleveRoids.Keywords = {
|
||||
|
||||
dead = function(conditionals)
|
||||
if not conditionals.target then return false end
|
||||
return UnitIsDeadOrGhost(conditionals.target)
|
||||
return CleveRoids.IsUnitDeadOrGhost(conditionals.target)
|
||||
end,
|
||||
|
||||
alive = function(conditionals)
|
||||
if not conditionals.target then return false end
|
||||
return not UnitIsDeadOrGhost(conditionals.target)
|
||||
return not CleveRoids.IsUnitDeadOrGhost(conditionals.target)
|
||||
end,
|
||||
|
||||
noalive = function(conditionals)
|
||||
if not conditionals.target then return false end
|
||||
return UnitIsDeadOrGhost(conditionals.target)
|
||||
return CleveRoids.IsUnitDeadOrGhost(conditionals.target)
|
||||
end,
|
||||
|
||||
nodead = function(conditionals)
|
||||
if not conditionals.target then return false end
|
||||
return not UnitIsDeadOrGhost(conditionals.target)
|
||||
return not CleveRoids.IsUnitDeadOrGhost(conditionals.target)
|
||||
end,
|
||||
|
||||
reactive = function(conditionals)
|
||||
@@ -8615,7 +8615,7 @@ CleveRoids.STATIC_CONDITIONALS = {
|
||||
--- @return number|nil Score (lower = better) or nil if invalid candidate
|
||||
function CleveRoids.GetMultiscanScore(unit, priority, currentTargetGuid, specifiedUnitGuid)
|
||||
if not unit or not UnitExists(unit) then return nil end
|
||||
if UnitIsDeadOrGhost(unit) then return nil end
|
||||
if CleveRoids.IsUnitDeadOrGhost(unit) then return nil end
|
||||
|
||||
-- Must be attackable
|
||||
if not UnitCanAttack("player", unit) then return nil end
|
||||
|
||||
+1
-1
@@ -85,7 +85,7 @@ end
|
||||
-- Define original implementations before hooking them.
|
||||
-- This ensures we have a fallback for non-conditional use.
|
||||
local StartAttack = function(msg)
|
||||
if not UnitExists("target") or UnitIsDead("target") then TargetNearestEnemy() end
|
||||
if not UnitExists("target") or CleveRoids.IsUnitDead("target") then TargetNearestEnemy() end
|
||||
-- Check both event-based flag AND action bar state for reliable detection
|
||||
local isAttacking = CleveRoids.CurrentSpell.autoAttack
|
||||
if not isAttacking then
|
||||
|
||||
@@ -2780,7 +2780,7 @@ function CleveRoids.DoTarget(msg)
|
||||
end
|
||||
|
||||
local function IsGuidValid(unitTok, conds)
|
||||
if not unitTok or not UnitExists(unitTok) or UnitIsDeadOrGhost(unitTok) then
|
||||
if not unitTok or not UnitExists(unitTok) or CleveRoids.IsUnitDeadOrGhost(unitTok) then
|
||||
return false
|
||||
end
|
||||
local orig = conds.target
|
||||
@@ -3042,7 +3042,7 @@ end
|
||||
|
||||
-- PERFORMANCE: Module-level action to avoid closure allocation per call
|
||||
local function _startAttackAction()
|
||||
if not UnitExists("target") or UnitIsDead("target") then TargetNearestEnemy() end
|
||||
if not UnitExists("target") or CleveRoids.IsUnitDead("target") then TargetNearestEnemy() end
|
||||
-- Check both event-based flag AND action bar state for reliable detection
|
||||
local isAttacking = CleveRoids.CurrentSpell.autoAttack
|
||||
if not isAttacking then
|
||||
|
||||
@@ -220,6 +220,25 @@ local function GetPlayerOverflowBuffs()
|
||||
local overflowBuffs = CleveRoids.OverflowBuffs
|
||||
if not overflowBuffs then return results end
|
||||
|
||||
-- If the player is no longer buff-capped (< 32 buff slots occupied),
|
||||
-- overflow entries are stale — the server doesn't migrate overflow buffs
|
||||
-- into freed slots, so they've expired. Wipe and return early.
|
||||
if _G.GetPlayerAuraDuration then
|
||||
local buffSlotCount = 0
|
||||
for slot = 0, 31 do
|
||||
local sid = _G.GetPlayerAuraDuration(slot)
|
||||
if sid and sid > 0 then
|
||||
buffSlotCount = buffSlotCount + 1
|
||||
end
|
||||
end
|
||||
if buffSlotCount < 32 then
|
||||
for k in pairs(overflowBuffs) do
|
||||
overflowBuffs[k] = nil
|
||||
end
|
||||
return results
|
||||
end
|
||||
end
|
||||
|
||||
-- Build set of spell IDs in debuff slots (32-47) to filter out debuffs
|
||||
-- that were incorrectly added due to AURA_CAST_ON_SELF race condition
|
||||
-- (debuff slot not yet assigned when the event fired)
|
||||
@@ -242,14 +261,23 @@ local function GetPlayerOverflowBuffs()
|
||||
elseif debuffSpellIds and debuffSpellIds[spellId] then
|
||||
-- Debuff in a visible debuff slot — not an overflow buff, clean up
|
||||
overflowBuffs[spellId] = nil
|
||||
elseif entry.timestamp and entry.durationSec then
|
||||
local remaining = entry.durationSec - (now - entry.timestamp)
|
||||
if remaining > 0 then
|
||||
elseif entry.timestamp then
|
||||
local permanent = not entry.durationSec or entry.durationSec <= 0
|
||||
if permanent then
|
||||
table.insert(results, {
|
||||
spellId = spellId,
|
||||
remaining = remaining,
|
||||
remaining = 0,
|
||||
source = "player",
|
||||
})
|
||||
else
|
||||
local remaining = entry.durationSec - (now - entry.timestamp)
|
||||
if remaining > 0 then
|
||||
table.insert(results, {
|
||||
spellId = spellId,
|
||||
remaining = remaining,
|
||||
source = "player",
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -270,6 +298,28 @@ local function GetTargetOverflowBuffs()
|
||||
local trackingData = CleveRoids.AllCasterAuraTracking
|
||||
if not trackingData or not trackingData[targetGuid] then return results end
|
||||
|
||||
-- Only show overflow for targets that are buff-capped (all 32 buff slots occupied).
|
||||
-- GetUnitField "aura" returns spell IDs for all 48 slots including hidden auras,
|
||||
-- so counting occupied buff slots 1-32 gives the true buff count in real-time.
|
||||
-- Without this gate, AllCasterAuraTracking (which stores ALL aura casts, not
|
||||
-- just overflow) would show false-positive overflow entries on uncapped targets.
|
||||
if not testMode then
|
||||
local buffSlotCount = 0
|
||||
local auras = _G.GetUnitField and _G.GetUnitField("target", "aura")
|
||||
if auras then
|
||||
for slot = 1, 32 do
|
||||
if auras[slot] and auras[slot] > 0 then
|
||||
buffSlotCount = buffSlotCount + 1
|
||||
end
|
||||
end
|
||||
if buffSlotCount < 32 then return results end
|
||||
else
|
||||
-- Fallback: auraCapStatus from last AURA_CAST_ON_OTHER event
|
||||
local capEntry = CleveRoids.AuraCapStatus.targetCapStatus[targetGuid]
|
||||
if not capEntry or not capEntry.buffCapped then return results end
|
||||
end
|
||||
end
|
||||
|
||||
-- Build set of visible aura spell IDs on target (buffs + debuffs)
|
||||
-- Use spellId matching instead of texture matching for accuracy:
|
||||
-- texture matching fails when icon isn't cached (false positive) or when
|
||||
@@ -429,13 +479,19 @@ local function RefreshDurations()
|
||||
local data = playerIcons[i]
|
||||
if data.spellId then
|
||||
local entry = CleveRoids.OverflowBuffs and CleveRoids.OverflowBuffs[data.spellId]
|
||||
if entry and entry.timestamp and entry.durationSec then
|
||||
local remaining = entry.durationSec - (now - entry.timestamp)
|
||||
if remaining > 0 then
|
||||
data.remaining = remaining
|
||||
data.duration:SetText(FormatDuration(remaining))
|
||||
if entry and entry.timestamp then
|
||||
local permanent = not entry.durationSec or entry.durationSec <= 0
|
||||
if permanent then
|
||||
data.remaining = 0
|
||||
data.duration:SetText("")
|
||||
else
|
||||
anyExpired = true
|
||||
local remaining = entry.durationSec - (now - entry.timestamp)
|
||||
if remaining > 0 then
|
||||
data.remaining = remaining
|
||||
data.duration:SetText(FormatDuration(remaining))
|
||||
else
|
||||
anyExpired = true
|
||||
end
|
||||
end
|
||||
else
|
||||
anyExpired = true
|
||||
|
||||
@@ -284,7 +284,8 @@ end
|
||||
-- Immunity data version - increment this when changing immunity data format
|
||||
-- This will cause all immunity data to be reset on addon update
|
||||
-- v3: Fixed Master Strike false physical immunity recording (split CC spell handling)
|
||||
CleveRoids.IMMUNITY_DATA_VERSION = 3
|
||||
-- v4: Fixed false immunity recording when target dies with spells in-flight (dead = IMMUNE)
|
||||
CleveRoids.IMMUNITY_DATA_VERSION = 4
|
||||
|
||||
-- Call on next frame to ensure everything is loaded
|
||||
local initFrame = CreateFrame("Frame")
|
||||
|
||||
+224
-141
@@ -82,6 +82,24 @@ function CleveRoids.GetGUID(unit)
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Check if a unit is truly dead using Nampower health (sees through feign death)
|
||||
-- Returns: true if unit HP is 0 (truly dead), false if alive or feigning
|
||||
function CleveRoids.IsUnitDead(unit)
|
||||
if CleveRoids.NampowerAPI then
|
||||
local hp = CleveRoids.NampowerAPI.GetUnitHealth(unit)
|
||||
if hp ~= nil then
|
||||
return hp == 0
|
||||
end
|
||||
end
|
||||
return UnitIsDead(unit)
|
||||
end
|
||||
|
||||
-- Check if a unit is truly dead or a ghost using Nampower health
|
||||
-- Returns: true if unit HP is 0 or unit is a ghost
|
||||
function CleveRoids.IsUnitDeadOrGhost(unit)
|
||||
return CleveRoids.IsUnitDead(unit) or UnitIsGhost(unit)
|
||||
end
|
||||
|
||||
-- Hidden tooltip for scanning spell info
|
||||
local SpellScanTooltip = nil
|
||||
|
||||
@@ -703,6 +721,8 @@ lib.slotOwnership = lib.slotOwnership or {} -- [targetGUID][auraSlot] = {casterG
|
||||
lib.allAuraCasts = lib.allAuraCasts or {} -- [targetGUID][spellName][casterGuid] = {startTime, duration, rank}
|
||||
lib.pendingCasts = lib.pendingCasts or {} -- [targetGUID][spellName] = {casterGuid, rank, time, comboPoints}
|
||||
lib.recentMisses = lib.recentMisses or {} -- [targetGUID][spellName] = {time, spellId, targetName, reason} for miss/dodge/parry detection
|
||||
lib.recentDeaths = lib.recentDeaths or {} -- [targetGUID] = GetTime() timestamp of UNIT_DIED (prevents false immunity on dead targets)
|
||||
lib.recentCCHits = lib.recentCCHits or {} -- [targetGUID][ccType] = {count, lastHitTime} for DR tracking (prevents DR immunity → permanent)
|
||||
lib.iconCache = lib.iconCache or {} -- [spellId] = texture (shared with pfUI 7.6 or standalone)
|
||||
|
||||
-- Buff tracking tables (parallel to debuff tables, standalone Nampower mode only)
|
||||
@@ -1230,6 +1250,15 @@ function lib:CleanupStaleTrackingData()
|
||||
end
|
||||
end
|
||||
|
||||
-- Clean recentDeaths (dead target immunity guard)
|
||||
if lib.recentDeaths then
|
||||
for guid, deathTime in pairs(lib.recentDeaths) do
|
||||
if (now - deathTime) > staleTime then
|
||||
lib.recentDeaths[guid] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Clean pendingCasts (including combo point data from SPELL_GO)
|
||||
if lib.pendingCasts then
|
||||
for guid, spells in pairs(lib.pendingCasts) do
|
||||
@@ -2985,7 +3014,7 @@ end
|
||||
local _GetTime = GetTime
|
||||
local _UnitExists = UnitExists
|
||||
local _UnitDebuff = UnitDebuff
|
||||
local _UnitIsDead = UnitIsDead
|
||||
local _IsUnitDead = CleveRoids.IsUnitDead
|
||||
local _UnitName = UnitName
|
||||
local _GetSpellRecField = GetSpellRecField
|
||||
local _string_find = string.find
|
||||
@@ -3018,6 +3047,23 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
|
||||
end
|
||||
_lastDelayedTrackingUpdate = currentTime
|
||||
|
||||
-- Process deferred guidToName cleanups
|
||||
if lib._guidNameCleanupQueue then
|
||||
local i = 1
|
||||
while i <= table.getn(lib._guidNameCleanupQueue) do
|
||||
local entry = lib._guidNameCleanupQueue[i]
|
||||
if entry and currentTime >= entry.expiry then
|
||||
-- Only remove if no active tracking data references this GUID
|
||||
if not (lib.ownDebuffs[entry.guid] or lib.pendingCasts[entry.guid]) then
|
||||
lib.guidToName[entry.guid] = nil
|
||||
end
|
||||
table.remove(lib._guidNameCleanupQueue, i)
|
||||
else
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- PERFORMANCE: Early exit if all queues are empty
|
||||
-- NOTE: Use next() instead of [1] to handle array holes from table.remove()
|
||||
-- Array holes can occur when combat log handlers remove entries mid-iteration
|
||||
@@ -3067,14 +3113,18 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
|
||||
|
||||
-- Cache frequently accessed values
|
||||
local hasSuperwow = CleveRoids.hasSuperwow
|
||||
local hasExtendedTokens = CleveRoids.NampowerAPI and CleveRoids.NampowerAPI.features
|
||||
and CleveRoids.NampowerAPI.features.hasExtendedUnitTokens
|
||||
local debug = CleveRoids.debug
|
||||
|
||||
-- Resolve a GUID to a queryable unit token
|
||||
-- SuperWoW: returns GUID directly (SuperWoW extends WoW APIs to accept GUIDs)
|
||||
-- Without SuperWoW: returns "target" if GUID matches current target, nil otherwise
|
||||
-- Nampower v2.41+ extended tokens: GUID works as unit token if unit is in range
|
||||
-- Fallback: returns "target" if GUID matches current target, nil otherwise
|
||||
local function ResolveGUIDUnit(guid)
|
||||
if not guid then return nil end
|
||||
if hasSuperwow then return guid end
|
||||
if hasExtendedTokens and _UnitExists(guid) then return guid end
|
||||
local ctGUID = CleveRoids.GetGUID("target")
|
||||
if ctGUID and ctGUID == guid then
|
||||
return "target"
|
||||
@@ -3196,7 +3246,7 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
|
||||
|
||||
if not isWhitelisted then
|
||||
-- Check if target is dead - requires special handling
|
||||
if _UnitIsDead(verifyUnit) then
|
||||
if _IsUnitDead(verifyUnit) then
|
||||
-- Target died - check if we saw "afflicted by" message before death
|
||||
if pending.verifiedByAffliction then
|
||||
-- We confirmed bleed landed via combat log before target died
|
||||
@@ -3440,7 +3490,7 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
|
||||
and pending.targetGUID and ResolveGUIDUnit(pending.targetGUID) or nil
|
||||
if ccVerifyUnit then
|
||||
-- Skip verification if target is dead (debuffs are removed on death)
|
||||
if _UnitIsDead(ccVerifyUnit) then
|
||||
if _IsUnitDead(ccVerifyUnit) then
|
||||
ccVerified = true -- Assume CC landed, can't verify on dead target
|
||||
if debug then
|
||||
DEFAULT_CHAT_FRAME:AddMessage(
|
||||
@@ -3503,7 +3553,7 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
|
||||
end
|
||||
|
||||
-- If CC didn't land, check if it's immunity or debuff cap
|
||||
if not ccVerified and ccVerifyUnit and not _UnitIsDead(ccVerifyUnit) then
|
||||
if not ccVerified and ccVerifyUnit and not _IsUnitDead(ccVerifyUnit) then
|
||||
-- totalDebuffs already counted above, reuse it
|
||||
if totalDebuffs == 0 then
|
||||
-- Count wasn't done (dead target check skipped counting), do it now
|
||||
@@ -3530,12 +3580,14 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
|
||||
lib.guidToName[pending.targetGUID] = resolvedTargetName
|
||||
end
|
||||
end
|
||||
-- Try to resolve using SuperWoW GUID-based query
|
||||
if not resolvedTargetName and hasSuperwow then
|
||||
local name = _UnitName(pending.targetGUID)
|
||||
if name and name ~= "Unknown" then
|
||||
resolvedTargetName = name
|
||||
lib.guidToName[pending.targetGUID] = resolvedTargetName
|
||||
-- Try to resolve using SuperWoW or Nampower extended tokens GUID-based query
|
||||
if not resolvedTargetName and (hasSuperwow or hasExtendedTokens) then
|
||||
if _UnitExists(pending.targetGUID) then
|
||||
local name = _UnitName(pending.targetGUID)
|
||||
if name and name ~= "Unknown" then
|
||||
resolvedTargetName = name
|
||||
lib.guidToName[pending.targetGUID] = resolvedTargetName
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -3556,8 +3608,22 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
|
||||
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
|
||||
-- Check DR before recording as permanent CC immunity
|
||||
local isDR = false
|
||||
if pending.targetGUID and pending.ccType then
|
||||
local drEntry = lib.recentCCHits[pending.targetGUID] and lib.recentCCHits[pending.targetGUID][pending.ccType]
|
||||
if drEntry and (GetTime() - drEntry.lastHitTime) < 20 and drEntry.count >= 3 then
|
||||
isDR = true
|
||||
end
|
||||
end
|
||||
if isDR then
|
||||
if debug then
|
||||
DEFAULT_CHAT_FRAME:AddMessage(
|
||||
_string_format("|cff00aaff[CC DR Skip]|r %s on %s - likely DR immune (3+ recent %s hits), not recording permanent immunity",
|
||||
pending.spellName or "Unknown", resolvedTargetName or "Unknown", pending.ccType or "Unknown")
|
||||
)
|
||||
end
|
||||
elseif resolvedTargetName and resolvedTargetName ~= "" and pending.ccType then
|
||||
CleveRoids.RecordCCImmunity(resolvedTargetName, pending.ccType, nil, pending.spellName)
|
||||
|
||||
if debug then
|
||||
@@ -3590,6 +3656,15 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
|
||||
if resolvedTargetName and pending.ccType then
|
||||
CleveRoids.RemoveCCImmunity(resolvedTargetName, pending.ccType)
|
||||
end
|
||||
-- Track successful CC hit for DR detection
|
||||
if pending.targetGUID and pending.ccType then
|
||||
lib.recentCCHits[pending.targetGUID] = lib.recentCCHits[pending.targetGUID] or {}
|
||||
local entry = lib.recentCCHits[pending.targetGUID][pending.ccType]
|
||||
lib.recentCCHits[pending.targetGUID][pending.ccType] = {
|
||||
count = (entry and entry.count or 0) + 1,
|
||||
lastHitTime = GetTime(),
|
||||
}
|
||||
end
|
||||
if debug then
|
||||
DEFAULT_CHAT_FRAME:AddMessage(
|
||||
_string_format("|cff00ff00[CC Verified]|r %s landed on %s",
|
||||
@@ -3669,7 +3744,7 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
|
||||
local sharedVerifyUnit = not debuffVerified and not pending.spellGoHit and not pending.spellGoMissed
|
||||
and pending.targetGUID and ResolveGUIDUnit(pending.targetGUID) or nil
|
||||
if sharedVerifyUnit then
|
||||
if _UnitIsDead(sharedVerifyUnit) then
|
||||
if _IsUnitDead(sharedVerifyUnit) then
|
||||
-- Target died - can't verify immunity, assume debuff landed
|
||||
debuffVerified = true
|
||||
if debug then
|
||||
@@ -3864,6 +3939,18 @@ ev:SetScript("OnEvent", function()
|
||||
CleveRoids.knownEnemyGuids[k] = nil
|
||||
end
|
||||
end
|
||||
-- Wipe GUID-to-name cache — GUIDs can be reused across zones
|
||||
if lib.guidToName then
|
||||
for k in pairs(lib.guidToName) do
|
||||
lib.guidToName[k] = nil
|
||||
end
|
||||
end
|
||||
-- Wipe DR tracking — DR state is invalid in a new zone
|
||||
if lib.recentCCHits then
|
||||
for k in pairs(lib.recentCCHits) do
|
||||
lib.recentCCHits[k] = nil
|
||||
end
|
||||
end
|
||||
return
|
||||
|
||||
elseif event == "PLAYER_TARGET_CHANGED" then
|
||||
@@ -5521,6 +5608,10 @@ ev:SetScript("OnEvent", function()
|
||||
-- Get unit name before cleanup (for debug output)
|
||||
local unitName = lib.guidToName[guid]
|
||||
|
||||
-- Track recent death to prevent false immunity recording
|
||||
-- (dead targets report SPELL_MISS IMMUNE for in-flight spells)
|
||||
lib.recentDeaths[guid] = GetTime()
|
||||
|
||||
-- Clean up all tracking data for this GUID immediately
|
||||
if lib.ownDebuffs[guid] then
|
||||
lib.ownDebuffs[guid] = nil
|
||||
@@ -5543,6 +5634,9 @@ ev:SetScript("OnEvent", function()
|
||||
if lib.recentMisses and lib.recentMisses[guid] then
|
||||
lib.recentMisses[guid] = nil
|
||||
end
|
||||
if lib.recentCCHits and lib.recentCCHits[guid] then
|
||||
lib.recentCCHits[guid] = nil
|
||||
end
|
||||
|
||||
-- Clean up buff tracking tables
|
||||
if lib.ownBuffCasts[guid] then
|
||||
@@ -5583,9 +5677,9 @@ ev:SetScript("OnEvent", function()
|
||||
CleveRoids.knownEnemyGuids[guid] = nil
|
||||
end
|
||||
|
||||
-- Clean up GUID to name mapping (after 5 seconds to allow final lookups)
|
||||
-- Actually, keep it for a bit in case we need it for immunity detection
|
||||
-- lib.guidToName[guid] = nil
|
||||
-- Deferred GUID-to-name cleanup (5s delay to allow immunity detection lookups)
|
||||
lib._guidNameCleanupQueue = lib._guidNameCleanupQueue or {}
|
||||
table.insert(lib._guidNameCleanupQueue, { guid = guid, expiry = GetTime() + 5 })
|
||||
|
||||
if CleveRoids.debug and unitName then
|
||||
DEFAULT_CHAT_FRAME:AddMessage(
|
||||
@@ -6007,7 +6101,7 @@ evCleanup:SetScript("OnEvent", function()
|
||||
local isCurrentTarget = (targetGUID == guid)
|
||||
|
||||
-- Check if current target is dead
|
||||
if isCurrentTarget and UnitIsDead("target") then
|
||||
if isCurrentTarget and CleveRoids.IsUnitDead("target") then
|
||||
lib.objects[guid] = nil
|
||||
lib.guidToName[guid] = nil -- MEMORY: Clean up name mapping
|
||||
else
|
||||
@@ -7834,40 +7928,40 @@ local function ParseImmunityCombatLog()
|
||||
end
|
||||
end
|
||||
|
||||
-- Skip if target is dead (dead targets report "immune" for in-flight spells)
|
||||
if targetName and UnitExists("target") and UnitName("target") == targetName and CleveRoids.IsUnitDead("target") then
|
||||
if CleveRoids.debug then
|
||||
CleveRoids.Print("|cff00aaff[CombatLog Dead Skip]|r " .. targetName .. " is dead - ignoring immunity from combat log")
|
||||
end
|
||||
CancelPendingVerification(targetName, spellName)
|
||||
return
|
||||
end
|
||||
|
||||
-- If we have a school but no spell, use the school directly
|
||||
if school and targetName and not spellName then
|
||||
-- Record immunity by school
|
||||
if not CleveRoids_ImmunityData[school] then
|
||||
CleveRoids_ImmunityData[school] = {}
|
||||
end
|
||||
|
||||
-- Check for conditional buff
|
||||
local buffs = nil
|
||||
if UnitExists("target") and UnitName("target") == targetName then
|
||||
buffs = GetUnitBuffs("target")
|
||||
end
|
||||
|
||||
if buffs and next(buffs) then
|
||||
local buffCount = 0
|
||||
local singleBuff = nil
|
||||
for buff, _ in pairs(buffs) do
|
||||
buffCount = buffCount + 1
|
||||
singleBuff = buff
|
||||
if buffCount > 1 then
|
||||
singleBuff = nil
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if singleBuff then
|
||||
CleveRoids_ImmunityData[school][targetName] = { buff = singleBuff }
|
||||
-- Check if target is queryable for buff check
|
||||
local canQuery = UnitExists("target") and UnitName("target") == targetName
|
||||
if canQuery then
|
||||
local immunityBuff = HasImmunityGrantingBuff("target")
|
||||
if immunityBuff then
|
||||
if CleveRoids.debug then
|
||||
CleveRoids.Print("|cffff6600Immunity:|r " .. targetName .. " is immune to " .. school .. " when buffed with: " .. singleBuff)
|
||||
CleveRoids.Print("|cff00aaff[CombatLog Temp Skip]|r " .. targetName .. " has " .. immunityBuff)
|
||||
end
|
||||
-- Cancel pending verification - combat log confirmed immunity
|
||||
CancelPendingVerification(targetName, nil)
|
||||
return
|
||||
end
|
||||
else
|
||||
-- Cannot query target — inconclusive, don't record
|
||||
if CleveRoids.debug then
|
||||
CleveRoids.Print("|cff00aaff[CombatLog Inconclusive]|r Cannot query " .. targetName .. " for buffs - not recording " .. school .. " immunity")
|
||||
end
|
||||
CancelPendingVerification(targetName, nil)
|
||||
return
|
||||
end
|
||||
|
||||
-- Record immunity by school
|
||||
if not CleveRoids_ImmunityData[school] then
|
||||
CleveRoids_ImmunityData[school] = {}
|
||||
end
|
||||
|
||||
-- Permanent immunity
|
||||
@@ -7892,75 +7986,9 @@ local function ParseImmunityCombatLog()
|
||||
)
|
||||
end
|
||||
|
||||
-- Check if target has any buffs (for conditional immunity)
|
||||
local buffs = nil
|
||||
if UnitExists("target") and UnitName("target") == targetName then
|
||||
buffs = GetUnitBuffs("target")
|
||||
end
|
||||
|
||||
-- If target has exactly one buff, assume it's causing the immunity
|
||||
if buffs and next(buffs) then
|
||||
local buffCount = 0
|
||||
local singleBuff = nil
|
||||
for buff, _ in pairs(buffs) do
|
||||
buffCount = buffCount + 1
|
||||
singleBuff = buff
|
||||
if buffCount > 1 then
|
||||
singleBuff = nil
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if singleBuff then
|
||||
-- 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
|
||||
RecordCCImmunity(targetName, ccType, singleBuff, spellName)
|
||||
else
|
||||
RecordImmunity(targetName, spellName, singleBuff, spellID)
|
||||
end
|
||||
-- Cancel pending verification - combat log confirmed immunity
|
||||
CancelPendingVerification(targetName, spellName)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- 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
|
||||
@@ -7970,14 +7998,33 @@ local function ParseImmunityCombatLog()
|
||||
return
|
||||
end
|
||||
|
||||
-- Check if this is a CC spell - if so, record CC immunity instead
|
||||
-- Check if target is queryable for buff check
|
||||
local canQuery = UnitExists("target") and UnitName("target") == targetName
|
||||
if canQuery then
|
||||
local immunityBuff = HasImmunityGrantingBuff("target")
|
||||
if immunityBuff then
|
||||
if CleveRoids.debug then
|
||||
CleveRoids.Print("|cff00aaff[CombatLog Temp Skip]|r " .. targetName .. " has " .. immunityBuff .. " active - skipping permanent immunity recording for " .. spellName)
|
||||
end
|
||||
CancelPendingVerification(targetName, spellName)
|
||||
return
|
||||
end
|
||||
else
|
||||
-- Cannot query target — inconclusive, don't record
|
||||
if CleveRoids.debug then
|
||||
CleveRoids.Print("|cff00aaff[CombatLog Inconclusive]|r Cannot query " .. targetName .. " for buffs - not recording immunity for " .. spellName)
|
||||
end
|
||||
CancelPendingVerification(targetName, spellName)
|
||||
return
|
||||
end
|
||||
|
||||
-- Record as permanent immunity
|
||||
local ccType = spellID and GetSpellCCType(spellID)
|
||||
if ccType then
|
||||
RecordCCImmunity(targetName, ccType, nil, spellName)
|
||||
else
|
||||
RecordImmunity(targetName, spellName, nil, spellID)
|
||||
end
|
||||
-- Cancel pending verification - combat log confirmed immunity
|
||||
CancelPendingVerification(targetName, spellName)
|
||||
end
|
||||
end
|
||||
@@ -8934,7 +8981,9 @@ CleveRoids.usingSpellMissEvents = false
|
||||
local function ProcessSpellMissSelf(spellId, targetGuid, missInfo)
|
||||
if not spellId or not targetGuid or not missInfo then return end
|
||||
|
||||
-- Resolve target name from GUID cache or current target
|
||||
-- Resolve target name from GUID cache, current target, or extended tokens
|
||||
local hasExtTokens = CleveRoids.NampowerAPI and CleveRoids.NampowerAPI.features
|
||||
and CleveRoids.NampowerAPI.features.hasExtendedUnitTokens
|
||||
local targetName = lib.guidToName[targetGuid]
|
||||
if not targetName then
|
||||
local currentTargetGUID = CleveRoids.GetGUID("target")
|
||||
@@ -8945,6 +8994,21 @@ local function ProcessSpellMissSelf(spellId, targetGuid, missInfo)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Nampower extended tokens fallback: resolve name from GUID directly
|
||||
if not targetName and hasExtTokens and UnitExists(targetGuid) then
|
||||
targetName = UnitName(targetGuid)
|
||||
if targetName then
|
||||
lib.guidToName[targetGuid] = targetName
|
||||
end
|
||||
end
|
||||
-- Cross-validate cached name against live data when extended tokens available
|
||||
if targetName and hasExtTokens and UnitExists(targetGuid) then
|
||||
local liveName = UnitName(targetGuid)
|
||||
if liveName and liveName ~= "Unknown" and liveName ~= targetName then
|
||||
lib.guidToName[targetGuid] = liveName
|
||||
targetName = liveName
|
||||
end
|
||||
end
|
||||
|
||||
-- Resolve spell name
|
||||
local spellName = GetSpellRecField and type(spellId) == "number" and spellId > 0 and GetSpellRecField(spellId, "name")
|
||||
@@ -8973,45 +9037,64 @@ local function ProcessSpellMissSelf(spellId, targetGuid, missInfo)
|
||||
return
|
||||
end
|
||||
|
||||
-- Skip if target has temporary immunity buff (Divine Shield, etc.)
|
||||
-- Skip if target recently died (dead targets report IMMUNE for in-flight spells)
|
||||
if lib.recentDeaths[targetGuid] and (GetTime() - lib.recentDeaths[targetGuid]) < 3 then
|
||||
if CleveRoids.debug then
|
||||
CleveRoids.Print("|cff00aaff[SPELL_MISS Dead Skip]|r " .. targetName .. " recently died - ignoring immunity")
|
||||
end
|
||||
return
|
||||
end
|
||||
-- Also check if target is truly dead via Nampower health
|
||||
if UnitExists("target") and UnitName("target") == targetName and CleveRoids.IsUnitDead("target") then
|
||||
if CleveRoids.debug then
|
||||
CleveRoids.Print("|cff00aaff[SPELL_MISS Dead Skip]|r " .. targetName .. " is dead - ignoring immunity")
|
||||
end
|
||||
-- Record death GUID for future spells in same frame
|
||||
lib.recentDeaths[targetGuid] = GetTime()
|
||||
return
|
||||
end
|
||||
|
||||
-- Resolve a queryable unit for buff checks
|
||||
-- Try current target first, then GUID via extended tokens
|
||||
local queryUnit = nil
|
||||
if UnitExists("target") and UnitName("target") == targetName then
|
||||
local immunityBuff = HasImmunityGrantingBuff("target")
|
||||
queryUnit = "target"
|
||||
elseif hasExtTokens and UnitExists(targetGuid) then
|
||||
queryUnit = targetGuid
|
||||
end
|
||||
|
||||
-- Skip if target has temporary immunity buff (Divine Shield, etc.)
|
||||
if queryUnit then
|
||||
local immunityBuff = HasImmunityGrantingBuff(queryUnit)
|
||||
if immunityBuff then
|
||||
if CleveRoids.debug then
|
||||
CleveRoids.Print("|cff00aaff[SPELL_MISS Temp Skip]|r " .. targetName .. " has " .. immunityBuff)
|
||||
end
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- Check for conditional buff-based immunity
|
||||
local buffs = GetUnitBuffs("target")
|
||||
if buffs and next(buffs) then
|
||||
local buffCount = 0
|
||||
local singleBuff = nil
|
||||
for buff, _ in pairs(buffs) do
|
||||
buffCount = buffCount + 1
|
||||
singleBuff = buff
|
||||
if buffCount > 1 then
|
||||
singleBuff = nil
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if singleBuff then
|
||||
-- Check CC immunity first
|
||||
local ccType = GetSpellCCType(spellId)
|
||||
if ccType then
|
||||
RecordCCImmunity(targetName, ccType, singleBuff, spellName)
|
||||
else
|
||||
RecordImmunity(targetName, spellName, singleBuff, spellId)
|
||||
end
|
||||
return
|
||||
-- Check DR before recording as permanent CC immunity
|
||||
local ccType = GetSpellCCType(spellId)
|
||||
if ccType and targetGuid then
|
||||
local drEntry = lib.recentCCHits[targetGuid] and lib.recentCCHits[targetGuid][ccType]
|
||||
if drEntry and (GetTime() - drEntry.lastHitTime) < 20 and drEntry.count >= 3 then
|
||||
if CleveRoids.debug then
|
||||
CleveRoids.Print("|cff00aaff[SPELL_MISS DR Skip]|r " .. targetName .. " - likely DR immune to " .. ccType .. ", not recording")
|
||||
end
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- No buff detected - record as permanent immunity
|
||||
local ccType = GetSpellCCType(spellId)
|
||||
-- Cannot query target (despawned/phased/switched) - inconclusive, don't record
|
||||
if not queryUnit then
|
||||
if CleveRoids.debug then
|
||||
CleveRoids.Print("|cff00aaff[SPELL_MISS Inconclusive]|r Cannot query " .. targetName .. " for buffs - not recording immunity")
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
-- Record as permanent immunity
|
||||
if ccType then
|
||||
RecordCCImmunity(targetName, ccType, nil, spellName)
|
||||
else
|
||||
|
||||
Reference in New Issue
Block a user