no superwow required, update for nampower 3

This commit is contained in:
Jrc13245
2026-03-01 09:33:41 -05:00
parent 58c7ab8457
commit 2f0f000733
7 changed files with 934 additions and 309 deletions
+2 -3
View File
@@ -190,8 +190,7 @@ function Extension.HookPfUILibdebuff()
-- Check if this is the current target
if UnitName("target") == unit then
local _, guid = UnitExists("target")
unitGUID = CleveRoids.NormalizeGUID(guid)
unitGUID = CleveRoids.GetGUID("target")
end
-- If we couldn't match to current target, check guidToName mapping
@@ -360,7 +359,7 @@ function Extension.SyncComboDurationToPfUI(guid, spellID, duration)
local unitLevel = 0
-- Check if this is the current target
local _, targetGUID = UnitExists("target")
local targetGUID = CleveRoids.GetGUID("target")
if targetGUID == guid then
unitName = UnitName("target")
unitLevel = UnitLevel("target") or 0
+169 -121
View File
@@ -48,23 +48,30 @@ function CleveRoids.GetCachedTime()
end
function CleveRoids.GetCachedPlayerHealthPercent()
local max = UnitHealthMax("player")
return max > 0 and (100 * UnitHealth("player") / max) or 0
local API = CleveRoids.NampowerAPI
local hp = API and API.GetUnitHealth and API.GetUnitHealth("player") or UnitHealth("player")
local max = API and API.GetUnitMaxHealth and API.GetUnitMaxHealth("player") or UnitHealthMax("player")
return max > 0 and (100 * hp / max) or 0
end
function CleveRoids.GetCachedPlayerPowerPercent()
local max = UnitManaMax("player")
return max > 0 and (100 * UnitMana("player") / max) or 0
local API = CleveRoids.NampowerAPI
local power = API and API.GetUnitPower and API.GetUnitPower("player") or UnitMana("player")
local max = API and API.GetUnitMaxPower and API.GetUnitMaxPower("player") or UnitManaMax("player")
return max > 0 and (100 * power / max) or 0
end
function CleveRoids.GetCachedPlayerPower()
return UnitMana("player")
local API = CleveRoids.NampowerAPI
return API and API.GetUnitPower and API.GetUnitPower("player") or UnitMana("player")
end
function CleveRoids.GetCachedTargetHealthPercent()
if not UnitExists("target") then return 0 end
local max = UnitHealthMax("target")
return max > 0 and (100 * UnitHealth("target") / max) or 0
local API = CleveRoids.NampowerAPI
local hp = API and API.GetUnitHealth and API.GetUnitHealth("target") or UnitHealth("target")
local max = API and API.GetUnitMaxHealth and API.GetUnitMaxHealth("target") or UnitHealthMax("target")
return max > 0 and (100 * hp / max) or 0
end
-- Cooldown uses original function directly
@@ -994,7 +1001,7 @@ end
local function OnAutoAttackOther(attackerGuid, targetGuid, totalDamage, hitInfo, victimState,
subDamageCount, blockedAmount, totalAbsorb, totalResist)
-- Check if player is the attacker
local _, playerGuid = UnitExists("player")
local playerGuid = CleveRoids.GetGUID("player")
if not playerGuid or attackerGuid ~= playerGuid then
return -- Not player's attack, ignore
end
@@ -1007,6 +1014,44 @@ local function OnAutoAttackOther(attackerGuid, targetGuid, totalDamage, hitInfo,
CleveRoids.LastSwing.absorbAmount = totalAbsorb or 0
CleveRoids.LastSwing.resistAmount = totalResist or 0
CleveRoids.LastSwing.targetGuid = targetGuid
-- Paladin: refresh active Judgements on melee hit (Nampower fallback for UNIT_CASTEVENT)
if CleveRoids.playerClass == "PALADIN" and targetGuid then
local lib = type(CleveRoids.libdebuff) == "table" and CleveRoids.libdebuff or nil
if lib and lib.objects then
local normalizedTarget = CleveRoids.NormalizeGUID(targetGuid)
if normalizedTarget and lib.objects[normalizedTarget] then
for spellID, rec in pairs(lib.objects[normalizedTarget]) do
if lib.judgementSpells and lib.judgementSpells[spellID] and rec.start and rec.duration then
local remaining = rec.duration + rec.start - GetTime()
if remaining > 0 and rec.caster == "player" then
rec.start = GetTime()
if CleveRoids.debug then
local spellName = SpellInfo and SpellInfo(spellID) or "Unknown"
local baseName = spellName and string.gsub(spellName, "%s*%(Rank %d+%)", "") or "Unknown"
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cff00ffaa[Judgement Refresh]|r Refreshed %s (ID:%d) on melee hit - new duration: %ds",
baseName, spellID, rec.duration)
)
end
-- Sync to pfUI if loaded (pre-7.6 only)
if not CleveRoids.hasPfUI76 and pfUI and pfUI.api and pfUI.api.libdebuff then
local spellName = SpellInfo and SpellInfo(spellID) or nil
local baseName = spellName and string.gsub(spellName, "%s*%(Rank %d+%)", "")
local targetName = (lib.guidToName and lib.guidToName[normalizedTarget]) or UnitName("target")
local targetLevel = UnitLevel("target") or 0
if targetName and baseName then
pfUI.api.libdebuff:AddEffect(targetName, targetLevel, baseName, rec.duration, "player")
end
end
end
end
end
end
end
end
end
-- Process AUTO_ATTACK_SELF event (player being attacked)
@@ -1104,8 +1149,7 @@ local function OnAuraCastSelf(spellId, casterGuid, targetGuid, effect, effectAur
if isBuffNotDebuff and spellId and durationMs and durationMs > 0 and lib and not lib.hasPfUIEnhanced then
local spellName = SpellInfo and SpellInfo(spellId)
if spellName then
local _, playerGuidRaw = UnitExists("player")
local playerGuid = playerGuidRaw and CleveRoids.NormalizeGUID(playerGuidRaw)
local playerGuid = CleveRoids.GetGUID("player")
if playerGuid then
lib.ownBuffCasts[playerGuid] = lib.ownBuffCasts[playerGuid] or {}
lib.ownBuffCasts[playerGuid][spellName] = {
@@ -1285,7 +1329,7 @@ autoAttackFrame:SetScript("OnEvent", function()
end
if spellId and spellId > 0 and durationMs and durationMs > 0 then
local _, playerGUID = UnitExists("player")
local playerGUID = CleveRoids.GetGUID("player")
if playerGUID then
if not CleveRoids.AllCasterAuraTracking[playerGUID] then
CleveRoids.AllCasterAuraTracking[playerGUID] = {}
@@ -1328,7 +1372,7 @@ autoAttackFrame:SetScript("OnEvent", function()
elseif event == "BUFF_REMOVED_OTHER" or event == "DEBUFF_REMOVED_OTHER" then
-- Instant cleanup of AllCasterAuraTracking when auras are removed
-- arg1=targetGuid, arg2=spellName, arg3=spellId, arg7=state (v2.32+: 0=added, 1=removed, 2=modified)
-- arg1=targetGuid, arg2=luaSlot, arg3=spellId, arg4=stackCount, arg5=auraLevel, arg6=auraSlot, arg7=state (v2.32+: 0=added, 1=removed, 2=modified)
local guid = arg1
local spellId = arg3
local state = arg7
@@ -1505,7 +1549,7 @@ function CleveRoids.IsTargetDebuffCapped(unit)
if not UnitExists(unit) then return false end
-- Check cached AURA_CAST data by GUID
local _, guid = UnitExists(unit)
local guid = CleveRoids.GetGUID(unit)
if guid then
local capData = CleveRoids.AuraCapStatus.targetCapStatus[guid]
if capData and (GetTime() - capData.timestamp) < 30 then
@@ -1515,20 +1559,6 @@ function CleveRoids.IsTargetDebuffCapped(unit)
-- Fallback: count debuffs on target manually
-- For NPCs: check up to 48 slots (16 debuff + 32 overflow in buff slots)
if not CleveRoids.hasSuperwow then
-- Without SuperWoW, can only see 16 debuff slots
local count = 0
for i = 1, 16 do
if UnitDebuff(unit, i) then
count = count + 1
else
break
end
end
return count >= 16
end
-- With SuperWoW: count all debuffs including overflow
local debuffCount = 0
-- Count regular debuff slots (1-16, dense)
@@ -1553,7 +1583,7 @@ function CleveRoids.IsTargetBuffCapped(unit)
unit = unit or "target"
if not UnitExists(unit) then return false end
local _, guid = UnitExists(unit)
local guid = CleveRoids.GetGUID(unit)
if guid then
local capData = CleveRoids.AuraCapStatus.targetCapStatus[guid]
if capData and (GetTime() - capData.timestamp) < 30 then
@@ -1614,8 +1644,7 @@ local function IsPendingDebuffCast(spellName, targetUnit)
-- Get target GUID for verification (only count as pending if casting AT this target)
local targetGuid = nil
if targetUnit and UnitExists(targetUnit) then
local _, guid = UnitExists(targetUnit)
targetGuid = guid
targetGuid = CleveRoids.GetGUID(targetUnit)
end
-- Check if currently CASTING this spell
@@ -1686,29 +1715,7 @@ local function _get_debuff_timeleft(unitToken, auraName)
auraName = string.gsub(auraName, "_", " ")
end
-- SuperWoW path: GUID-based lookup
-- SuperWoW debuff slots: 1-16 are regular debuffs, 17-48 overflow to buff slots 1-32
if CleveRoids.hasSuperwow and lib then
local _, guid = UnitExists(unitToken)
if guid and lib.objects and lib.objects[guid] then
-- Check 1-48: debuff slots 1-16 + overflow debuffs in buff slots 1-32
-- NOTE: Slots 1-16 are dense (break on nil), slots 17-48 are sparse (continue on nil)
-- Overflow debuffs in buff slots are mixed with regular buffs, so we can't break early
for i = 1, 48 do
local effect, _, _, _, _, duration, timeleft = lib:UnitDebuff(unitToken, i)
-- Only break for slots 1-16 (regular debuffs are dense)
-- For overflow slots 17-48, nil means "regular buff filtered out", not "end of list"
if not effect and i <= 16 then break end
-- Strip rank from effect name for comparison
local effectBase = effect and string.gsub(effect, "%s*%(%s*Rank%s+%d+%s*%)", "")
if effectBase and effectBase == auraName and timeleft and timeleft >= 0 then
return timeleft, duration
end
end
end
end
-- Non-SuperWoW fallback
-- GUID-based debuff lookup via libdebuff (works with SuperWoW or Nampower)
if lib and lib.UnitDebuff then
for idx = 1, 48 do
local effect, _, _, _, _, duration, timeleft = lib:UnitDebuff(unitToken, idx)
@@ -1803,17 +1810,32 @@ function CleveRoids.CancelAura(auraName)
-- v2.34+ path: cancel by spell ID (works for buff-capped overflow auras too)
local API = CleveRoids.NampowerAPI
if API and API.features.hasCancelPlayerAuraSpellId and CleveRoids.hasSuperwow then
-- First scan visible buffs via GetPlayerBuff (fast, covers normal case)
while true do
local aura_ix = GetPlayerBuff(ix, "HELPFUL")
ix = ix + 1
if aura_ix == -1 then break end
local bid = GetPlayerBuffID(aura_ix)
bid = (bid < -1) and (bid + 65536) or bid
if string.lower(SpellInfo(bid)) == auraName then
_G.CancelPlayerAuraSpellId(bid, 1)
return true
if API and API.features.hasCancelPlayerAuraSpellId then
-- First scan visible buffs via GetPlayerBuff
if CleveRoids.hasSuperwow then
-- SuperWoW path: GetPlayerBuffID provides spell ID directly
while true do
local aura_ix = GetPlayerBuff(ix, "HELPFUL")
ix = ix + 1
if aura_ix == -1 then break end
local bid = GetPlayerBuffID(aura_ix)
bid = (bid < -1) and (bid + 65536) or bid
if string.lower(SpellInfo(bid)) == auraName then
_G.CancelPlayerAuraSpellId(bid, 1)
return true
end
end
elseif API.features.hasGetPlayerAuraDuration and _G.GetPlayerAuraDuration then
-- Nampower path: scan buff aura slots 0-31 for spell IDs
for slot = 0, 31 do
local spellId = _G.GetPlayerAuraDuration(slot)
if spellId and spellId > 0 then
local name = SpellInfo(spellId)
if name and string.lower(name) == auraName then
_G.CancelPlayerAuraSpellId(spellId, 1)
return true
end
end
end
end
@@ -2751,8 +2773,7 @@ function CleveRoids.CountEnemiesMatching(checkFunc)
-- Save current target for restoration
local currentTargetGuid = nil
if UnitExists("target") then
local _, guid = UnitExists("target")
currentTargetGuid = guid
currentTargetGuid = CleveRoids.GetGUID("target")
end
local count = 0
@@ -2774,7 +2795,7 @@ function CleveRoids.CountEnemiesMatching(checkFunc)
if not found then break end
if not UnitExists("target") then break end
local _, currentGuid = UnitExists("target")
local currentGuid = CleveRoids.GetGUID("target")
if not currentGuid then break end
-- Cycle detection: stop when we see the first target again
@@ -2956,7 +2977,7 @@ function CleveRoids.GetCursiveTimeRemaining(unit, spellName)
if not CleveRoids.HasCursive() then return nil end
if not unit or not UnitExists(unit) then return nil end
local _, guid = UnitExists(unit)
local guid = CleveRoids.GetGUID(unit)
if not guid then return nil end
-- Normalize spell name (lowercase, no rank) to match Cursive's format
@@ -2978,7 +2999,7 @@ function CleveRoids.ValidateCursiveDebuff(unit, spellName, operator, amount)
if not CleveRoids.HasCursive() then return false end
if not unit or not UnitExists(unit) then return false end
local _, guid = UnitExists(unit)
local guid = CleveRoids.GetGUID(unit)
if not guid then return false end
-- Normalize spell name for Cursive lookup
@@ -3009,7 +3030,7 @@ function CleveRoids.HasAnyCursiveDebuff(unit)
if not CleveRoids.HasCursive() then return false end
if not unit or not UnitExists(unit) then return false end
local _, guid = UnitExists(unit)
local guid = CleveRoids.GetGUID(unit)
if not guid then return false end
return Cursive.curses:HasAnyCurse(guid) == true
@@ -3095,9 +3116,12 @@ end
-- returns: True or false
function CleveRoids.ValidatePower(unit, operator, amount)
if not unit or not operator or not amount then return false end
local powerPercent = 100 / UnitManaMax(unit) * UnitMana(unit)
local API = CleveRoids.NampowerAPI
local power = API and API.GetUnitPower and API.GetUnitPower(unit) or UnitMana(unit)
local maxPower = API and API.GetUnitMaxPower and API.GetUnitMaxPower(unit) or UnitManaMax(unit)
local powerPercent = maxPower > 0 and (100 * power / maxPower) or 0
if powerPercent and CleveRoids.operators[operator] then
if CleveRoids.operators[operator] then
return CleveRoids.comparators[operator](powerPercent, amount)
end
@@ -3111,7 +3135,8 @@ end
-- returns: True or false
function CleveRoids.ValidateRawPower(unit, operator, amount)
if not unit or not operator or not amount then return false end
local power = UnitMana(unit)
local API = CleveRoids.NampowerAPI
local power = API and API.GetUnitPower and API.GetUnitPower(unit) or UnitMana(unit)
if power and CleveRoids.operators[operator] then
return CleveRoids.comparators[operator](power, amount)
@@ -3149,7 +3174,10 @@ end
-- returns: True or false
function CleveRoids.ValidatePowerLost(unit, operator, amount)
if not unit or not operator or not amount then return false end
local powerLost = UnitManaMax(unit) - UnitMana(unit)
local API = CleveRoids.NampowerAPI
local maxPower = API and API.GetUnitMaxPower and API.GetUnitMaxPower(unit) or UnitManaMax(unit)
local power = API and API.GetUnitPower and API.GetUnitPower(unit) or UnitMana(unit)
local powerLost = maxPower - power
if CleveRoids.operators[operator] then
return CleveRoids.comparators[operator](powerLost, amount)
@@ -3165,7 +3193,10 @@ end
-- returns: True or false
function CleveRoids.ValidateHp(unit, operator, amount)
if not unit or not operator or not amount then return false end
local hpPercent = 100 / UnitHealthMax(unit) * UnitHealth(unit)
local API = CleveRoids.NampowerAPI
local hp = API and API.GetUnitHealth and API.GetUnitHealth(unit) or UnitHealth(unit)
local maxHp = API and API.GetUnitMaxHealth and API.GetUnitMaxHealth(unit) or UnitHealthMax(unit)
local hpPercent = maxHp > 0 and (100 * hp / maxHp) or 0
if CleveRoids.operators[operator] then
return CleveRoids.comparators[operator](hpPercent, amount)
@@ -3181,7 +3212,8 @@ end
-- returns: True or false
function CleveRoids.ValidateRawHp(unit, operator, amount)
if not unit or not operator or not amount then return false end
local rawhp = UnitHealth(unit)
local API = CleveRoids.NampowerAPI
local rawhp = API and API.GetUnitHealth and API.GetUnitHealth(unit) or UnitHealth(unit)
if CleveRoids.operators[operator] then
return CleveRoids.comparators[operator](rawhp, amount)
@@ -3197,7 +3229,10 @@ end
-- returns: True or false
function CleveRoids.ValidateHpLost(unit, operator, amount)
if not unit or not operator or not amount then return false end
local hpLost = UnitHealthMax(unit) - UnitHealth(unit)
local API = CleveRoids.NampowerAPI
local maxHp = API and API.GetUnitMaxHealth and API.GetUnitMaxHealth(unit) or UnitHealthMax(unit)
local hp = API and API.GetUnitHealth and API.GetUnitHealth(unit) or UnitHealth(unit)
local hpLost = maxHp - hp
if CleveRoids.operators[operator] then
return CleveRoids.comparators[operator](hpLost, amount)
@@ -3287,7 +3322,17 @@ function CleveRoids.GetPlayerAura(index, isbuff)
local bid = GetPlayerBuff(index, buffType)
if bid < 0 then return end
local spellID = CleveRoids.hasSuperwow and GetPlayerBuffID(bid)
local spellID
if CleveRoids.hasSuperwow then
spellID = GetPlayerBuffID(bid)
elseif _G.GetPlayerAuraDuration then
-- Nampower v2.30+: GetPlayerAuraDuration uses same raw aura slot numbering as GetPlayerBuff
-- (0-31 for buffs, 32-47 for debuffs)
local sid = _G.GetPlayerAuraDuration(bid)
if sid and sid > 0 then
spellID = sid
end
end
return GetPlayerBuffTexture(bid), GetPlayerBuffApplications(bid), spellID, GetPlayerBuffTimeLeft(bid)
end
@@ -3363,10 +3408,6 @@ end
function CleveRoids.ValidateAura(unit, args, isbuff)
if not args or not UnitExists(unit) then return false end
if not CleveRoids.hasSuperwow then
return false
end
if type(args) ~= "table" then
args = {name = args}
end
@@ -3527,8 +3568,7 @@ function CleveRoids.ValidateAura(unit, args, isbuff)
if found and remaining == nil and isPlayer and isbuff and searchName then
local lib = type(CleveRoids.libdebuff) == "table" and CleveRoids.libdebuff or nil
if lib and lib.allBuffAuras then
local _, playerGuidRaw = UnitExists("player")
local playerGuid = CleveRoids.NormalizeGUID(playerGuidRaw)
local playerGuid = CleveRoids.GetGUID("player")
if playerGuid and lib.allBuffAuras[playerGuid] then
-- Try exact name match first
local casters = lib.allBuffAuras[playerGuid][args.name]
@@ -3558,7 +3598,7 @@ function CleveRoids.ValidateAura(unit, args, isbuff)
-- Guard: verify the spell isn't a visible debuff on the target (AllCasterAuraTracking
-- stores both buffs and debuffs, so without this check [buff:DebuffName] could false-positive).
if not found and not isPlayer and isbuff and (searchID or searchName) then
local _, targetGuid = UnitExists(unit)
local targetGuid = CleveRoids.GetGUID(unit)
if targetGuid then
-- Check if the spell is in a visible debuff slot — if so, it's a debuff, not a buff
local isDebuff = false
@@ -3610,8 +3650,7 @@ function CleveRoids.ValidateAura(unit, args, isbuff)
if nonPlayerAuraTimeRemaining == nil and isbuff then
local lib = type(CleveRoids.libdebuff) == "table" and CleveRoids.libdebuff or nil
if lib and lib.allBuffAuras then
local _, targetGuid = UnitExists(unit)
targetGuid = targetGuid and CleveRoids.NormalizeGUID(targetGuid)
local targetGuid = CleveRoids.GetGUID(unit)
if targetGuid then
local buffEntries = lib.allBuffAuras[targetGuid]
if buffEntries then
@@ -3648,7 +3687,7 @@ function CleveRoids.ValidateAura(unit, args, isbuff)
-- Second try: All-caster tracking from AURA_CAST events (works for any caster)
-- Only use if libdebuff didn't find it (libdebuff has more accurate timing for player casts)
if nonPlayerAuraTimeRemaining == nil then
local _, targetGuid = UnitExists(unit)
local targetGuid = CleveRoids.GetGUID(unit)
if targetGuid then
local remaining, casterGuid = CleveRoids.FindAllCasterAuraByName(targetGuid, args.name)
@@ -3740,21 +3779,12 @@ function CleveRoids.ValidateUnitDebuff(unit, args)
local texture, stacks, spellID, remaining
local i
-- PERFORMANCE: For non-SuperWoW, early return if no texture registered
if not searchID and not CleveRoids.hasSuperwow and not CleveRoids.auraTextures[args.name] then
return false
end
-- For non-player units, check tracking table directly
-- SIMPLE: Did the player cast this spell? Is the timer still valid?
-- Defensive: verify libdebuff is a table before accessing properties
local lib = type(CleveRoids.libdebuff) == "table" and CleveRoids.libdebuff or nil
if unit ~= "player" and lib and lib.objects then
local _, guid = UnitExists(unit)
if not guid then return false end
-- Normalize GUID to string for consistent table key lookups
guid = CleveRoids.NormalizeGUID(guid)
local guid = CleveRoids.GetGUID(unit)
if not guid then return false end
-- PERFORMANCE: Use cached spell name -> ID mapping instead of iterating every call
@@ -3846,7 +3876,7 @@ function CleveRoids.ValidateUnitDebuff(unit, args)
-- Includes stack/time checks (e.g., [debuff:Sunder_Armor>#3]) not just existence.
-- The isShared gate below ensures personal debuffs still require tracking table data.
if not found and CleveRoids.hasSuperwow then
if not found then
-- FALLBACK: Only scan for SHARED debuffs (Sunder, Faerie Fire, etc.)
-- Personal debuffs (Rip, Rake, Rupture, etc.) MUST be in tracking table
-- to be considered "found" - this ensures [nodebuff] only finds YOUR debuffs
@@ -3954,7 +3984,7 @@ function CleveRoids.ValidateUnitDebuff(unit, args)
found = true
break
end
elseif CleveRoids.hasSuperwow then
elseif spellID then
local baseName, fullName = GetSpellNames(spellID)
if baseName and (baseName == args.name or fullName == args.name) then
found = true
@@ -3979,7 +4009,7 @@ function CleveRoids.ValidateUnitDebuff(unit, args)
found = true
break
end
elseif CleveRoids.hasSuperwow then
elseif spellID then
local baseName, fullName = GetSpellNames(spellID)
if baseName and (baseName == args.name or fullName == args.name) then
found = true
@@ -4646,9 +4676,7 @@ function CleveRoids.CheckSpellCast(unit, spell)
end
end
-- Legacy fallback: UNIT_CASTEVENT-based spell_tracking (requires SuperWoW)
if not CleveRoids.hasSuperwow then return false end
-- Legacy fallback: UNIT_CASTEVENT-based spell_tracking (SuperWoW or Nampower SPELL_START_SELF)
if not CleveRoids.spell_tracking[guid] then
return false
else
@@ -4934,7 +4962,7 @@ end
function CleveRoids.ValidateUnitCCSingleMechanic(unit, mechanic)
-- Use BuffLib if available (most accurate - tracks overflow debuffs and hidden auras)
if CleveRoids.HasBuffLib() then
local _, guid = UnitExists(unit)
local guid = CleveRoids.GetGUID(unit)
if not guid then return false end
if unit == "player" then
@@ -4955,7 +4983,7 @@ function CleveRoids.ValidateUnitAnyCrowdControl(unit)
-- Use BuffLib if available
if CleveRoids.HasBuffLib() then
local _, guid = UnitExists(unit)
local guid = CleveRoids.GetGUID(unit)
if not guid then return false end
if unit == "player" then
@@ -4984,10 +5012,9 @@ function CleveRoids.ValidateUnitAnyCrowdControl(unit)
return false
end
-- Direct CC check - scans unit debuffs using SuperWoW API
-- Direct CC check - scans unit debuffs for spell IDs to determine CC mechanics
-- Works without BuffLib by using built-in spell mechanic table
function CleveRoids.ValidateUnitCCDirect(unit, mechanic)
if not CleveRoids.hasSuperwow then return false end
-- Players only have 16 debuff slots, no overflow
-- Non-player units can have overflow debuffs in buff slots (17-48)
@@ -6646,6 +6673,30 @@ CleveRoids.Keywords = {
return PlayerIsSwimming() ~= 1
end,
-- [rooted] - Player is currently rooted (Nampower v2.36+)
rooted = function(conditionals)
if not CleveRoids.NampowerAPI.features.hasPlayerIsRooted then
if not CleveRoids._rootedErrorShown then
DEFAULT_CHAT_FRAME:AddMessage("|cffff0000[SuperCleveRoidMacros]|r The [rooted] conditional requires Nampower v2.36.0 or newer.", 1, 0.5, 0.5)
CleveRoids._rootedErrorShown = true
end
return false
end
return PlayerIsRooted() == 1
end,
-- [norooted] - Player is NOT rooted (Nampower v2.36+)
norooted = function(conditionals)
if not CleveRoids.NampowerAPI.features.hasPlayerIsRooted then
if not CleveRoids._rootedErrorShown then
DEFAULT_CHAT_FRAME:AddMessage("|cffff0000[SuperCleveRoidMacros]|r The [rooted] conditional requires Nampower v2.36.0 or newer.", 1, 0.5, 0.5)
CleveRoids._rootedErrorShown = true
end
return false
end
return PlayerIsRooted() ~= 1
end,
distance = function(conditionals)
if not CleveRoids.hasUnitXP then return false end
@@ -7923,6 +7974,7 @@ CleveRoids.STATIC_CONDITIONALS = {
mod = true, nomod = true,
keydown = true, nokeydown = true,
swimming = true, noswimming = true,
rooted = true, norooted = true,
resting = true, noresting = true,
}
@@ -7940,7 +7992,7 @@ function CleveRoids.GetMultiscanScore(unit, priority, currentTargetGuid, specifi
if not UnitCanAttack("player", unit) then return nil end
-- Combat check: must be in combat with player, UNLESS it's current target OR specified @unit
local _, unitGuid = UnitExists(unit)
local unitGuid = CleveRoids.GetGUID(unit)
local isCurrentTarget = currentTargetGuid and unitGuid == currentTargetGuid
local isSpecifiedUnit = specifiedUnitGuid and unitGuid == specifiedUnitGuid
if not isCurrentTarget and not isSpecifiedUnit and not UnitAffectingCombat(unit) then
@@ -8062,15 +8114,13 @@ function CleveRoids.ResolveMultiscanTarget(conditionals, specifiedUnit)
-- Save current target for restoration and combat-check exemption
local currentTargetGuid = nil
if UnitExists("target") then
local _, guid = UnitExists("target")
currentTargetGuid = guid
currentTargetGuid = CleveRoids.GetGUID("target")
end
-- Resolve specified @unit GUID (also exempt from combat check)
local specifiedUnitGuid = nil
if specifiedUnit and UnitExists(specifiedUnit) then
local _, guid = UnitExists(specifiedUnit)
specifiedUnitGuid = guid
specifiedUnitGuid = CleveRoids.GetGUID(specifiedUnit)
end
-- Handle raid mark priorities (direct unit reference)
@@ -8079,8 +8129,7 @@ function CleveRoids.ResolveMultiscanTarget(conditionals, specifiedUnit)
if UnitExists(markUnit) and UnitCanAttack("player", markUnit) then
-- Validate against target-dependent conditionals
if CleveRoids.ValidateMultiscanCandidate(conditionals, markUnit) then
local _, guid = UnitExists(markUnit)
return guid
return CleveRoids.GetGUID(markUnit)
end
end
return nil -- Raid mark not found or doesn't pass conditionals
@@ -8094,8 +8143,7 @@ function CleveRoids.ResolveMultiscanTarget(conditionals, specifiedUnit)
if UnitExists(markUnit) and UnitCanAttack("player", markUnit) then
-- Validate against target-dependent conditionals
if CleveRoids.ValidateMultiscanCandidate(conditionals, markUnit) then
local _, guid = UnitExists(markUnit)
return guid
return CleveRoids.GetGUID(markUnit)
end
end
end
@@ -8108,7 +8156,7 @@ function CleveRoids.ResolveMultiscanTarget(conditionals, specifiedUnit)
if not specifiedUnitGuid then
local found = UnitXP("target", "nearestEnemy")
if found and UnitExists("target") then
local _, foundGuid = UnitExists("target")
local foundGuid = CleveRoids.GetGUID("target")
-- Restore original target
if currentTargetGuid then
TargetUnit(currentTargetGuid)
@@ -8136,7 +8184,7 @@ function CleveRoids.ResolveMultiscanTarget(conditionals, specifiedUnit)
if not specifiedUnitGuid then
local found = UnitXP("target", "mostHP")
if found and UnitExists("target") then
local _, foundGuid = UnitExists("target")
local foundGuid = CleveRoids.GetGUID("target")
-- Restore original target
if currentTargetGuid then
TargetUnit(currentTargetGuid)
@@ -8167,7 +8215,7 @@ function CleveRoids.ResolveMultiscanTarget(conditionals, specifiedUnit)
local function evaluateCandidate(unit)
if not UnitExists(unit) then return end
local _, guid = UnitExists(unit)
local guid = CleveRoids.GetGUID(unit)
if not guid or seenGuids[guid] then return end
seenGuids[guid] = true
@@ -8213,7 +8261,7 @@ function CleveRoids.ResolveMultiscanTarget(conditionals, specifiedUnit)
if not found then break end
if not UnitExists("target") then break end
local _, currentGuid = UnitExists("target")
local currentGuid = CleveRoids.GetGUID("target")
if not currentGuid then break end
if firstGuid == nil then
+208 -42
View File
@@ -106,16 +106,11 @@ requirementCheckFrame:SetScript("OnEvent", function()
if arg1 ~= "SuperCleveRoidMacros" then return end
-- Check requirements immediately when our addon loads
local hasSuperwow = CleveRoids.hasSuperwow
local hasNampower = (IsSpellInRange ~= nil)
local hasUnitXP = pcall(UnitXP, "nop", "nop")
if not hasSuperwow or not hasNampower or not hasUnitXP then
if not hasNampower or not hasUnitXP then
-- Show errors
if not hasSuperwow then
CleveRoids.Print("|cFFFF0000SuperCleveRoidMacros|r requires |cFF00FFFFbalakethelock's SuperWoW|r:")
CleveRoids.Print("https://github.com/balakethelock/SuperWoW")
end
if not hasNampower then
CleveRoids.Print("|cFFFF0000SuperCleveRoidMacros|r requires |cFF00FFFFAvitasia's Nampower|r:")
CleveRoids.Print("https://gitea.com/avitasia/nampower")
@@ -2670,7 +2665,7 @@ function CleveRoids.DoTarget(msg)
end
-- Save original target GUID for potential restoration (SuperWoW returns GUID as 2nd value)
local _, originalTargetGuid = UnitExists("target")
local originalTargetGuid = CleveRoids.GetGUID("target")
-- Handle [multiscan:priority] - use ResolveMultiscanTarget for enemy scanning
-- ResolveMultiscanTarget handles its own target save/restore internally
@@ -2829,7 +2824,7 @@ function CleveRoids.DoTarget(msg)
found = UnitXP("target", scanMode)
if not found then break end
local _, currentGuid = UnitExists("target")
local currentGuid = CleveRoids.GetGUID("target")
if not currentGuid then break end
-- Check if we've cycled back to start
@@ -3728,10 +3723,6 @@ function CleveRoids.DoNoFirstAction(msg)
end
function CleveRoids.DoCastSequence(sequence)
if not CleveRoids.hasSuperwow then
CleveRoids.Print("|cFFFF0000/castsequence|r requires |cFF00FFFFSuperWoW|r.")
return
end
if type(sequence) == "string" then
sequence = CleveRoids.GetSequence(sequence)
if not sequence then return end
@@ -3788,7 +3779,7 @@ function CleveRoids.DoCastSequence(sequence)
-- Capture target GUID for reset=target (only resets on NEW target, not same target)
if sequence.reset and sequence.reset.target and UnitExists("target") then
local _, targetGuid = UnitExists("target")
local targetGuid = CleveRoids.GetGUID("target")
sequence.lastTargetGuid = targetGuid
end
@@ -4927,9 +4918,18 @@ CleveRoids.Frame:RegisterEvent("PLAYER_REGEN_DISABLED") -- Entered actual combat
CleveRoids.Frame:RegisterEvent("PLAYER_REGEN_ENABLED") -- Left actual combat (no threat)
CleveRoids.Frame:RegisterEvent("UPDATE_SHAPESHIFT_FORM")
CleveRoids.Frame:RegisterEvent("SPELL_UPDATE_COOLDOWN")
CleveRoids.Frame:RegisterEvent("UNIT_AURA")
CleveRoids.Frame:RegisterEvent("UNIT_HEALTH")
CleveRoids.Frame:RegisterEvent("UNIT_POWER")
-- Use GUID events when available (v2.39+), fall back to standard per-token events
if CleveRoids.NampowerAPI.features.hasUnitGuidEvents then
CleveRoids.Frame:RegisterEvent("UNIT_AURA_GUID")
CleveRoids.Frame:RegisterEvent("UNIT_HEALTH_GUID")
CleveRoids.Frame:RegisterEvent("UNIT_MANA_GUID")
CleveRoids.Frame:RegisterEvent("UNIT_RAGE_GUID")
CleveRoids.Frame:RegisterEvent("UNIT_ENERGY_GUID")
else
CleveRoids.Frame:RegisterEvent("UNIT_AURA")
CleveRoids.Frame:RegisterEvent("UNIT_HEALTH")
CleveRoids.Frame:RegisterEvent("UNIT_POWER")
end
if CleveRoids.hasSuperwow then
CleveRoids.Frame:RegisterEvent("UNIT_CASTEVENT")
end
@@ -4946,11 +4946,17 @@ CleveRoids.Frame:RegisterEvent("SPELLCAST_STOP")
CleveRoids.Frame:RegisterEvent("SPELLCAST_FAILED")
CleveRoids.Frame:RegisterEvent("SPELLCAST_INTERRUPTED")
-- Nampower SPELL_CAST_EVENT for reliable channel tracking
-- Nampower SPELL_CAST_EVENT for reliable channel tracking + cast sequence + spell_tracking
if GetCurrentCastingInfo then
CleveRoids.Frame:RegisterEvent("SPELL_CAST_EVENT")
end
-- Nampower v2.25+: SPELL_START_SELF for spell_tracking and cast sequence (Nampower fallback for UNIT_CASTEVENT)
if CleveRoids.NampowerAPI and CleveRoids.NampowerAPI.features and CleveRoids.NampowerAPI.features.hasSpellStartEvents then
CleveRoids.Frame:RegisterEvent("SPELL_START_SELF")
CleveRoids.Frame:RegisterEvent("SPELL_FAILED_SELF")
end
-- Nampower v2.41+: keyboard input events for [keydown:X] conditional
if CleveRoids.NampowerAPI.features.hasKeyEvents then
CleveRoids.Frame:RegisterEvent("KEY_DOWN")
@@ -5250,7 +5256,7 @@ function CleveRoids.Frame:UNIT_CASTEVENT(caster,target,action,spell_id,cast_time
end
-- Nampower SPELL_CAST_EVENT handler for reliable channel tracking
-- This is the PRIMARY source of truth for channel state (not GetCurrentCastingInfo polling)
-- Also handles spell_tracking clearing and cast sequence advancement (Nampower fallback for UNIT_CASTEVENT)
function CleveRoids.Frame:SPELL_CAST_EVENT(success, spellId, castType, targetGuid, itemId)
local CHANNEL = 4
@@ -5267,6 +5273,147 @@ function CleveRoids.Frame:SPELL_CAST_EVENT(success, spellId, castType, targetGui
-- Force immediate action update
CleveRoids.TestForAllActiveActions()
end
-- Nampower fallback: spell_tracking and cast sequence (when SuperWoW not available)
if not CleveRoids.hasSuperwow and spellId then
local playerGuid = CleveRoids.playerGuid or CleveRoids.GetGUID("player")
-- Clear spell_tracking on success or failure
if success == 1 or success == 0 then
local cast = CleveRoids.spell_tracking[playerGuid]
if cast and cast.spell_id == spellId then
CleveRoids.spell_tracking[playerGuid] = nil
CleveRoids.spell_tracking["player"] = nil
end
end
-- Cast sequence advancement
if CleveRoids.currentSequence and success == 1 then
local active = CleveRoids.GetCurrentSequenceAction(CleveRoids.currentSequence)
if active and active.action then
local name, rank = SpellInfo(spellId)
local nameRank = (rank and rank ~= "") and (name .. "(" .. rank .. ")") or nil
local isSeqSpell = (active.action == name or (nameRank and active.action == nameRank))
if isSeqSpell then
CleveRoids.currentSequence.status = 2
CleveRoids.currentSequence.lastUpdate = GetTime()
CleveRoids.AdvanceSequence(CleveRoids.currentSequence)
CleveRoids.currentSequence = nil
end
end
end
-- Cast sequence failure handling
if CleveRoids.currentSequence and success == 0 then
local active = CleveRoids.GetCurrentSequenceAction(CleveRoids.currentSequence)
if active and active.action then
local name = SpellInfo(spellId)
local isSeqSpell = (active.action == name)
if isSeqSpell then
CleveRoids.currentSequence.status = 1 -- Reset to retry
end
end
end
if CleveRoidMacros.realtime == 0 then
CleveRoids.QueueActionUpdate()
end
end
end
-- Nampower SPELL_START_SELF handler (v2.25+)
-- Handles spell_tracking population and cast sequence START detection (Nampower fallback for UNIT_CASTEVENT)
function CleveRoids.Frame:SPELL_START_SELF(casterGuid, targetGuid, spellId, castTimeMs, durationMs, spellType, ...)
-- Skip if SuperWoW is handling this via UNIT_CASTEVENT
if CleveRoids.hasSuperwow then return end
if not spellId then return end
local playerGuid = CleveRoids.playerGuid or CleveRoids.GetGUID("player")
if casterGuid ~= playerGuid then return end
-- Populate spell_tracking (equivalent to UNIT_CASTEVENT START/CHANNEL)
local isChannel = (spellType == 1)
local action = isChannel and "CHANNEL" or "START"
if castTimeMs and castTimeMs > 0 then
CleveRoids.spell_tracking[casterGuid] = {
spell_id = spellId,
expires = GetTime() + castTimeMs / 1000,
type = action
}
CleveRoids.spell_tracking["player"] = CleveRoids.spell_tracking[casterGuid]
-- Channel duration capture (equivalent to UNIT_CASTEVENT CHANNEL)
if isChannel then
CleveRoids.channelStartTime = GetTime()
local tooltipDuration = CleveRoids.GetChannelDurationFromTooltipByID(spellId)
if tooltipDuration then
CleveRoids.channelDuration = tooltipDuration
else
CleveRoids.channelDuration = castTimeMs / 1000
end
end
-- Cast duration capture (equivalent to UNIT_CASTEVENT START)
if not isChannel then
CleveRoids.castStartTime = GetTime()
CleveRoids.castDuration = castTimeMs / 1000
end
end
-- Cast sequence: set status=1 (casting) for cast-time spells
if CleveRoids.currentSequence and castTimeMs and castTimeMs > 0 then
local active = CleveRoids.GetCurrentSequenceAction(CleveRoids.currentSequence)
if active and active.action then
local name, rank = SpellInfo(spellId)
local nameRank = (rank and rank ~= "") and (name .. "(" .. rank .. ")") or nil
local isSeqSpell = (active.action == name or (nameRank and active.action == nameRank))
if isSeqSpell and CleveRoids.currentSequence.status == 0 then
CleveRoids.currentSequence.status = 1
CleveRoids.currentSequence.expires = GetTime() + (castTimeMs / 1000) - 2
end
end
end
if CleveRoidMacros.realtime == 0 then
CleveRoids.QueueActionUpdate()
end
end
-- Nampower SPELL_FAILED_SELF handler (v2.25+)
-- Clears spell_tracking on failure (Nampower fallback for UNIT_CASTEVENT FAIL)
function CleveRoids.Frame:SPELL_FAILED_SELF(casterGuid, targetGuid, spellId, ...)
-- Skip if SuperWoW is handling this via UNIT_CASTEVENT
if CleveRoids.hasSuperwow then return end
if not spellId then return end
local playerGuid = CleveRoids.playerGuid or CleveRoids.GetGUID("player")
if casterGuid ~= playerGuid then return end
-- Clear spell_tracking
local cast = CleveRoids.spell_tracking[casterGuid]
if cast and cast.spell_id == spellId then
CleveRoids.spell_tracking[casterGuid] = nil
CleveRoids.spell_tracking["player"] = nil
end
-- Cast sequence failure handling
if CleveRoids.currentSequence then
local active = CleveRoids.GetCurrentSequenceAction(CleveRoids.currentSequence)
if active and active.action then
local name = SpellInfo(spellId)
local isSeqSpell = name and (active.action == name)
if isSeqSpell then
CleveRoids.currentSequence.status = 1 -- Reset to retry
end
end
end
if CleveRoidMacros.realtime == 0 then
CleveRoids.QueueActionUpdate()
end
end
function CleveRoids.Frame:SPELLCAST_CHANNEL_START()
@@ -5488,8 +5635,7 @@ function CleveRoids.Frame:PLAYER_TARGET_CHANGED()
-- Instead of resetting, we remember each target's progress in the sequence
local currentGuid = nil
if UnitExists("target") then
local _, guid = UnitExists("target")
currentGuid = guid
currentGuid = CleveRoids.GetGUID("target")
end
for _, sequence in pairs(CleveRoids.Sequences) do
@@ -5733,6 +5879,39 @@ function CleveRoids.Frame:UNIT_POWER()
end
end
-- GUID event handlers (v2.39+): fire once per unit state change instead of per-token
function CleveRoids.Frame:UNIT_AURA_GUID()
if CleveRoidMacros.realtime == 0 then
local now = GetTime()
if (now - CleveRoids.lastUnitAuraUpdate) >= CleveRoids.EVENT_THROTTLE then
CleveRoids.lastUnitAuraUpdate = now
CleveRoids.QueueActionUpdate()
end
end
end
function CleveRoids.Frame:UNIT_HEALTH_GUID()
if CleveRoidMacros.realtime == 0 then
local now = GetTime()
if (now - CleveRoids.lastUnitHealthUpdate) >= CleveRoids.EVENT_THROTTLE then
CleveRoids.lastUnitHealthUpdate = now
CleveRoids.QueueActionUpdate()
end
end
end
-- All power GUID events share the same handler
local function OnPowerGuidEvent()
if CleveRoidMacros.realtime == 0 then
local now = GetTime()
if (now - CleveRoids.lastUnitPowerUpdate) >= CleveRoids.EVENT_THROTTLE then
CleveRoids.lastUnitPowerUpdate = now
CleveRoids.QueueActionUpdate()
end
end
end
CleveRoids.Frame.UNIT_MANA_GUID = OnPowerGuidEvent
CleveRoids.Frame.UNIT_RAGE_GUID = OnPowerGuidEvent
CleveRoids.Frame.UNIT_ENERGY_GUID = OnPowerGuidEvent
function CleveRoids.Frame:SPELL_QUEUE_EVENT()
if event == "SPELL_QUEUE_EVENT" then
local eventCode = arg1
@@ -5980,11 +6159,9 @@ SlashCmdList["CLEVEROID"] = function(msg)
DEFAULT_CHAT_FRAME:AddMessage("/cleveroid realtime 0 or 1 - Force realtime updates")
DEFAULT_CHAT_FRAME:AddMessage("/cleveroid refresh X - Set refresh rate (1-10 updates/sec)")
DEFAULT_CHAT_FRAME:AddMessage("/cleveroid macrocheck 0 or 1 - Enable/disable macro syntax checker")
if CleveRoids.hasSuperwow then
DEFAULT_CHAT_FRAME:AddMessage("/cleveroid learn <spellID> <duration> - Manually set spell duration")
DEFAULT_CHAT_FRAME:AddMessage("/cleveroid forget <spellID|all> - Forget learned duration(s)")
DEFAULT_CHAT_FRAME:AddMessage("/cleveroid debug [0|1] - Toggle learning debug messages")
end
DEFAULT_CHAT_FRAME:AddMessage("/cleveroid learn <spellID> <duration> - Manually set spell duration")
DEFAULT_CHAT_FRAME:AddMessage("/cleveroid forget <spellID|all> - Forget learned duration(s)")
DEFAULT_CHAT_FRAME:AddMessage("/cleveroid debug [0|1] - Toggle learning debug messages")
DEFAULT_CHAT_FRAME:AddMessage("|cffffaa00Spell Schools:|r")
DEFAULT_CHAT_FRAME:AddMessage('/cleveroid listschools - List all learned spell schools')
DEFAULT_CHAT_FRAME:AddMessage('/cleveroid clearschools - Clear learned spell school data')
@@ -6067,14 +6244,10 @@ SlashCmdList["CLEVEROID"] = function(msg)
-- learn (manual set duration)
if cmd == "learn" then
if not CleveRoids.hasSuperwow then
CleveRoids.Print("Learning system requires SuperWoW client!")
return
end
local spellID = tonumber(val)
local duration = tonumber(val2)
if spellID and duration then
local _, playerGUID = UnitExists("player")
local playerGUID = CleveRoids.GetGUID("player")
CleveRoids_LearnedDurations = CleveRoids_LearnedDurations or {}
CleveRoids_LearnedDurations[spellID] = CleveRoids_LearnedDurations[spellID] or {}
CleveRoids_LearnedDurations[spellID][playerGUID] = duration
@@ -6089,10 +6262,6 @@ SlashCmdList["CLEVEROID"] = function(msg)
-- forget (delete learned duration)
if cmd == "forget" or cmd == "unlearn" then
if not CleveRoids.hasSuperwow then
CleveRoids.Print("Learning system requires SuperWoW client!")
return
end
if val == "all" then
CleveRoids_LearnedDurations = {}
CleveRoids.Print("Forgot all learned spell durations")
@@ -6466,14 +6635,13 @@ SlashCmdList["CLEVEROID"] = function(msg)
CleveRoids.Print("|cff88ff88=== Debuff Tracking Debug ===|r")
local _, guid = UnitExists("target")
local guid = CleveRoids.GetGUID("target")
if not guid then
CleveRoids.Print("|cffff0000No target selected!|r")
return
end
local targetName = UnitName("target") or "Unknown"
guid = CleveRoids.NormalizeGUID(guid)
CleveRoids.Print("Target: " .. targetName .. " (GUID: " .. tostring(guid) .. ")")
-- Show tracking table for this target
@@ -6755,7 +6923,7 @@ SlashCmdList["CLEVEROID"] = function(msg)
if UnitExists("target") then
CleveRoids.Print(" ")
CleveRoids.Print("|cffffaa00Target Buff Check:|r")
local _, targetGuid = UnitExists("target")
local targetGuid = CleveRoids.GetGUID("target")
CleveRoids.Print(" Target GUID: " .. tostring(targetGuid))
local targetData = CleveRoids.AllCasterAuraTracking[targetGuid]
if targetData then
@@ -6824,11 +6992,9 @@ SlashCmdList["CLEVEROID"] = function(msg)
DEFAULT_CHAT_FRAME:AddMessage("/cleveroid - Show current settings")
DEFAULT_CHAT_FRAME:AddMessage("/cleveroid realtime 0 or 1 - Force realtime updates (Default: 0. 1 = on, increases CPU load)")
DEFAULT_CHAT_FRAME:AddMessage("/cleveroid refresh X - Set refresh rate (1 to 10 updates per second. Default: 5)")
if CleveRoids.hasSuperwow then
DEFAULT_CHAT_FRAME:AddMessage("/cleveroid learn <spellID> <duration> - Manually set spell duration")
DEFAULT_CHAT_FRAME:AddMessage("/cleveroid forget <spellID|all> - Forget learned duration(s)")
DEFAULT_CHAT_FRAME:AddMessage("/cleveroid debug [0|1] - Toggle learning debug messages")
end
DEFAULT_CHAT_FRAME:AddMessage("/cleveroid learn <spellID> <duration> - Manually set spell duration")
DEFAULT_CHAT_FRAME:AddMessage("/cleveroid forget <spellID|all> - Forget learned duration(s)")
DEFAULT_CHAT_FRAME:AddMessage("/cleveroid debug [0|1] - Toggle learning debug messages")
DEFAULT_CHAT_FRAME:AddMessage("|cffffaa00Immunity Tracking:|r")
DEFAULT_CHAT_FRAME:AddMessage('/cleveroid listimmune [school] - List immunity data')
DEFAULT_CHAT_FRAME:AddMessage('/cleveroid addimmune "<NPC>" <school> [buff] - Add immunity')
+3 -3
View File
@@ -340,7 +340,7 @@ local function HookJudgementDetection()
if event ~= "CAST" or spellID ~= JUDGEMENT_SPELL_ID then return end
-- Check if it's the player casting
local _, playerGuid = UnitExists("player")
local playerGuid = CleveRoids.GetGUID("player")
if casterGuid ~= playerGuid then return end
-- Detect which seal is active and get full judgement info
@@ -448,7 +448,7 @@ meleeRefreshFrame:SetScript("OnEvent", function()
-- Check if current target matches the hit target
local currentTargetName = UnitName("target")
if currentTargetName and currentTargetName == targetName then
local _, targetGuid = UnitExists("target")
local targetGuid = CleveRoids.GetGUID("target")
if targetGuid then
RefreshJudgementsOnTarget(targetGuid)
end
@@ -465,7 +465,7 @@ meleeAbilityFrame:SetScript("OnEvent", function()
if event ~= "CAST" then return end
-- Check if it's the player casting
local _, playerGuid = UnitExists("player")
local playerGuid = CleveRoids.GetGUID("player")
if casterGuid ~= playerGuid then return end
-- Skip Judgement itself (20271) - it applies, doesn't refresh
+11 -9
View File
@@ -222,7 +222,10 @@ local function GetPlayerOverflowBuffs()
local now = GetTime()
for spellId, entry in pairs(overflowBuffs) do
if entry.timestamp and entry.durationSec then
-- v3.0+: Skip hidden auras (not real overflow)
if _G.IsAuraHidden and _G.IsAuraHidden(spellId) == 1 then
-- Hidden aura, don't show in overflow UI
elseif entry.timestamp and entry.durationSec then
local remaining = entry.durationSec - (now - entry.timestamp)
if remaining > 0 then
table.insert(results, {
@@ -244,7 +247,7 @@ local function GetTargetOverflowBuffs()
if not UnitExists("target") then return results end
if not testMode and not (UnitInParty("target") or UnitInRaid("target")) then return results end
local _, targetGuid = UnitExists("target")
local targetGuid = CleveRoids.GetGUID("target")
if not targetGuid then return results end
local trackingData = CleveRoids.AllCasterAuraTracking
@@ -267,7 +270,10 @@ local function GetTargetOverflowBuffs()
local lib = CleveRoids.libdebuff
for spellId, auraData in pairs(trackingData[targetGuid]) do
if auraData.start and auraData.duration then
-- v3.0+: Skip hidden auras (not real overflow)
if _G.IsAuraHidden and _G.IsAuraHidden(spellId) == 1 then
-- Hidden aura, skip
elseif auraData.start and auraData.duration then
local remaining = auraData.duration + auraData.start - now
if remaining > 0 then
local isVisible = false
@@ -410,11 +416,7 @@ local function RefreshDurations()
-- Target durations
if targetFrame and targetFrame:IsVisible() then
local targetGuid = nil
if UnitExists("target") then
local _, guid = UnitExists("target")
targetGuid = guid
end
local targetGuid = CleveRoids.GetGUID("target")
for i = 1, lastTargetCount do
local data = targetIcons[i]
if data.spellId and targetGuid then
@@ -499,7 +501,7 @@ end
-- Inject test entries for the current target
local function InjectTestTargetData()
if not UnitExists("target") then return end
local _, targetGuid = UnitExists("target")
local targetGuid = CleveRoids.GetGUID("target")
if not targetGuid then return end
-- Clean up old test target if it changed
+139 -1
View File
@@ -157,7 +157,15 @@
Suffix matching is case-insensitive.
Note: GetGUIDFromName is an internal C++ helper, NOT a new Lua global.
Current version: v2.41.0
GetUnitGUID Rename, IsAuraHidden, Hidden Aura luaSlot Fix (v3.0+):
- UnitGUID renamed to GetUnitGUID (global function). The addon uses SuperWoW's
UnitExists for GUIDs so this rename is not breaking, but GetUnitGUID supports
extended tokens (mark1-mark8, <base>owner/target/pet suffixes).
- IsAuraHidden(spellId): returns 1 if the spell's aura is hidden from Lua aura
APIs (UnitBuff/UnitDebuff won't show it). Useful for detecting hidden CC spells.
- luaSlot in BUFF/DEBUFF events now returns 0 for hidden auras (was incorrect before).
Current version: v3.0.0
]]
local _G = _G or getfenv(0)
@@ -170,6 +178,12 @@ if type(CleveRoids.NampowerAPI) ~= "table" then
end
local API = CleveRoids.NampowerAPI
-- Provide SpellInfo globally when SuperWoW is not available
-- GetSpellNameAndRankForId (Nampower v2.12+) is functionally identical
if not _G.SpellInfo and _G.GetSpellNameAndRankForId then
_G.SpellInfo = _G.GetSpellNameAndRankForId
end
--------------------------------------------------------------------------------
-- VERSION DETECTION AND FEATURE FLAGS
--------------------------------------------------------------------------------
@@ -352,6 +366,11 @@ API.VERSION_REQUIREMENTS = {
-- v2.41+ - Keyboard events and extended unit token support
["KeyEvents"] = { 2, 41, 0 }, -- KEY_DOWN / KEY_UP events (keyboard input events)
["ExtendedUnitTokens"] = { 2, 41, 0 }, -- Internal unit token resolution now supports "owner"/"target"/"pet"/"mark" in all Nampower functions (SetMouseoverUnit, UseItemIdOrName, etc.)
-- v3.0+ - GetUnitGUID rename, IsAuraHidden, hidden aura luaSlot fix
["GetUnitGUID"] = { 3, 0, 0, "GetUnitGUID" },
["IsAuraHidden"] = { 3, 0, 0, "IsAuraHidden" },
["HiddenAuraLuaSlotFix"] = { 3, 0, 0 },
}
-- Check if a specific feature is available
@@ -530,6 +549,11 @@ local function InitializeFeatures()
f.hasKeyEvents = API.HasFeature("KeyEvents")
f.hasExtendedUnitTokens = API.HasFeature("ExtendedUnitTokens")
-- v3.0+ GetUnitGUID, IsAuraHidden, hidden aura luaSlot fix
f.hasGetUnitGUID = API.HasFeature("GetUnitGUID")
f.hasIsAuraHidden = API.HasFeature("IsAuraHidden")
f.hasHiddenAuraLuaSlotFix = API.HasFeature("HiddenAuraLuaSlotFix")
-- Runtime detection for enhanced spell functions (verify by testing)
if f.hasEnhancedSpellFunctions and GetSpellTexture then
local success, result = pcall(function()
@@ -2691,11 +2715,60 @@ end
-- INITIALIZATION
--------------------------------------------------------------------------------
-- Hook UnitBuff/UnitDebuff to append spell IDs when SuperWoW is not available
-- SuperWoW extends these to return spell IDs as extra return values.
-- Without SuperWoW, we use GetUnitField(unit, "aura") to look up spell IDs.
-- GetUnitField "aura" returns [1-32]=buff spellIDs, [33-48]=debuff spellIDs
local function InstallAuraSpellIdHooks()
if CleveRoids.hasSuperwow then return end -- SuperWoW already provides spell IDs
if not _G.GetUnitField then return end -- Need GetUnitField for aura data
local _origUnitBuff = _G.UnitBuff
local _origUnitDebuff = _G.UnitDebuff
-- UnitBuff(unit, index) => texture, stacks, spellID
-- SuperWoW returns: texture, stacks, spellID
_G.UnitBuff = function(unit, index)
local texture, stacks = _origUnitBuff(unit, index)
if not texture then return nil end
local spellID = nil
local auras = _G.GetUnitField(unit, "aura")
if auras then
-- Buff index i maps to aura slot i (1-based)
spellID = auras[index]
if spellID and spellID <= 0 then spellID = nil end
end
return texture, stacks, spellID
end
-- UnitDebuff(unit, index) => texture, stacks, debuffType, spellID
-- SuperWoW returns: texture, stacks, debuffType, spellID
_G.UnitDebuff = function(unit, index)
local texture, stacks, debuffType = _origUnitDebuff(unit, index)
if not texture then return nil end
local spellID = nil
local auras = _G.GetUnitField(unit, "aura")
if auras then
-- Debuff index i maps to aura slot 32 + i (1-based)
spellID = auras[32 + index]
if spellID and spellID <= 0 then spellID = nil end
end
return texture, stacks, debuffType, spellID
end
end
-- Initialize the API module
function API.Initialize()
-- Detect enhanced spell functions
DetectEnhancedSpellFunctions()
-- Install UnitBuff/UnitDebuff spell ID hooks (when SuperWoW not available)
InstallAuraSpellIdHooks()
-- Auto-enable required Nampower event CVars.
-- Nampower reads CVars at DLL load time, so changes take effect on next reload.
-- We set them now so every subsequent session works automatically.
@@ -3247,5 +3320,70 @@ function API.LearnTalentRank(talentPage, talentIndex, rank)
return true
end
-- Get unit GUID by token (v3.0+)
-- Supports extended tokens: mark1-mark8, <base>owner/target/pet suffixes
function API.GetUnitGUID(unitToken)
if not API.features.hasGetUnitGUID or not _G.GetUnitGUID then
return nil
end
return _G.GetUnitGUID(unitToken)
end
-- Check if a spell's aura would be hidden from Lua aura APIs (v3.0+)
function API.IsAuraHidden(spellId)
if not API.features.hasIsAuraHidden or not _G.IsAuraHidden then
return nil
end
return _G.IsAuraHidden(spellId)
end
--------------------------------------------------------------------------------
-- HEALTH/POWER WRAPPERS (GetUnitField extended token support with fallback)
--------------------------------------------------------------------------------
-- Get unit health via GetUnitField (extended token support) with standard API fallback
function API.GetUnitHealth(unitToken)
if API.features.hasGetUnitField and GetUnitField then
local val = GetUnitField(unitToken, "health")
if val then return val end
end
return UnitHealth(unitToken)
end
function API.GetUnitMaxHealth(unitToken)
if API.features.hasGetUnitField and GetUnitField then
local val = GetUnitField(unitToken, "maxHealth")
if val then return val end
end
return UnitHealthMax(unitToken)
end
-- powerType: nil=current, 0=mana, 1=rage, 2=focus, 3=energy
-- GetUnitField uses power1-power4 fields
local POWER_FIELDS = { [0] = "power1", [1] = "power2", [2] = "power3", [3] = "power4" }
local MAX_POWER_FIELDS = { [0] = "maxPower1", [1] = "maxPower2", [2] = "maxPower3", [3] = "maxPower4" }
function API.GetUnitPower(unitToken, powerType)
if API.features.hasGetUnitField and GetUnitField and powerType then
local field = POWER_FIELDS[powerType]
if field then
local val = GetUnitField(unitToken, field)
if val then return val end
end
end
return UnitMana(unitToken)
end
function API.GetUnitMaxPower(unitToken, powerType)
if API.features.hasGetUnitField and GetUnitField and powerType then
local field = MAX_POWER_FIELDS[powerType]
if field then
local val = GetUnitField(unitToken, field)
if val then return val end
end
end
return UnitManaMax(unitToken)
end
-- Expose API globally for other addons
_G.CleveRoidsNampowerAPI = API
+402 -130
View File
@@ -51,6 +51,18 @@ function CleveRoids.NormalizeGUID(guid)
return tostring(guid)
end
-- Get GUID for a unit token (prefers Nampower GetUnitGUID for extended token support)
-- Returns: normalized GUID string, or nil
function CleveRoids.GetGUID(unit)
if _G.GetUnitGUID then
local guid = _G.GetUnitGUID(unit)
if guid then return tostring(guid) end
end
local _, guid = UnitExists(unit)
if guid then return tostring(guid) end
return nil
end
-- Hidden tooltip for scanning spell info
local SpellScanTooltip = nil
@@ -838,6 +850,42 @@ function lib:InitPfUIIntegration()
CleveRoidsLibDebuffLearnFrame:UnregisterEvent("CHAT_MSG_SPELL_SELF_DAMAGE")
end
-- Unregister Nampower events from the libdebuff frame that pfUI handles.
-- All these handlers early-return when hasPfUIEnhanced, so unregistering
-- avoids wasted event dispatch overhead.
-- Uses pfUI's external hook system for any supplementary processing instead.
local ev = CleveRoidsLibDebuffFrame
if ev then
-- Events whose handlers early-return when hasPfUIEnhanced
ev:UnregisterEvent("SPELL_GO_SELF")
ev:UnregisterEvent("SPELL_GO_OTHER")
ev:UnregisterEvent("AURA_CAST_ON_SELF")
ev:UnregisterEvent("AURA_CAST_ON_OTHER")
ev:UnregisterEvent("DEBUFF_ADDED_OTHER")
ev:UnregisterEvent("DEBUFF_REMOVED_OTHER")
ev:UnregisterEvent("BUFF_ADDED_OTHER")
ev:UnregisterEvent("BUFF_REMOVED_SELF")
ev:UnregisterEvent("BUFF_REMOVED_OTHER")
-- pfUI 7.6+ also handles cast tracking internally
if lib.hasPfUI76 then
ev:UnregisterEvent("SPELL_START_OTHER")
ev:UnregisterEvent("SPELL_FAILED_OTHER")
end
-- Keep registered: SPELL_START_SELF (channel duration capture before early return),
-- UNIT_DIED (AllCasterAuraTracking + OverflowBuff cleanup), UNIT_CASTEVENT (SuperWoW),
-- PLAYER_TARGET_CHANGED, UNIT_AURA (SeedUnit)
if CleveRoids.debug then
DEFAULT_CHAT_FRAME:AddMessage("|cff33ff99[libdebuff]|r Unregistered redundant events (pfUI handles via hooks)")
end
end
-- Register pfUI libdebuff hooks for supplementary processing.
-- These fire after pfUI processes each event, avoiding duplicate event listeners.
lib:RegisterPfUIHooks()
if CleveRoids.debug then
local v = pfUI.version
local tierMsg = lib.hasPfUI76 and " (7.6+ cast tracking)" or ""
@@ -879,6 +927,74 @@ function lib:InitPfUIIntegration()
return false
end
-- Register pfUI libdebuff external hooks for supplementary processing.
-- Hooks fire after pfUI processes each event, letting us react without
-- registering duplicate event listeners.
--
-- Available hooks (registered on pfUI global tables):
-- pfUI.libdebuff_spell_go_hooks["key"] = fn(spellId, arg1..arg7)
-- pfUI.libdebuff_spell_go_other_hooks["key"] = fn(spellId, casterGuid, targetGuid)
-- pfUI.libdebuff_spell_start_self_hooks["key"] = fn(spellId, casterGuid, targetGuid, castTime)
-- pfUI.libdebuff_spell_start_other_hooks["key"] = fn(spellId, casterGuid, targetGuid, castTime)
-- pfUI.libdebuff_spell_failed_other_hooks["key"] = fn(casterGuid, spellId)
-- pfUI.libdebuff_spell_cast_hooks["key"] = fn(success, spellId, castType, targetGuid)
-- pfUI.libdebuff_aura_cast_on_self_hooks["key"] = fn(spellId, casterGuid, targetGuid)
-- pfUI.libdebuff_aura_cast_on_other_hooks["key"] = fn(spellId, casterGuid, targetGuid)
-- pfUI.libdebuff_debuff_added_other_hooks["key"] = fn(guid, luaSlot, spellId, stackCount)
-- pfUI.libdebuff_debuff_removed_other_hooks["key"] = fn(guid, luaSlot, spellId, stackCount)
-- pfUI.libdebuff_unit_health_hooks["key"] = fn(unitToken)
-- pfUI.libdebuff_unit_died_hooks["key"] = fn(guid)
-- pfUI.libdebuff_player_target_changed_hooks["key"] = fn()
--
-- Note: AURA_CAST hooks don't provide durationMs/auraCapStatus, so
-- AllCasterAuraTracking and overflow buff tracking remain on their own
-- event frame (CleveRoidsAutoAttackFrame in Conditionals.lua).
function lib:RegisterPfUIHooks()
if not pfUI then return end
local HOOK_KEY = "SuperCleveRoidMacros"
local registered = 0
-- UNIT_DIED hook: supplementary cleanup for our private tables
-- (AllCasterAuraTracking, OverflowBuffs) that pfUI doesn't manage.
-- Our UNIT_DIED event handler on the libdebuff frame also does this,
-- but the hook provides a second path in case event ordering shifts.
if type(pfUI.libdebuff_unit_died_hooks) == "table" then
pfUI.libdebuff_unit_died_hooks[HOOK_KEY] = function(guid)
if not guid then return end
guid = CleveRoids.NormalizeGUID(guid)
-- Clean up AllCasterAuraTracking (our own table, not shared with pfUI)
if CleveRoids.AllCasterAuraTracking and CleveRoids.AllCasterAuraTracking[guid] then
CleveRoids.AllCasterAuraTracking[guid] = nil
end
-- Clean up OverflowBuffs on player death
local playerGUID = CleveRoids.GetGUID("player")
if playerGUID and guid == playerGUID then
if CleveRoids.OverflowBuffs then
for k in pairs(CleveRoids.OverflowBuffs) do
CleveRoids.OverflowBuffs[k] = nil
end
end
if CleveRoids.AuraCapStatus then
CleveRoids.AuraCapStatus.playerBuffCapped = false
CleveRoids.AuraCapStatus.playerDebuffCapped = false
end
end
end
registered = registered + 1
end
lib.pfUIHooksRegistered = registered > 0
if CleveRoids.debug and registered > 0 then
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cff33ff99[libdebuff]|r Registered %d pfUI libdebuff hook(s)", registered)
)
end
end
-- Unique debuffs: Same spell overwrites itself when cast by different player
-- Only one instance can exist on a target (regardless of caster)
lib.uniqueDebuffs = lib.uniqueDebuffs or {
@@ -1253,17 +1369,12 @@ end
function lib:GetDebuffCaster(unit, spellName)
if not spellName then return nil end
local _, guid = UnitExists(unit)
local guid = CleveRoids.GetGUID(unit)
if not guid then return nil end
guid = CleveRoids.NormalizeGUID(guid)
-- Check own debuffs first
if lib.ownDebuffs[guid] and lib.ownDebuffs[guid][spellName] then
local playerGuid = nil
if UnitExists then
local _, pg = UnitExists("player")
playerGuid = pg
end
local playerGuid = CleveRoids.GetGUID("player")
return playerGuid
end
@@ -1301,9 +1412,8 @@ end
function lib:IsOurDebuff(unit, spellName)
if not spellName then return false end
local _, guid = UnitExists(unit)
local guid = CleveRoids.GetGUID(unit)
if not guid then return false end
guid = CleveRoids.NormalizeGUID(guid)
-- Check own debuffs
if lib.ownDebuffs[guid] and lib.ownDebuffs[guid][spellName] then
@@ -2190,12 +2300,9 @@ function lib:AddEffect(guid, unitName, spellID, duration, stacks, caster)
end
function lib:UnitDebuff(unit, id, filterCaster)
local _, guid = UnitExists(unit)
local guid = CleveRoids.GetGUID(unit)
if not guid then return nil end
-- Normalize GUID to string for consistent table key lookups
guid = CleveRoids.NormalizeGUID(guid)
local texture, stacks, dtype, spellID = nil, nil, nil, nil
-- SuperWoW debuff slots: 1-16 are regular debuffs, 17-48 overflow to buff slots 1-32
@@ -2252,7 +2359,7 @@ end
-- Query buff data with duration and caster tracking (buff slots only)
function lib:UnitBuff(unit, id, filterCaster)
local _, guid = UnitExists(unit)
local guid = CleveRoids.GetGUID(unit)
if not guid then return nil end
-- Only check buff slots
@@ -2290,7 +2397,7 @@ end
-- Find a player-cast debuff by spell ID (searches all slots including buff slots)
function lib:FindPlayerDebuff(unit, spellID)
local _, guid = UnitExists(unit)
local guid = CleveRoids.GetGUID(unit)
if not guid then return nil end
-- Check if we're tracking this spell for this unit
@@ -2342,7 +2449,7 @@ end
-- Find a player-cast buff by spell ID (searches buff slots only)
function lib:FindPlayerBuff(unit, spellID)
local _, guid = UnitExists(unit)
local guid = CleveRoids.GetGUID(unit)
if not guid then return nil end
-- Check if we're tracking this spell for this unit
@@ -2379,7 +2486,7 @@ function lib:FindPlayerBuff(unit, spellID)
end
local function SeedUnit(unit)
local _, guid = UnitExists(unit)
local guid = CleveRoids.GetGUID(unit)
if not guid then return end
local unitName = UnitName(unit)
@@ -2684,8 +2791,7 @@ lib.trackedAfflictions = {
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 currentTargetGUID = CleveRoids.GetGUID("target")
local guidMatch = (targetGUID == currentTargetGUID) and "MATCH" or "MISMATCH"
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cffff00ff[Carnage]|r ApplyCarnageRefresh called for %s (GUID:%s, current:%s, %s)",
@@ -3028,6 +3134,19 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
local hasSuperwow = CleveRoids.hasSuperwow
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
local function ResolveGUIDUnit(guid)
if not guid then return nil end
if hasSuperwow then return guid end
local _, ctGUID = _UnitExists("target")
if ctGUID and CleveRoids.NormalizeGUID(ctGUID) == guid then
return "target"
end
return nil
end
-- Process pending judgement scans to detect actual debuff IDs
if hasJudgements then
local writeIdx = 0
@@ -3045,7 +3164,7 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
local _, currentTargetGUID = _UnitExists("target")
currentTargetGUID = CleveRoids.NormalizeGUID(currentTargetGUID)
if currentTargetGUID == pending.targetGUID and hasSuperwow then
if currentTargetGUID == pending.targetGUID then
-- Scan all debuffs on target to find judgement-type debuffs
for slot = 1, 16 do
local _, _, _, debuffSpellID = _UnitDebuff("target", slot)
@@ -3136,14 +3255,14 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
)
end
if isBleedSpell and hasSuperwow and pending.targetGUID then
local verifyUnit = isBleedSpell and pending.targetGUID and ResolveGUIDUnit(pending.targetGUID) or nil
if isBleedSpell and verifyUnit then
-- Check if mob is in bleed whitelist (skip verification for known bleeders)
local isWhitelisted = CleveRoids.MobsThatBleed and CleveRoids.MobsThatBleed[pending.targetGUID]
if not isWhitelisted then
-- Check if target is dead - requires special handling
-- Note: SuperWoW allows GUID-based queries for all unit functions
if _UnitIsDead(pending.targetGUID) then
if _UnitIsDead(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
@@ -3195,7 +3314,7 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
end
end
end
elseif not UnitExists(pending.targetGUID) then
elseif not UnitExists(verifyUnit) then
-- Target despawned or GUID is invalid - can't verify, skip without recording immunity
-- This is similar to one-shot kills: inconclusive result, don't record immunity
bleedVerified = false
@@ -3209,12 +3328,12 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
-- Note: We set bleedVerified = false but DON'T record immunity
-- This is intentional - despawned targets are inconclusive
else
-- Target is alive - check debuffs by GUID (SuperWoW supports GUID-based queries)
-- Target is alive - check debuffs for bleed spell
bleedVerified = false
local totalDebuffs = 0
for slot = 1, 48 do
local _, _, _, debuffSpellID = _UnitDebuff(pending.targetGUID, slot)
local _, _, _, debuffSpellID = _UnitDebuff(verifyUnit, slot)
if not debuffSpellID then
if slot <= 16 then break end -- Regular debuffs are dense, overflow continues on nil
else
@@ -3383,10 +3502,11 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
-- 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)
-- Guard: skip debuff scanning if SPELL_GO already determined outcome
if not ccVerified and not pending.spellGoHit and not pending.spellGoMissed
and hasSuperwow and pending.targetGUID then
local ccVerifyUnit = not ccVerified and not pending.spellGoHit and not pending.spellGoMissed
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(pending.targetGUID) then
if _UnitIsDead(ccVerifyUnit) then
ccVerified = true -- Assume CC landed, can't verify on dead target
if debug then
DEFAULT_CHAT_FRAME:AddMessage(
@@ -3398,7 +3518,7 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
-- Method 1: Direct spell ID matching (scan debuffs for exact spell ID)
if pending.spellID then
for slot = 1, 48 do
local texture, _, _, debuffSpellID = _UnitDebuff(pending.targetGUID, slot)
local texture, _, _, debuffSpellID = _UnitDebuff(ccVerifyUnit, slot)
if not texture then
if slot <= 16 then break end -- Regular debuffs are dense, overflow continues on nil
else
@@ -3430,7 +3550,7 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
pending.ccType)
)
end
ccVerified = CleveRoids.ValidateUnitCC(pending.targetGUID, pending.ccType)
ccVerified = CleveRoids.ValidateUnitCC(ccVerifyUnit, pending.ccType)
if debug then
DEFAULT_CHAT_FRAME:AddMessage(
_string_format("|cff00aaff[CC Verify]|r Mechanic check result: %s",
@@ -3449,12 +3569,12 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
end
-- If CC didn't land, check if it's immunity or debuff cap
if not ccVerified and not _UnitIsDead(pending.targetGUID) then
if not ccVerified and ccVerifyUnit and not _UnitIsDead(ccVerifyUnit) then
-- totalDebuffs already counted above, reuse it
if totalDebuffs == 0 and hasSuperwow and pending.targetGUID then
if totalDebuffs == 0 then
-- Count wasn't done (dead target check skipped counting), do it now
for slot = 1, 48 do
local texture, _, _, debuffSpellID = _UnitDebuff(pending.targetGUID, slot)
local texture, _, _, debuffSpellID = _UnitDebuff(ccVerifyUnit, slot)
if not texture then
if slot <= 16 then break end
else
@@ -3612,9 +3732,10 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
-- Skip verification if target is dead (debuffs are removed on death)
-- Guard: skip debuff scanning if SPELL_GO already determined outcome
if not debuffVerified and not pending.spellGoHit and not pending.spellGoMissed
and hasSuperwow and pending.targetGUID then
if _UnitIsDead(pending.targetGUID) then
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
-- Target died - can't verify immunity, assume debuff landed
debuffVerified = true
if debug then
@@ -3627,7 +3748,7 @@ delayedTrackingFrame:SetScript("OnUpdate", function()
else
-- Target is alive - check if debuff exists
for slot = 1, 48 do
local _, _, _, debuffSpellID = _UnitDebuff(pending.targetGUID, slot)
local _, _, _, debuffSpellID = _UnitDebuff(sharedVerifyUnit, slot)
if not debuffSpellID then
if slot <= 16 then break end -- Regular debuffs are dense, overflow continues on nil
else
@@ -3818,7 +3939,7 @@ ev:SetScript("OnEvent", function()
-- Capture combo points when cast STARTS (before they're consumed)
if (eventType == "START" or eventType == "CHANNEL") and spellID then
local _, playerGUID = UnitExists("player")
local playerGUID = CleveRoids.GetGUID("player")
if casterGUID == playerGUID and targetGUID then
-- If this is a combo scaling spell OR Ferocious Bite, capture combo points NOW (before consumption)
local isComboSpell = CleveRoids.IsComboScalingSpellID and CleveRoids.IsComboScalingSpellID(spellID)
@@ -3867,7 +3988,7 @@ ev:SetScript("OnEvent", function()
end
if eventType == "CAST" and spellID then
local _, playerGUID = UnitExists("player")
local playerGUID = CleveRoids.GetGUID("player")
if casterGUID == playerGUID and targetGUID then
-- DRUID CARNAGE TALENT: Track Ferocious Bite cast for proc detection
@@ -3882,8 +4003,7 @@ ev:SetScript("OnEvent", function()
if carnageRank >= 1 then
local targetName = lib.guidToName[targetGUID]
if not targetName then
local _, currentTargetGUID = UnitExists("target")
currentTargetGUID = CleveRoids.NormalizeGUID(currentTargetGUID)
local currentTargetGUID = CleveRoids.GetGUID("target")
if currentTargetGUID == targetGUID then
targetName = UnitName("target")
lib.guidToName[targetGUID] = targetName
@@ -3979,12 +4099,15 @@ ev:SetScript("OnEvent", function()
-- 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]
-- v3.0+: Dynamically detect hidden CC via IsAuraHidden
if not isHiddenCC and _G.IsAuraHidden then
isHiddenCC = (_G.IsAuraHidden(spellID) == 1)
end
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)
local currentTargetGUID = CleveRoids.GetGUID("target")
if currentTargetGUID == targetGUID then
ccTargetName = UnitName("target")
lib.guidToName[targetGUID] = ccTargetName
@@ -4044,9 +4167,7 @@ ev:SetScript("OnEvent", function()
if duration and duration > 0 then
local targetName = lib.guidToName[targetGUID]
if not targetName then
local _, currentTargetGUID = UnitExists("target")
-- IMPORTANT: Normalize GUID before comparison to avoid type mismatch
currentTargetGUID = CleveRoids.NormalizeGUID(currentTargetGUID)
local currentTargetGUID = CleveRoids.GetGUID("target")
if currentTargetGUID == targetGUID then
targetName = UnitName("target")
lib.guidToName[targetGUID] = targetName
@@ -4167,9 +4288,8 @@ ev:SetScript("OnEvent", function()
local newStacks = 0
-- Check current stacks on target
local _, currentTargetGUID = UnitExists("target")
currentTargetGUID = CleveRoids.NormalizeGUID(currentTargetGUID)
if currentTargetGUID == targetGUID and CleveRoids.hasSuperwow then
local currentTargetGUID = CleveRoids.GetGUID("target")
if currentTargetGUID == targetGUID then
-- Scan debuff slots to find current stacks
for i = 1, 16 do
local _, existingStacks, _, existingSpellID = UnitDebuff("target", i)
@@ -4380,7 +4500,7 @@ ev:SetScript("OnEvent", function()
-- SHARED DEBUFFS FROM OTHER PLAYERS: Track when other players cast shared debuffs
-- This ensures Sunder Armor, Faerie Fire, etc. are tracked when ANY player casts them
-- Personal debuffs are still only tracked from the player (we only care about our own)
local _, playerGUID = UnitExists("player")
local playerGUID = CleveRoids.GetGUID("player")
if casterGUID ~= playerGUID and targetGUID then
-- Check if this is a shared debuff we should track
if lib.sharedDebuffs[spellID] then
@@ -4389,8 +4509,7 @@ ev:SetScript("OnEvent", function()
-- Get target name from cache or current target
local targetName = lib.guidToName[targetGUID]
if not targetName then
local _, currentTargetGUID = UnitExists("target")
currentTargetGUID = CleveRoids.NormalizeGUID(currentTargetGUID)
local currentTargetGUID = CleveRoids.GetGUID("target")
if currentTargetGUID == targetGUID then
targetName = UnitName("target")
lib.guidToName[targetGUID] = targetName
@@ -4399,9 +4518,8 @@ ev:SetScript("OnEvent", function()
-- For stacking debuffs, predict new stack count
local newStacks = 1
local _, currentTargetGUID = UnitExists("target")
currentTargetGUID = CleveRoids.NormalizeGUID(currentTargetGUID)
if currentTargetGUID == targetGUID and CleveRoids.hasSuperwow then
local currentTargetGUID = CleveRoids.GetGUID("target")
if currentTargetGUID == targetGUID then
-- Scan debuff slots to find current stacks
for i = 1, 16 do
local _, existingStacks, _, existingSpellID = UnitDebuff("target", i)
@@ -4458,6 +4576,58 @@ ev:SetScript("OnEvent", function()
CleveRoids._v238ChannelDuration = arg7 / 1000
CleveRoids._v238ChannelSpellId = arg2
end
-- Nampower fallback for UNIT_CASTEVENT START/CHANNEL: capture combo points and Dark Harvest
-- Only needed when SuperWoW is not available (UNIT_CASTEVENT won't fire)
if event == "SPELL_START_SELF" and not CleveRoids.hasSuperwow then
local spellID = arg2
local casterGuid = arg3
local targetGuid = arg4
local playerGUID = CleveRoids.GetGUID("player")
if casterGuid == playerGUID and spellID and targetGuid then
targetGuid = CleveRoids.NormalizeGUID(targetGuid)
-- Capture combo points before consumption (equivalent to UNIT_CASTEVENT START)
local isComboSpell = CleveRoids.IsComboScalingSpellID and CleveRoids.IsComboScalingSpellID(spellID)
local isFerociousBite = CleveRoids.FerociousBiteSpellIDs and CleveRoids.FerociousBiteSpellIDs[spellID]
if isComboSpell or isFerociousBite then
local currentCP = CleveRoids.GetComboPoints and CleveRoids.GetComboPoints()
if currentCP and currentCP > 0 then
CleveRoids.lastComboPoints = currentCP
if CleveRoids.debug then
local spellName = _SpellInfo(spellID) or "Unknown"
DEFAULT_CHAT_FRAME:AddMessage(
_string_format("|cffaaaaff[SPELL_START_SELF]|r Captured %d CP before casting %s (ID:%d)",
currentCP, spellName, spellID)
)
end
end
end
-- WARLOCK DARK HARVEST: Track channeling for DoT acceleration (TWoW Custom)
local spellType = arg8
if spellType == 1 and CleveRoids.DarkHarvestSpellIDs and CleveRoids.DarkHarvestSpellIDs[spellID] then
local channelDuration = 8 -- Base Dark Harvest duration
CleveRoids.darkHarvestData = {
targetGUID = targetGuid,
spellID = spellID,
startTime = GetTime(),
channelDuration = channelDuration,
isActive = true
}
lib.ApplyDarkHarvestStart(targetGuid)
if CleveRoids.debug then
DEFAULT_CHAT_FRAME:AddMessage(
_string_format("|cff9482c9[Dark Harvest]|r Started channeling on %s (DoTs will tick 30%% faster)",
lib.guidToName[targetGuid] or "Unknown")
)
end
end
end
end
-- pfUI 7.6 manages castTracking via its own SPELL_START handler
if lib.hasPfUI76 then return end
@@ -4555,7 +4725,7 @@ ev:SetScript("OnEvent", function()
local spellName = SpellInfo and SpellInfo(spellId)
if not spellName then return end
local _, playerGUID = UnitExists("player")
local playerGUID = CleveRoids.GetGUID("player")
local isOurs = (casterGuid == playerGUID)
-- Annotate pending CC/shared debuffs with SPELL_GO hit/miss outcome
@@ -4595,8 +4765,8 @@ ev:SetScript("OnEvent", function()
-- Get target name for immunity tracking
local targetName = lib.guidToName[targetGuid]
if not targetName then
local _, currentTargetGUID = UnitExists("target")
if CleveRoids.NormalizeGUID(currentTargetGUID) == targetGuid then
local currentTargetGUID = CleveRoids.GetGUID("target")
if currentTargetGUID == targetGuid then
targetName = UnitName("target")
lib.guidToName[targetGuid] = targetName
end
@@ -4727,6 +4897,93 @@ ev:SetScript("OnEvent", function()
end
end
-- Nampower fallback: Cast-complete features from UNIT_CASTEVENT CAST
-- Only needed when SuperWoW is not available and spell hit the target
if isOurs and not CleveRoids.hasSuperwow and numHit > 0 then
-- DRUID CARNAGE TALENT: Track Ferocious Bite cast for proc detection
if CleveRoids.FerociousBiteSpellIDs and CleveRoids.FerociousBiteSpellIDs[spellId] then
local _, _, _, _, rank = GetTalentInfo(2, 17)
local carnageRank = tonumber(rank) or 0
if carnageRank >= 1 then
local targetName = lib.guidToName[targetGuid] or UnitName("target") or "Unknown"
CleveRoids.lastFerociousBiteTime = GetTime()
CleveRoids.lastFerociousBiteTargetGUID = targetGuid
CleveRoids.lastFerociousBiteTargetName = targetName
CleveRoids.lastFerociousBiteSpellID = spellId
end
end
-- SHAMAN MOLTEN BLAST: Track for Flame Shock refresh detection
if CleveRoids.MoltenBlastSpellIDs and CleveRoids.MoltenBlastSpellIDs[spellId] then
CleveRoids.lastMoltenBlastTime = GetTime()
CleveRoids.lastMoltenBlastTargetGUID = targetGuid
end
-- WARLOCK CONFLAGRATE: Reduces Immolate duration by 3 seconds
if CleveRoids.ConflagrateSpellIDs and CleveRoids.ConflagrateSpellIDs[spellId] then
if lib.objects[targetGuid] then
for immolateID, _ in pairs(CleveRoids.ImmolateSpellIDs or {}) do
local rec = lib.objects[targetGuid][immolateID]
if rec and rec.duration and rec.start then
local remaining = rec.duration + rec.start - GetTime()
if remaining > 0 then
rec.duration = rec.duration - 3
break
end
end
end
end
end
-- CC IMMUNITY TRACKING: Check if this spell is a CC spell
local ccType = CleveRoids.GetSpellCCType and CleveRoids.GetSpellCCType(spellId)
if ccType then
local isHiddenCC = lib.hiddenCCSpells and lib.hiddenCCSpells[spellId]
if not isHiddenCC and _G.IsAuraHidden then
isHiddenCC = (_G.IsAuraHidden(spellId) == 1)
end
local ccTargetName = lib.guidToName[targetGuid]
if not ccTargetName then
local currentTargetGUID = CleveRoids.GetGUID("target")
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,
isHiddenCC = isHiddenCC,
spellGoHit = true, -- We already know it hit
})
end
-- DRUID CARNAGE: Save Rip cast data for potential Ferocious Bite refresh
if CleveRoids.RipSpellIDs and CleveRoids.RipSpellIDs[spellId] then
if CleveRoids.carnageDurationOverrides and CleveRoids.carnageDurationOverrides[spellId] then
CleveRoids.carnageDurationOverrides[spellId] = nil
end
end
-- CARNAGE: Save Rake cast data for potential Ferocious Bite refresh
if CleveRoids.RakeSpellIDs and CleveRoids.RakeSpellIDs[spellId] then
if CleveRoids.carnageDurationOverrides and CleveRoids.carnageDurationOverrides[spellId] then
CleveRoids.carnageDurationOverrides[spellId] = nil
end
end
-- Track last player cast for miss/dodge/parry removal
lib.lastPlayerCast = {
spellID = spellId,
targetGUID = targetGuid,
timestamp = GetTime()
}
end
elseif event == "AURA_CAST_ON_SELF" or event == "AURA_CAST_ON_OTHER" then
-- Skip if pfUI enhanced tracking is active
if lib.hasPfUIEnhanced then return end
@@ -4750,7 +5007,7 @@ ev:SetScript("OnEvent", function()
local duration = durationMs and (durationMs / 1000) or 0
local now = GetTime()
local _, playerGUID = UnitExists("player")
local playerGUID = CleveRoids.GetGUID("player")
local isOurs = (playerGUID and casterGuid == playerGUID)
-- Check if this debuff recently failed (miss/dodge/parry) on this specific target
@@ -4883,7 +5140,7 @@ ev:SetScript("OnEvent", function()
local spellName = SpellInfo and SpellInfo(spellId)
if not spellName then return end
local _, playerGUID = UnitExists("player")
local playerGUID = CleveRoids.GetGUID("player")
local isOurs = lib.ownDebuffs[guid] and lib.ownDebuffs[guid][spellName] ~= nil
-- v2.32+: state == 2 means stack increase - update stacks on existing entry
@@ -4925,12 +5182,15 @@ ev:SetScript("OnEvent", function()
end
-- Legacy: Update allSlots for slot tracking (pre-v2.30 or fallback)
lib.allSlots[guid] = lib.allSlots[guid] or {}
lib.allSlots[guid][slot] = {
spellName = spellName,
casterGuid = casterGuid,
isOurs = isOurs,
}
-- Skip if luaSlot is 0 (hidden aura in v3.0+)
if slot > 0 then
lib.allSlots[guid] = lib.allSlots[guid] or {}
lib.allSlots[guid][slot] = {
spellName = spellName,
casterGuid = casterGuid,
isOurs = isOurs,
}
end
elseif event == "DEBUFF_REMOVED_OTHER" then
-- Skip if pfUI enhanced tracking is active
@@ -4963,7 +5223,8 @@ ev:SetScript("OnEvent", function()
end
-- Remove from ownSlots
if lib.ownSlots[guid] and lib.ownSlots[guid][slot] then
-- Skip if luaSlot is 0 (hidden aura in v3.0+)
if slot > 0 and lib.ownSlots[guid] and lib.ownSlots[guid][slot] then
lib.ownSlots[guid][slot] = nil
end
@@ -4973,7 +5234,8 @@ ev:SetScript("OnEvent", function()
end
-- Legacy: Remove from allSlots and shift slots down (pre-v2.30 or fallback)
if lib.allSlots[guid] and lib.allSlots[guid][slot] then
-- Skip if luaSlot is 0 (hidden aura in v3.0+)
if slot > 0 and lib.allSlots[guid] and lib.allSlots[guid][slot] then
lib.allSlots[guid][slot] = nil
-- Shift slots down (only needed for legacy slot tracking)
@@ -5019,8 +5281,7 @@ ev:SetScript("OnEvent", function()
}
-- ownBuffCasts: only if player is the caster
local _, playerGuidRaw = UnitExists("player")
local playerGuid = playerGuidRaw and CleveRoids.NormalizeGUID(playerGuidRaw)
local playerGuid = CleveRoids.GetGUID("player")
if playerGuid and casterGuid == playerGuid then
lib.ownBuffCasts[guid] = lib.ownBuffCasts[guid] or {}
lib.ownBuffCasts[guid][spellName] = {
@@ -5044,8 +5305,7 @@ ev:SetScript("OnEvent", function()
if state == 2 then return end -- Stack decrease only, not full removal
local spellName = SpellInfo and SpellInfo(spellId)
local _, playerGuidRaw = UnitExists("player")
local playerGuid = playerGuidRaw and CleveRoids.NormalizeGUID(playerGuidRaw)
local playerGuid = CleveRoids.GetGUID("player")
if spellName and playerGuid then
if lib.ownBuffCasts[playerGuid] then
@@ -5158,7 +5418,7 @@ ev:SetScript("OnEvent", function()
end
-- Clean up overflow buff tracking (death removes all buffs)
local _, playerGUID = UnitExists("player")
local playerGUID = CleveRoids.GetGUID("player")
if playerGUID and guid == playerGUID then
-- Player died: clear all overflow buff entries and reset cap status
if CleveRoids.OverflowBuffs then
@@ -5565,7 +5825,7 @@ evCleanup:SetScript("OnEvent", function()
-- Cleanup on zone change / login / death
if event == "PLAYER_ENTERING_WORLD" or event == "PLAYER_DEAD" then
-- Keep only current target's data
local _, currentGUID = UnitExists("target")
local currentGUID = CleveRoids.GetGUID("target")
if currentGUID then
local temp = lib.objects[currentGUID]
lib.objects = {}
@@ -5588,7 +5848,7 @@ evCleanup:SetScript("OnEvent", function()
-- Remove expired effects from all GUIDs
for guid, effects in pairs(lib.objects) do
local _, targetGUID = UnitExists("target")
local targetGUID = CleveRoids.GetGUID("target")
local isCurrentTarget = (targetGUID == guid)
-- Check if current target is dead
@@ -5621,19 +5881,22 @@ evCleanup:SetScript("OnEvent", function()
end)
-- Judgement refresh on melee hits
-- NOTE: Judgement refresh is now handled in Core.lua via UNIT_CASTEVENT (MAINHAND/OFFHAND)
-- This chat-based fallback is only used if SuperWoW is not available
-- Priority: SuperWoW UNIT_CASTEVENT > Nampower AUTO_ATTACK_OTHER > Chat log fallback
-- This chat-based fallback is only used if neither SuperWoW nor Nampower v2.24+ is available
local evJudgement = CreateFrame("Frame", "CleveRoidsLibDebuffJudgementRefreshFrame", UIParent)
-- Only use chat-based detection if SuperWoW is not available
if not CleveRoids.hasSuperwow then
-- Only use chat-based detection if SuperWoW and Nampower auto-attack events are not available
local hasAutoAttackEvents = CleveRoids.NampowerAPI and CleveRoids.NampowerAPI.features
and CleveRoids.NampowerAPI.features.hasAutoAttackEvents
if not CleveRoids.hasSuperwow and not hasAutoAttackEvents then
evJudgement:RegisterEvent("CHAT_MSG_COMBAT_SELF_HITS")
evJudgement:RegisterEvent("CHAT_MSG_COMBAT_SELF_MISSES")
end
evJudgement:SetScript("OnEvent", function()
-- Skip if SuperWoW is available (handled by UNIT_CASTEVENT instead)
-- Skip if SuperWoW or Nampower auto-attack events handle this
if CleveRoids.hasSuperwow then return end
if hasAutoAttackEvents then return end
-- Only process for paladins
if CleveRoids.playerClass ~= "PALADIN" then return end
@@ -5657,11 +5920,10 @@ evJudgement:SetScript("OnEvent", function()
if not hasHit then return end
-- Get current target
local _, targetGUID = UnitExists("target")
local targetGUID = CleveRoids.GetGUID("target")
if not targetGUID then return end
targetGUID = CleveRoids.NormalizeGUID(targetGUID)
if not targetGUID or not lib.objects[targetGUID] then return end
if not lib.objects[targetGUID] then return end
-- Refresh all active Judgements on the target
for spellID, rec in pairs(lib.objects[targetGUID]) do
@@ -6731,7 +6993,6 @@ end
-- Get current buffs on a unit
local function GetUnitBuffs(unit)
local buffs = {}
if not CleveRoids.hasSuperwow then return buffs end
for i = 1, 32 do
local texture, stacks, spellID = UnitBuff(unit, i)
@@ -6820,7 +7081,6 @@ local INVULNERABILITY_SPELL_IDS = {
-- 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
@@ -7681,7 +7941,7 @@ function CleveRoids.CheckImmunity(unitId, spellOrSchool)
-- Universal debuff-based immunities (Banish, etc.)
-- Banish makes target immune to most damage schools (not all spells)
if CleveRoids.hasSuperwow then
do
local hasBanish = false
-- Check debuffs first (Banish: 710 = Rank 1, 18647 = Rank 2)
@@ -7787,16 +8047,14 @@ function CleveRoids.CheckImmunity(unitId, spellOrSchool)
initialImmune = true
elseif type(initialImmunityData) == "table" and initialImmunityData.buff then
-- Check if target has the immunity-granting buff
if CleveRoids.hasSuperwow then
for i = 1, 32 do
local texture, stacks, spellID = UnitBuff(unitId, i)
if not texture then break end
if spellID then
local buffName = SpellInfo(spellID)
if buffName and buffName == initialImmunityData.buff then
initialImmune = true
break
end
for i = 1, 32 do
local texture, stacks, spellID = UnitBuff(unitId, i)
if not texture then break end
if spellID then
local buffName = SpellInfo(spellID)
if buffName and buffName == initialImmunityData.buff then
initialImmune = true
break
end
end
end
@@ -7813,16 +8071,14 @@ function CleveRoids.CheckImmunity(unitId, spellOrSchool)
debuffImmune = true
elseif type(debuffImmunityData) == "table" and debuffImmunityData.buff then
-- Check if target has the immunity-granting buff
if CleveRoids.hasSuperwow then
for i = 1, 32 do
local texture, stacks, spellID = UnitBuff(unitId, i)
if not texture then break end
if spellID then
local buffName = SpellInfo(spellID)
if buffName and buffName == debuffImmunityData.buff then
debuffImmune = true
break
end
for i = 1, 32 do
local texture, stacks, spellID = UnitBuff(unitId, i)
if not texture then break end
if spellID then
local buffName = SpellInfo(spellID)
if buffName and buffName == debuffImmunityData.buff then
debuffImmune = true
break
end
end
end
@@ -7881,16 +8137,14 @@ function CleveRoids.CheckImmunity(unitId, spellOrSchool)
local requiredBuff = immunityData.buff
-- Check target's buffs
if CleveRoids.hasSuperwow then
for i = 1, 32 do
local texture, stacks, spellID = UnitBuff(unitId, i)
if not texture then break end
for i = 1, 32 do
local texture, stacks, spellID = UnitBuff(unitId, i)
if not texture then break end
if spellID then
local buffName = SpellInfo(spellID)
if buffName and buffName == requiredBuff then
return true
end
if spellID then
local buffName = SpellInfo(spellID)
if buffName and buffName == requiredBuff then
return true
end
end
end
@@ -8284,7 +8538,7 @@ function CleveRoids.HasReactiveProc(spellName)
-- If proc has a target GUID requirement, check if current target matches
if procData.targetGUID then
local _, targetGUID = UnitExists("target")
local targetGUID = CleveRoids.GetGUID("target")
if not targetGUID or targetGUID ~= procData.targetGUID then
return false
end
@@ -8317,7 +8571,7 @@ function CleveRoids.ParseReactiveCombatLog(lowerMsg)
return
end
local _, targetGUID = UnitExists("target")
local targetGUID = CleveRoids.GetGUID("target")
-- Check each reactive ability's trigger patterns
-- NOTE: Overpower (enemy_dodge) ALWAYS uses combat log text parsing, even when SPELL_GO
@@ -8384,6 +8638,25 @@ if originalUnitCastEvent then
end
end
-- Hook SPELL_START_SELF to clear reactive procs (Nampower fallback when SuperWoW not available)
local originalSpellStartSelf = CleveRoids.Frame and CleveRoids.Frame.SPELL_START_SELF
if originalSpellStartSelf and not CleveRoids.hasSuperwow then
CleveRoids.Frame.SPELL_START_SELF = function(...)
-- Call original handler first
if type(originalSpellStartSelf) == "function" then
originalSpellStartSelf(unpack(arg))
end
-- Clear reactive proc and resist state on spell cast start
-- SPELL_START_SELF args: casterGuid, targetGuid, spellId, ...
local spellId = arg[3]
if spellId then
CleveRoids.ClearReactiveProcOnCast(spellId)
CleveRoids.ClearResistState()
end
end
end
-- NAMPOWER v2.24+ AUTO_ATTACK EVENT HANDLER FOR REACTIVE ABILITIES
-- Uses native events for dodge/parry/block detection when available.
-- Falls back to combat log parsing for older Nampower versions.
@@ -8409,11 +8682,11 @@ CleveRoids.usingNampowerAutoAttack = false
-- Parameters: attackerGuid, targetGuid, totalDamage, hitInfo, victimState, ...
function CleveRoids.ProcessAutoAttackEvent(isPlayerAttacker, attackerGuid, targetGuid, totalDamage, hitInfo, victimState)
-- Get player GUID for comparison
local _, playerGUID = UnitExists("player")
local playerGUID = CleveRoids.GetGUID("player")
if not playerGUID then return end
-- Determine current target GUID
local _, currentTargetGUID = UnitExists("target")
local currentTargetGUID = CleveRoids.GetGUID("target")
-- ========================================================================
-- OVERPOWER / SURPRISE ATTACK: Enemy dodges YOUR attack
@@ -8509,8 +8782,8 @@ local function ProcessSpellMissSelf(spellId, targetGuid, missInfo)
-- Resolve target name from GUID cache or current target
local targetName = lib.guidToName[targetGuid]
if not targetName then
local _, currentTargetGUID = UnitExists("target")
if CleveRoids.NormalizeGUID(currentTargetGUID) == CleveRoids.NormalizeGUID(targetGuid) then
local currentTargetGUID = CleveRoids.GetGUID("target")
if currentTargetGUID == targetGuid then
targetName = UnitName("target")
if targetName then
lib.guidToName[targetGuid] = targetName
@@ -8747,7 +9020,7 @@ local function ProcessSpellMissOther(spellId, casterGuid, targetGuid, missInfo)
if not targetGuid or not missInfo then return end
-- Only care when player is the target (victim)
local _, playerGUID = UnitExists("player")
local playerGUID = CleveRoids.GetGUID("player")
if not playerGUID or targetGuid ~= playerGUID then return end
-- DODGE: Enemy spell dodged by player → Revenge proc
@@ -8924,7 +9197,7 @@ reactiveFrame:SetScript("OnEvent", function()
local victimState = arg5
-- Check if player is the attacker
local _, playerGUID = UnitExists("player")
local playerGUID = CleveRoids.GetGUID("player")
local isPlayerAttacker = (attackerGuid == playerGUID)
CleveRoids.ProcessAutoAttackEvent(isPlayerAttacker, attackerGuid, targetGuid, totalDamage, hitInfo, victimState)
@@ -8977,8 +9250,7 @@ reactiveFrame:SetScript("OnEvent", function()
if not procTarget or procTarget == "0x0000000000000000" then
procTarget = pending.targetGuid
if not procTarget or procTarget == "0x0000000000000000" then
local _, currentTargetGUID = UnitExists("target")
procTarget = currentTargetGUID
procTarget = CleveRoids.GetGUID("target")
end
end
@@ -9081,7 +9353,7 @@ function CleveRoids.CheckResistState(resistType)
if not state then return false end
-- Must have current target that matches the GUID from resist event
local _, currentTargetGUID = UnitExists("target")
local currentTargetGUID = CleveRoids.GetGUID("target")
if not currentTargetGUID or currentTargetGUID ~= state.targetGUID then
return false
end
@@ -9111,7 +9383,7 @@ local function ParseResistCombatLog(lowerMsg)
if not string.find(lowerMsg, "resist") then return end
-- Get current target info for matching
local _, targetGUID = UnitExists("target")
local targetGUID = CleveRoids.GetGUID("target")
if not targetGUID then return end
local targetName = UnitName("target")
@@ -9230,7 +9502,7 @@ local function HandleDebuffFade()
if rec.caster == "player" then
local hasExpired = (rec.start + rec.duration + 1) <= timestamp
local stillExists = false
local _, checkGUID = UnitExists("target")
local checkGUID = CleveRoids.GetGUID("target")
if checkGUID == targetGUID then
for i = 1, 16 do
local _, _, _, checkSpellID = UnitDebuff("target", i)
@@ -9247,7 +9519,7 @@ local function HandleDebuffFade()
else
-- Shared debuff: scan to verify it's gone
local stillExists = false
local _, checkGUID = UnitExists("target")
local checkGUID = CleveRoids.GetGUID("target")
if checkGUID == targetGUID then
for i = 1, 16 do
local _, _, _, checkSpellID = UnitDebuff("target", i)