fix some blizzard commands missed in the macro checker, updates for nampower 2.33

This commit is contained in:
Jrc13245
2026-02-11 17:22:08 -05:00
parent eff56e56fc
commit f2bce42acb
5 changed files with 1033 additions and 125 deletions
+204 -102
View File
@@ -448,6 +448,26 @@ local stat_checks = {
nature_power = function() return GetSpellBonusDamage(2) end,
shadow_power = function() return GetSpellBonusDamage(5) end,
-- Highest spell power across all schools
-- Uses Nampower v2.31+ GetSpellPower when available (single call), falls back to per-school
spell_power = function()
local API = CleveRoids.NampowerAPI
if API and API.features and API.features.hasGetSpellPower then
local p, h, fi, n, fr, s, a = API.GetSpellPower()
if p then
return math.max(p, h, fi, n, fr, s, a)
end
end
-- Fallback: max of per-school GetSpellBonusDamage calls
return math.max(
GetSpellBonusDamage(2) or 0,
GetSpellBonusDamage(3) or 0,
GetSpellBonusDamage(4) or 0,
GetSpellBonusDamage(5) or 0,
GetSpellBonusDamage(6) or 0
)
end,
-- Defensive Stats
armor = function() local _, effective = UnitArmor("player"); return effective end,
defense = function()
@@ -1091,6 +1111,10 @@ autoAttackFrame:SetScript("OnEvent", function()
this:RegisterEvent("AURA_CAST_ON_SELF")
this:RegisterEvent("AURA_CAST_ON_OTHER")
-- Register removal events for instant AllCasterAuraTracking cleanup
this:RegisterEvent("BUFF_REMOVED_OTHER")
this:RegisterEvent("DEBUFF_REMOVED_OTHER")
-- Check if the CVar is enabled (required for all-caster buff tracking)
local auraCastEnabled = GetCVar("NP_EnableAuraCastEvents")
if auraCastEnabled ~= "1" then
@@ -1109,32 +1133,42 @@ autoAttackFrame:SetScript("OnEvent", function()
elseif event == "BUFF_UPDATE_DURATION_SELF" or event == "DEBUFF_UPDATE_DURATION_SELF" then
-- v2.30+: Player aura duration was refreshed
-- arg1 = auraSlot (0-based raw slot index)
-- arg2 = durationMs, arg3 = expirationTimeMs, arg4 = spellId (v2.33+)
local auraSlot = arg1
if auraSlot and API and API.GetPlayerAuraDuration then
local spellId, durationMs, expirationTimeMs = API.GetPlayerAuraDuration(auraSlot)
if spellId and spellId > 0 and durationMs and durationMs > 0 then
local _, playerGUID = UnitExists("player")
if playerGUID then
if not CleveRoids.AllCasterAuraTracking[playerGUID] then
CleveRoids.AllCasterAuraTracking[playerGUID] = {}
end
local durationSec = durationMs / 1000
local now = GetTime()
local expirationSec = expirationTimeMs and expirationTimeMs / 1000
local startTime = expirationSec and (expirationSec - durationSec) or now
CleveRoids.AllCasterAuraTracking[playerGUID][spellId] = {
start = startTime,
duration = durationSec,
casterGuid = playerGUID,
}
local spellId, durationMs, expirationTimeMs
if CleveRoids.debug then
local spellName = SpellInfo and SpellInfo(spellId) or "Unknown"
DEFAULT_CHAT_FRAME:AddMessage(string.format(
"|cff88ff88[AuraDurUpdate]|r %s (slot:%d, ID:%d) dur=%.1fs",
spellName, auraSlot, spellId, durationSec
))
end
-- v2.33+: spellId provided directly as arg4, with duration info in arg2/arg3
if arg4 and arg4 > 0 then
spellId = arg4
durationMs = arg2
expirationTimeMs = arg3
elseif auraSlot and API and API.GetPlayerAuraDuration then
-- Pre-v2.33 fallback: query duration by aura slot
spellId, durationMs, expirationTimeMs = API.GetPlayerAuraDuration(auraSlot)
end
if spellId and spellId > 0 and durationMs and durationMs > 0 then
local _, playerGUID = UnitExists("player")
if playerGUID then
if not CleveRoids.AllCasterAuraTracking[playerGUID] then
CleveRoids.AllCasterAuraTracking[playerGUID] = {}
end
local durationSec = durationMs / 1000
local now = GetTime()
local expirationSec = expirationTimeMs and expirationTimeMs / 1000
local startTime = expirationSec and (expirationSec - durationSec) or now
CleveRoids.AllCasterAuraTracking[playerGUID][spellId] = {
start = startTime,
duration = durationSec,
casterGuid = playerGUID,
}
if CleveRoids.debug then
local spellName = SpellInfo and SpellInfo(spellId) or "Unknown"
DEFAULT_CHAT_FRAME:AddMessage(string.format(
"|cff88ff88[AuraDurUpdate]|r %s (slot:%d, ID:%d) dur=%.1fs",
spellName, auraSlot, spellId, durationSec
))
end
end
end
@@ -1154,6 +1188,20 @@ autoAttackFrame:SetScript("OnEvent", function()
elseif event == "AURA_CAST_ON_OTHER" then
OnAuraCastOther(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9)
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)
local guid = arg1
local spellId = arg3
local state = arg7
if state == 2 then return end -- Stack change, not full removal
if guid and spellId and CleveRoids.AllCasterAuraTracking[guid] then
CleveRoids.AllCasterAuraTracking[guid][spellId] = nil
if not next(CleveRoids.AllCasterAuraTracking[guid]) then
CleveRoids.AllCasterAuraTracking[guid] = nil
end
end
end
end)
@@ -3132,60 +3180,58 @@ function CleveRoids.ValidateAura(unit, args, isbuff)
local searchID = args.name and tonumber(args.name)
local searchName = not searchID and args.name and _string_lower(args.name) or nil
-- Primary search: BUFFS if isbuff==true, DEBUFFS if isbuff==false
while true do
local texture
local current_spellID = nil
if isPlayer then
-- GetPlayerAura(index, isbuff) => texture, stacks, spellID, timeLeft
texture, stacks, current_spellID, remaining = CleveRoids.GetPlayerAura(i, isbuff)
else
if isbuff then
-- UnitBuff => texture, stacks, spellID
texture, stacks, current_spellID = UnitBuff(unit, i)
else
-- UnitDebuff => texture, stacks, debuffType, spellID
texture, stacks, _, current_spellID = UnitDebuff(unit, i)
end
remaining = nil
end
if not texture then break end
if current_spellID then
if searchID then
-- Spell ID matching: [mybuff:17941]
if current_spellID == searchID then
found = true
break
end
elseif searchName then
-- PERFORMANCE: Use cached lowercase spell name lookup
local lowerName = GetLowercaseSpellName(current_spellID)
if lowerName and lowerName == searchName then
found = true
break
-- Fast path: Use GetUnitField to search all 48 aura slots in 2 C-to-Lua calls
-- Only for non-player units (player uses GetPlayerAura which provides duration)
local skipSlowSearch = false
if not isPlayer then
local API = CleveRoids.NampowerAPI
if API and API.FindUnitAuraInfo then
local gufResult, gufSpellId, gufStacks, gufSlot = API.FindUnitAuraInfo(unit, searchID, searchName)
if gufResult ~= nil then -- GetUnitField was available and searched
skipSlowSearch = true
if gufResult then
if isbuff then
-- For buff checks: only accept buff slots (1-32)
if gufSlot <= 32 then
found = true
stacks = gufStacks or 0
end
else
-- For debuff checks: accept any slot (debuffs overflow to buff slots)
found = true
stacks = gufStacks or 0
end
end
end
end
i = i + 1
end
-- Overflow handling: when searching DEBUFFS on non-players, also scan BUFFS
if not isbuff and not isPlayer and not found and (searchID or searchName) then
i = 1
-- Slow path: Per-slot iteration (player always, non-player when GetUnitField unavailable)
if not skipSlowSearch then
-- Primary search: BUFFS if isbuff==true, DEBUFFS if isbuff==false
while true do
local texture
local current_spellID = nil
-- UnitBuff => texture, stacks, spellID
texture, stacks, current_spellID = UnitBuff(unit, i)
if isPlayer then
-- GetPlayerAura(index, isbuff) => texture, stacks, spellID, timeLeft
texture, stacks, current_spellID, remaining = CleveRoids.GetPlayerAura(i, isbuff)
else
if isbuff then
-- UnitBuff => texture, stacks, spellID
texture, stacks, current_spellID = UnitBuff(unit, i)
else
-- UnitDebuff => texture, stacks, debuffType, spellID
texture, stacks, _, current_spellID = UnitDebuff(unit, i)
end
remaining = nil
end
if not texture then break end
if current_spellID then
if searchID then
-- Spell ID matching: [mybuff:17941]
if current_spellID == searchID then
found = true
break
@@ -3202,6 +3248,37 @@ function CleveRoids.ValidateAura(unit, args, isbuff)
i = i + 1
end
-- Overflow handling: when searching DEBUFFS on non-players, also scan BUFFS
if not isbuff and not isPlayer and not found and (searchID or searchName) then
i = 1
while true do
local texture
local current_spellID = nil
-- UnitBuff => texture, stacks, spellID
texture, stacks, current_spellID = UnitBuff(unit, i)
if not texture then break end
if current_spellID then
if searchID then
if current_spellID == searchID then
found = true
break
end
elseif searchName then
-- PERFORMANCE: Use cached lowercase spell name lookup
local lowerName = GetLowercaseSpellName(current_spellID)
if lowerName and lowerName == searchName then
found = true
break
end
end
end
i = i + 1
end
end
end
local ops = CleveRoids.operators
@@ -3412,75 +3489,100 @@ function CleveRoids.ValidateUnitDebuff(unit, args)
-- 2. Personal debuffs during the 0.2s pending delay after casting
-- 3. Any debuffs not yet registered in tracking (edge cases)
--
-- For simple existence checks ([nodebuff]Spell), we want to find the debuff
-- regardless of whether it's in our tracking table. Time-remaining checks
-- will use tracking table data when available, but existence is from scan.
local isSimpleExistenceCheck = not args.operator and not args.amount
-- 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 and isSimpleExistenceCheck then
if not found and CleveRoids.hasSuperwow 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
-- after /reload when tracking is cleared.
-- Scan debuff slots
for i = 1, 16 do
local tex, debuffStacks, _, debuffSpellID = UnitDebuff(unit, i)
if not tex then break end
if debuffSpellID then
local matched = false
if searchID then
matched = (debuffSpellID == searchID)
else
local baseName, fullName = GetSpellNames(debuffSpellID)
matched = baseName and (baseName == args.name or fullName == args.name)
end
if matched then
-- IMPORTANT: Only use fallback for SHARED debuffs
-- Personal debuffs must come from tracking table (caster check)
local isShared = lib and lib.IsPersonalDebuff and lib:IsPersonalDebuff(debuffSpellID) == false
-- Fast path: Use GetUnitField to search all 48 aura slots in 2 C-to-Lua calls
local API = CleveRoids.NampowerAPI
local searchNameLower = not searchID and args.name and _string_lower(args.name) or nil
if API and API.FindUnitAuraInfo then
local gufResult, gufSpellId, gufStacks, gufSlot = API.FindUnitAuraInfo(unit, searchID, searchNameLower)
if gufResult ~= nil then
-- GetUnitField was available and searched
if gufResult then
local isShared = lib and lib.IsPersonalDebuff and lib:IsPersonalDebuff(gufSpellId) == false
if isShared then
found = true
texture = tex
stacks = debuffStacks or 0
spellID = debuffSpellID
-- No duration tracking for shared debuffs
stacks = gufStacks or 0
spellID = gufSpellId
remaining = nil
break
end
end
else
-- GetUnitField unavailable, fall through to per-slot iteration
API = nil -- Signal slow path below
end
else
API = nil -- Signal slow path below
end
-- If still not found, check buff slots (overflow debuffs)
if not found then
for i = 1, 32 do
local tex, buffStacks, buffSpellID = UnitBuff(unit, i)
-- Slow path: Per-slot UnitDebuff + UnitBuff iteration (GetUnitField unavailable)
if not found and not API then
-- Scan debuff slots
for i = 1, 16 do
local tex, debuffStacks, _, debuffSpellID = UnitDebuff(unit, i)
if not tex then break end
if buffSpellID then
if debuffSpellID then
local matched = false
if searchID then
matched = (buffSpellID == searchID)
matched = (debuffSpellID == searchID)
else
local baseName, fullName = GetSpellNames(buffSpellID)
local baseName, fullName = GetSpellNames(debuffSpellID)
matched = baseName and (baseName == args.name or fullName == args.name)
end
if matched then
-- IMPORTANT: Only use fallback for SHARED debuffs
local isShared = lib and lib.IsPersonalDebuff and lib:IsPersonalDebuff(buffSpellID) == false
-- Personal debuffs must come from tracking table (caster check)
local isShared = lib and lib.IsPersonalDebuff and lib:IsPersonalDebuff(debuffSpellID) == false
if isShared then
found = true
texture = tex
stacks = buffStacks or 0
spellID = buffSpellID
stacks = debuffStacks or 0
spellID = debuffSpellID
-- No duration tracking for shared debuffs
remaining = nil
break
end
end
end
end
-- If still not found, check buff slots (overflow debuffs)
if not found then
for i = 1, 32 do
local tex, buffStacks, buffSpellID = UnitBuff(unit, i)
if not tex then break end
if buffSpellID then
local matched = false
if searchID then
matched = (buffSpellID == searchID)
else
local baseName, fullName = GetSpellNames(buffSpellID)
matched = baseName and (baseName == args.name or fullName == args.name)
end
if matched then
-- IMPORTANT: Only use fallback for SHARED debuffs
local isShared = lib and lib.IsPersonalDebuff and lib:IsPersonalDebuff(buffSpellID) == false
if isShared then
found = true
texture = tex
stacks = buffStacks or 0
spellID = buffSpellID
remaining = nil
break
end
end
end
end
end
end
end
-- For player unit, use standard search (player only sees own debuffs on self)
+332 -16
View File
@@ -85,29 +85,341 @@ local VALID_COMMANDS = {
["/cleveroid"] = true,
["/cleveroidmacros"] = true,
["/macrocheck"] = true,
-- Chat / emotes / common WoW commands
["/s"] = true,
["/y"] = true,
["/r"] = true,
["/bg"] = true,
["/e"] = true,
["/w"] = true,
["/g"] = true,
["/p"] = true,
["/invite"] = true,
["/trade"] = true,
-- Turtle WoW / third-party addon commands
["/db"] = true,
["/roll"] = true,
["/bow"] = true,
["/rinse"] = true,
["/am"] = true,
["/aux"] = true,
["/instancetimers"] = true,
["/umacro"] = true,
["/camp"] = true,
["/logout"] = true,
["/exit"] = true,
--------------------------------------------------------------------------
-- Standard WoW 1.12.1 Commands
--------------------------------------------------------------------------
-- Chat
["/say"] = true,
["/s"] = true,
["/yell"] = true,
["/y"] = true,
["/shout"] = true,
["/whisper"] = true,
["/w"] = true,
["/tell"] = true,
["/reply"] = true,
["/r"] = true,
["/party"] = true,
["/p"] = true,
["/guild"] = true,
["/g"] = true,
["/officer"] = true,
["/o"] = true,
["/raid"] = true,
["/ra"] = true,
["/battleground"] = true,
["/bg"] = true,
["/emote"] = true,
["/e"] = true,
["/em"] = true,
["/me"] = true,
["/rw"] = true,
["/announce"] = true,
-- Channel
["/join"] = true,
["/leave"] = true,
["/channel"] = true,
["/chatlist"] = true,
["/chatwho"] = true,
["/chatinvite"] = true,
["/ckick"] = true,
-- Targeting
["/tar"] = true,
["/assist"] = true,
["/a"] = true,
["/targetenemy"] = true,
["/targetfriend"] = true,
["/targetnearestenemy"] = true,
["/targetnearestfriend"] = true,
["/targetlasttarget"] = true,
["/targetlastenemy"] = true,
["/targetlastfriend"] = true,
-- Group / Raid
["/invite"] = true,
["/inv"] = true,
["/uninvite"] = true,
["/kick"] = true,
["/promote"] = true,
["/leader"] = true,
["/disband"] = true,
["/trade"] = true,
["/roll"] = true,
["/random"] = true,
["/raidinfo"] = true,
["/readycheck"] = true,
["/lfg"] = true,
["/lfm"] = true,
-- Loot
["/masterloot"] = true,
["/ffa"] = true,
["/roundrobin"] = true,
["/needbeforegreed"] = true,
["/grouploot"] = true,
-- Guild
["/ginvite"] = true,
["/guildinvite"] = true,
["/gremove"] = true,
["/guildremove"] = true,
["/gpromote"] = true,
["/guildpromote"] = true,
["/gdemote"] = true,
["/guilddemote"] = true,
["/gquit"] = true,
["/guildquit"] = true,
["/gdisband"] = true,
["/guilddisband"] = true,
["/gmotd"] = true,
["/guildmotd"] = true,
["/ginfo"] = true,
["/guildinfo"] = true,
["/groster"] = true,
["/guildroster"] = true,
["/guildleader"] = true,
-- System
["/logout"] = true,
["/camp"] = true,
["/quit"] = true,
["/exit"] = true,
["/reload"] = true,
["/reloadui"] = true,
["/console"] = true,
["/macro"] = true,
["/played"] = true,
["/time"] = true,
["/who"] = true,
["/afk"] = true,
["/away"] = true,
["/dnd"] = true,
["/busy"] = true,
["/help"] = true,
["/pvp"] = true,
["/combatlog"] = true,
["/chatlog"] = true,
["/clear"] = true,
["/ignore"] = true,
["/unignore"] = true,
["/friend"] = true,
["/friends"] = true,
["/removefriend"] = true,
["/gm"] = true,
["/bug"] = true,
["/suggest"] = true,
-- Movement / Stance
["/follow"] = true,
["/f"] = true,
["/dismount"] = true,
["/cancelform"] = true,
-- Pet (standard WoW extras)
["/petabandon"] = true,
["/petstay"] = true,
-- Emotes
["/agree"] = true,
["/amaze"] = true,
["/angry"] = true,
["/apologize"] = true,
["/applaud"] = true,
["/attacktarget"] = true,
["/bark"] = true,
["/bashful"] = true,
["/beckon"] = true,
["/beg"] = true,
["/bite"] = true,
["/bleed"] = true,
["/blink"] = true,
["/blush"] = true,
["/boggle"] = true,
["/bonk"] = true,
["/bored"] = true,
["/bounce"] = true,
["/bow"] = true,
["/bravo"] = true,
["/burp"] = true,
["/bye"] = true,
["/cackle"] = true,
["/calm"] = true,
["/cat"] = true,
["/charge"] = true,
["/cheer"] = true,
["/chicken"] = true,
["/chuckle"] = true,
["/clap"] = true,
["/cold"] = true,
["/comfort"] = true,
["/commend"] = true,
["/confused"] = true,
["/congratulate"] = true,
["/congrats"] = true,
["/cough"] = true,
["/cower"] = true,
["/crack"] = true,
["/cringe"] = true,
["/cry"] = true,
["/cuddle"] = true,
["/curious"] = true,
["/curtsey"] = true,
["/dance"] = true,
["/disappointed"] = true,
["/doom"] = true,
["/drink"] = true,
["/drool"] = true,
["/duck"] = true,
["/eat"] = true,
["/embarrass"] = true,
["/encourage"] = true,
["/enemy"] = true,
["/eye"] = true,
["/fart"] = true,
["/feast"] = true,
["/fidget"] = true,
["/flap"] = true,
["/flee"] = true,
["/flex"] = true,
["/flirt"] = true,
["/flop"] = true,
["/gasp"] = true,
["/gaze"] = true,
["/giggle"] = true,
["/glad"] = true,
["/gloat"] = true,
["/glare"] = true,
["/golfclap"] = true,
["/goodbye"] = true,
["/greet"] = true,
["/grin"] = true,
["/groan"] = true,
["/grovel"] = true,
["/growl"] = true,
["/guffaw"] = true,
["/hail"] = true,
["/happy"] = true,
["/healme"] = true,
["/hello"] = true,
["/helpme"] = true,
["/hug"] = true,
["/hungry"] = true,
["/impatient"] = true,
["/incoming"] = true,
["/insult"] = true,
["/introduce"] = true,
["/jk"] = true,
["/kiss"] = true,
["/kneel"] = true,
["/laugh"] = true,
["/laydown"] = true,
["/lick"] = true,
["/listen"] = true,
["/lost"] = true,
["/love"] = true,
["/massage"] = true,
["/moan"] = true,
["/mock"] = true,
["/moo"] = true,
["/moon"] = true,
["/mourn"] = true,
["/no"] = true,
["/nod"] = true,
["/nosepick"] = true,
["/oom"] = true,
["/openfire"] = true,
["/panic"] = true,
["/pat"] = true,
["/peek"] = true,
["/peer"] = true,
["/peon"] = true,
["/pest"] = true,
["/pick"] = true,
["/pinch"] = true,
["/pity"] = true,
["/plead"] = true,
["/point"] = true,
["/poke"] = true,
["/ponder"] = true,
["/pounce"] = true,
["/praise"] = true,
["/pray"] = true,
["/purr"] = true,
["/puzzle"] = true,
["/question"] = true,
["/raise"] = true,
["/rasp"] = true,
["/ready"] = true,
["/regret"] = true,
["/roar"] = true,
["/rofl"] = true,
["/rude"] = true,
["/ruffle"] = true,
["/sad"] = true,
["/salute"] = true,
["/scared"] = true,
["/scratch"] = true,
["/sexy"] = true,
["/shake"] = true,
["/shimmy"] = true,
["/shiver"] = true,
["/shoo"] = true,
["/shrug"] = true,
["/shy"] = true,
["/sigh"] = true,
["/silly"] = true,
["/sit"] = true,
["/slap"] = true,
["/sleep"] = true,
["/smile"] = true,
["/smirk"] = true,
["/snarl"] = true,
["/snicker"] = true,
["/sniff"] = true,
["/sob"] = true,
["/soothe"] = true,
["/sorry"] = true,
["/spit"] = true,
["/stand"] = true,
["/stare"] = true,
["/stink"] = true,
["/strong"] = true,
["/surprised"] = true,
["/surrender"] = true,
["/tap"] = true,
["/tease"] = true,
["/thank"] = true,
["/thirsty"] = true,
["/threaten"] = true,
["/tickle"] = true,
["/tired"] = true,
["/train"] = true,
["/truce"] = true,
["/twiddle"] = true,
["/veto"] = true,
["/victory"] = true,
["/violin"] = true,
["/volunteer"] = true,
["/wait"] = true,
["/wave"] = true,
["/welcome"] = true,
["/whine"] = true,
["/whistle"] = true,
["/wink"] = true,
["/work"] = true,
["/yawn"] = true,
["/yes"] = true,
}
-- Commands that can have conditionals without actions
@@ -122,6 +434,10 @@ local COMMANDS_NO_ACTION_NEEDED = {
["/target"] = true,
["/cleartarget"] = true,
["/focus"] = true,
["/follow"] = true,
["/f"] = true,
["/dismount"] = true,
["/cancelform"] = true,
["/startattack"] = true,
["/stopattack"] = true,
["/stopcasting"] = true,
+125 -3
View File
@@ -69,11 +69,23 @@
- BUFF_UPDATE_DURATION_SELF / DEBUFF_UPDATE_DURATION_SELF events
- GetPlayerAuraDuration(auraSlot) - Get duration info for player aura by slot
Spell Miss Events (v2.31+):
- SPELL_MISS_SELF / SPELL_MISS_OTHER - Spell miss/resist/immune/dodge/etc.
- GetSpellPower([mode]) - Player mod damage done for all 7 schools
Aura Event State Parameter (v2.32+):
- Buff/debuff events include 7th `state` parameter (0=added, 1=removed, 2=modified)
- REMOVED events now properly fire for stack decrements (state=2)
Duration Event SpellId (v2.33+):
- BUFF/DEBUFF_UPDATE_DURATION_SELF include spellId as 4th parameter
- GetCastInfo() guid field fix (no code change needed)
Settings Integration:
- Reads from NampowerSettings addon when available
- Falls back to CVars when addon not present
Current version: v2.30.0
Current version: v2.33.0
]]
local _G = _G or getfenv(0)
@@ -219,6 +231,17 @@ API.VERSION_REQUIREMENTS = {
["AuraSlotParameter"] = { 2, 30, 0 },
["AuraDurationEvents"] = { 2, 30, 0 },
["GetPlayerAuraDuration"] = { 2, 30, 0, "GetPlayerAuraDuration" },
-- v2.31+ - Spell miss events and spell power query
["SpellMissEvents"] = { 2, 31, 0 }, -- SPELL_MISS_SELF/OTHER events
["GetSpellPower"] = { 2, 31, 0, "GetSpellPower" },
-- v2.32+ - Aura event state parameter and stack removal fix
["AuraEventState"] = { 2, 32, 0 },
-- v2.33+ - UPDATE_DURATION spellId parameter and GetCastInfo guid fix
["DurationEventSpellId"] = { 2, 33, 0 },
["GetCastInfoGuidFix"] = { 2, 33, 0 },
}
-- Check if a specific feature is available
@@ -348,6 +371,17 @@ local function InitializeFeatures()
f.hasAuraDurationEvents = API.HasFeature("AuraDurationEvents")
f.hasGetPlayerAuraDuration = API.HasFeature("GetPlayerAuraDuration")
-- v2.31+ Spell miss events and spell power
f.hasSpellMissEvents = API.HasFeature("SpellMissEvents")
f.hasGetSpellPower = API.HasFeature("GetSpellPower")
-- v2.32+ Aura event state parameter
f.hasAuraEventState = API.HasFeature("AuraEventState")
-- v2.33+ Duration event spellId and GetCastInfo guid fix
f.hasDurationEventSpellId = API.HasFeature("DurationEventSpellId")
f.hasGetCastInfoGuidFix = API.HasFeature("GetCastInfoGuidFix")
-- Runtime detection for enhanced spell functions (verify by testing)
if f.hasEnhancedSpellFunctions and GetSpellTexture then
local success, result = pcall(function()
@@ -1041,6 +1075,61 @@ function API.UnitHasAura(unitToken, spellId)
return false
end
-- Get unit's aura stack counts (auraApplications field)
-- Pass copy=1 to get an independent copy safe for storage (v2.20+)
function API.GetUnitAuraApplications(unitToken, copy)
return API.GetUnitFieldValue(unitToken, "auraApplications", copy)
end
-- Composite helper: Search unit's auras for a spell by ID or lowercase name,
-- returning match info with stack count from auraApplications.
--
-- Parameters:
-- unitToken - Unit token (e.g., "target", "focus")
-- searchSpellId - Spell ID to match (number), or nil for name search
-- searchNameLower - Lowercase spell name to match (string), or nil for ID search
--
-- Returns:
-- nil - GetUnitField unavailable (caller should use slow path)
-- false - Searched but aura not found
-- true, matchedSpellId, stacks, slotIndex - Found; slotIndex 1-32 = buff, 33-48 = debuff
function API.FindUnitAuraInfo(unitToken, searchSpellId, searchNameLower)
if not unitToken then return nil end
if not searchSpellId and not searchNameLower then return nil end
local auras = API.GetUnitAuras(unitToken)
if not auras then return nil end -- GetUnitField unavailable
local applications = API.GetUnitAuraApplications(unitToken)
for i = 1, 48 do
local auraId = auras[i]
if auraId and auraId ~= 0 then
local matched = false
if searchSpellId then
matched = (auraId == searchSpellId)
elseif searchNameLower then
local name = SpellInfo and SpellInfo(auraId)
if name then
-- Strip rank suffix for consistent matching
local baseName = string.gsub(name, "%s*%(%s*Rank%s+%d+%s*%)", "")
if string.lower(baseName) == searchNameLower then
matched = true
end
end
end
if matched then
local stacks = applications and applications[i] or 0
return true, auraId, stacks, i
end
end
end
return false
end
--------------------------------------------------------------------------------
-- INVENTORY & EQUIPMENT API (v2.18+)
--------------------------------------------------------------------------------
@@ -1930,7 +2019,8 @@ API.CAST_TYPE = {
}
-- Buff/debuff event names (v2.18+)
-- Parameters: guid, slot, spellId, stackCount, auraLevel (v2.20+), auraSlot (v2.30+, raw 0-based)
-- Parameters: guid, slot, spellId, stackCount, auraLevel (v2.20+), auraSlot (v2.30+, raw 0-based),
-- state (v2.32+, 0=added/1=removed/2=modified)
API.AURA_EVENTS = {
"BUFF_ADDED_SELF",
"BUFF_REMOVED_SELF",
@@ -1958,9 +2048,17 @@ API.AURA_CAP_STATUS = {
BOTH_FULL = 3,
}
-- Aura event state constants (v2.32+)
-- The 7th parameter on BUFF/DEBUFF_ADDED/REMOVED events
API.AURA_STATE = {
ADDED = 0, -- Newly added aura
REMOVED = 1, -- Fully removed aura
MODIFIED = 2, -- Stack change (ADDED if increased, REMOVED if decreased)
}
-- Aura duration update event names (v2.30+)
-- Fires when a buff/debuff duration is refreshed on the player
-- Parameters: auraSlot (0-based raw slot index)
-- Parameters: auraSlot (0-based raw slot index), durationMs, expirationTimeMs, spellId (v2.33+)
API.AURA_DURATION_EVENTS = {
"BUFF_UPDATE_DURATION_SELF", -- Player buff duration refreshed
"DEBUFF_UPDATE_DURATION_SELF", -- Player debuff duration refreshed
@@ -2860,5 +2958,29 @@ function API.ClearSpellTypeCache()
API.spellTypeCache = {}
end
--------------------------------------------------------------------------------
-- SPELL MISS CONSTANTS (v2.31+)
--------------------------------------------------------------------------------
-- MissInfo enum values from SPELL_MISS_SELF/OTHER events
API.MISS_INFO = {
NONE = 0, MISS = 1, RESIST = 2, DODGE = 3, PARRY = 4, BLOCK = 5,
EVADE = 6, IMMUNE = 7, IMMUNE2 = 8, DEFLECT = 9, ABSORB = 10, REFLECT = 11,
}
--------------------------------------------------------------------------------
-- SPELL POWER QUERY (v2.31+)
--------------------------------------------------------------------------------
-- Get spell power for all 7 damage schools (v2.31+)
-- mode: optional mode parameter passed to GetSpellPower
-- Returns: physical, holy, fire, nature, frost, shadow, arcane (or nil if unavailable)
function API.GetSpellPower(mode)
if not API.features.hasGetSpellPower or not _G.GetSpellPower then
return nil, nil, nil, nil, nil, nil, nil
end
return _G.GetSpellPower(mode)
end
-- Expose API globally for other addons
_G.CleveRoidsNampowerAPI = API
+1 -1
View File
@@ -346,7 +346,7 @@ Scans enemies and soft-casts without changing your target. Requires UnitXP_SP3.
| `mainhand` / `mh` | Main-hand swing (lastswing only) |
| `hit` | Successful hit (not miss/dodge/parry) |
**Stat Types** (for `[stat]`): `str`, `agi`, `stam`, `int`, `spi`, `ap`, `rap`, `healing`, `armor`, `defense`, `arcane_power`, `fire_power`, `frost_power`, `nature_power`, `shadow_power`, `arcane_res`, `fire_res`, `frost_res`, `nature_res`, `shadow_res`
**Stat Types** (for `[stat]`): `str`, `agi`, `stam`, `int`, `spi`, `ap`, `rap`, `healing`, `spell_power` (highest across all schools), `arcane_power`, `fire_power`, `frost_power`, `nature_power`, `shadow_power`, `armor`, `defense`, `arcane_res`, `fire_res`, `frost_res`, `nature_res`, `shadow_res`
**Aura Capacity:** Player: 32 buffs, 16 debuffs. NPCs: 16 debuff slots + 32 overflow = 48 total.
+371 -3
View File
@@ -4511,8 +4511,9 @@ ev:SetScript("OnEvent", function()
-- Schedule immunity verification after a short delay
-- This gives combat log time to report the miss reason (immune/reflect/evade)
-- Optimization: Use shorter delay with Nampower v2.26+ since AURA_CAST events are faster
if isOurs and targetName then
if isOurs and targetName and not CleveRoids.usingSpellMissEvents then
-- Delay: 150ms with Nampower v2.26+ (AURA_CAST confirms faster), 300ms fallback
-- Skipped when SPELL_MISS events handle immunity detection directly (v2.31+)
local verifyDelay = lib.hasUnitDiedEvent and 0.15 or 0.3
local verifyFrame = CreateFrame("Frame")
@@ -4760,6 +4761,7 @@ ev:SetScript("OnEvent", function()
local spellId = arg3
local stacks = arg4 or 1
local auraSlot = arg6 -- v2.30+: raw 0-based aura slot index (stable, no shifting)
local state = arg7 -- v2.32+: 0=added, 1=removed, 2=modified (stack increase)
if not guid or not slot or not spellId then return end
guid = CleveRoids.NormalizeGUID(guid)
@@ -4770,6 +4772,16 @@ ev:SetScript("OnEvent", function()
local _, playerGUID = UnitExists("player")
local isOurs = lib.ownDebuffs[guid] and lib.ownDebuffs[guid][spellName] ~= nil
-- v2.32+: state == 2 means stack increase - update stacks on existing entry
if state == 2 and isOurs then
lib.ownDebuffs[guid][spellName].stacks = stacks
lib.ownDebuffs[guid][spellName].slot = slot
if auraSlot then
lib.ownDebuffs[guid][spellName].auraSlot = auraSlot
end
return
end
-- Update slot info in ownDebuffs if this is our debuff
if isOurs then
lib.ownDebuffs[guid][spellName].slot = slot
@@ -4813,13 +4825,24 @@ ev:SetScript("OnEvent", function()
local guid = arg1
local slot = arg2
local spellId = arg3
local auraSlot = arg6 -- v2.30+: raw 0-based aura slot index (stable, no shifting)
local stacks = arg4 -- v2.32+: current stack count after decrement
local auraSlot = arg6 -- v2.30+: raw 0-based aura slot index (stable, no shifting)
local state = arg7 -- v2.32+: 0=added, 1=removed, 2=modified (stack decrease)
if not guid or not slot then return end
guid = CleveRoids.NormalizeGUID(guid)
local spellName = spellId and SpellInfo and SpellInfo(spellId)
-- v2.32+: state == 2 means stack decrease - update stacks, don't remove
if state == 2 and stacks and stacks > 0 then
if spellName and lib.ownDebuffs[guid] and lib.ownDebuffs[guid][spellName] then
lib.ownDebuffs[guid][spellName].stacks = stacks
end
return
end
-- Full removal (state == 1, or nil for pre-v2.32)
-- Remove from ownDebuffs if present
if spellName and lib.ownDebuffs[guid] and lib.ownDebuffs[guid][spellName] then
lib.ownDebuffs[guid][spellName] = nil
@@ -8188,6 +8211,313 @@ function CleveRoids.ProcessAutoAttackEvent(isPlayerAttacker, attackerGuid, targe
end
end
-- NAMPOWER v2.31+ SPELL_MISS EVENT HANDLERS
-- Provides direct miss type information (immune, dodge, resist, etc.) with spellId + targetGuid,
-- eliminating the need for combat log text parsing and time-delayed correlation.
-- MissInfo constants (local copies for performance)
local MISSINFO_MISS = 1
local MISSINFO_RESIST = 2
local MISSINFO_DODGE = 3
local MISSINFO_PARRY = 4
local MISSINFO_BLOCK = 5
local MISSINFO_EVADE = 6
local MISSINFO_IMMUNE = 7
local MISSINFO_IMMUNE2 = 8
local MISSINFO_REFLECT = 11
-- Track if we're using SPELL_MISS events (set during initialization)
CleveRoids.usingSpellMissEvents = false
-- Process SPELL_MISS_SELF: player's spell missed/was resisted/immune/etc.
-- Parameters: spellId, targetGuid, missInfo, [unused]
local function ProcessSpellMissSelf(spellId, targetGuid, missInfo)
if not spellId or not targetGuid or not missInfo then return end
-- Resolve target name from GUID cache or current target
local targetName = lib.guidToName[targetGuid]
if not targetName then
local _, currentTargetGUID = UnitExists("target")
if CleveRoids.NormalizeGUID(currentTargetGUID) == CleveRoids.NormalizeGUID(targetGuid) then
targetName = UnitName("target")
if targetName then
lib.guidToName[targetGuid] = targetName
end
end
end
-- Resolve spell name
local spellName = SpellInfo and SpellInfo(spellId)
local baseName = spellName and string.gsub(spellName, "%s*%(.-%)%s*$", "") or nil
-- ========================================================================
-- IMMUNE / IMMUNE2 (missInfo 7/8)
-- ========================================================================
if missInfo == MISSINFO_IMMUNE or missInfo == MISSINFO_IMMUNE2 then
if CleveRoids.debug then
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cffff6600[SPELL_MISS]|r IMMUNE - %s on %s (spellId=%d, missInfo=%d)",
spellName or "?", targetName or "?", spellId, missInfo)
)
end
if targetName and spellName then
-- Cancel any pending delayed verification immediately
CancelPendingVerification(targetName, spellName)
-- Skip split CC spells (physical damage + resistable CC)
if SPLIT_CC_SPELLS[spellId] or (baseName and SPLIT_CC_SPELL_NAMES[baseName]) then
if CleveRoids.debug then
CleveRoids.Print("|cff00aaff[SPELL_MISS Split CC Skip]|r " .. spellName .. " CC immune on " .. targetName)
end
return
end
-- Skip if target has temporary immunity buff (Divine Shield, etc.)
if UnitExists("target") and UnitName("target") == targetName then
local immunityBuff = HasImmunityGrantingBuff("target")
if immunityBuff then
if CleveRoids.debug then
CleveRoids.Print("|cff00aaff[SPELL_MISS Temp Skip]|r " .. targetName .. " has " .. immunityBuff)
end
return
end
-- Check for conditional buff-based immunity
local buffs = GetUnitBuffs("target")
if buffs and next(buffs) then
local buffCount = 0
local singleBuff = nil
for buff, _ in pairs(buffs) do
buffCount = buffCount + 1
singleBuff = buff
if buffCount > 1 then
singleBuff = nil
break
end
end
if singleBuff then
-- Check CC immunity first
local ccType = GetSpellCCType(spellId)
if ccType then
RecordCCImmunity(targetName, ccType, singleBuff, spellName)
else
RecordImmunity(targetName, spellName, singleBuff, spellId)
end
return
end
end
end
-- No buff detected - record as permanent immunity
local ccType = GetSpellCCType(spellId)
if ccType then
RecordCCImmunity(targetName, ccType, nil, spellName)
else
RecordImmunity(targetName, spellName, nil, spellId)
end
end
-- Populate backward-compat tables for SPELL_GO correlation
if targetName and spellName then
lib.recentCombatLogReasons[targetName] = lib.recentCombatLogReasons[targetName] or {}
lib.recentCombatLogReasons[targetName][spellName] = {
time = GetTime(),
reason = "immune",
}
end
if targetGuid and spellName then
lib.recentMisses[targetGuid] = lib.recentMisses[targetGuid] or {}
lib.recentMisses[targetGuid][spellName] = {
time = GetTime(),
spellId = spellId,
targetGuid = targetGuid,
targetName = targetName,
reason = "immune",
}
end
return
end
-- ========================================================================
-- REFLECT (missInfo 11)
-- ========================================================================
if missInfo == MISSINFO_REFLECT then
if CleveRoids.debug then
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cffff00ff[SPELL_MISS]|r REFLECT - %s reflected by %s",
spellName or "?", targetName or "?")
)
end
if targetName and spellName then
lib.recentReflects = lib.recentReflects or {}
lib.recentReflects[targetName] = lib.recentReflects[targetName] or {}
lib.recentReflects[targetName][spellName] = {
time = GetTime(),
spellId = spellId,
}
lib.recentCombatLogReasons[targetName] = lib.recentCombatLogReasons[targetName] or {}
lib.recentCombatLogReasons[targetName][spellName] = {
time = GetTime(),
reason = "reflect",
}
end
return
end
-- ========================================================================
-- EVADE (missInfo 6)
-- ========================================================================
if missInfo == MISSINFO_EVADE then
if CleveRoids.debug then
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cffaaaaaa[SPELL_MISS]|r EVADE - %s evaded by %s (not immunity)",
spellName or "?", targetName or "?")
)
end
if targetName and spellName then
lib.recentCombatLogReasons[targetName] = lib.recentCombatLogReasons[targetName] or {}
lib.recentCombatLogReasons[targetName][spellName] = {
time = GetTime(),
reason = "evade",
}
end
return
end
-- ========================================================================
-- DODGE (missInfo 3) - Overpower proc for yellow abilities
-- ========================================================================
if missInfo == MISSINFO_DODGE then
if CleveRoids.debug then
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cff00ff00[SPELL_MISS]|r DODGE - %s dodged by %s - Overpower proc",
spellName or "?", targetName or "?")
)
end
-- Overpower proc (enemy dodged our yellow ability)
if CleveRoids.reactiveSpells and CleveRoids.reactiveSpells["Overpower"] then
CleveRoids.SetReactiveProc("Overpower", 4.0, targetGuid)
end
-- Update LastSwing tracking
if CleveRoids.LastSwing then
CleveRoids.LastSwing.timestamp = GetTime()
CleveRoids.LastSwing.hitInfo = HITINFO_MISS
CleveRoids.LastSwing.victimState = VICTIMSTATE_DODGE
CleveRoids.LastSwing.damage = 0
CleveRoids.LastSwing.blockedAmount = 0
CleveRoids.LastSwing.absorbAmount = 0
CleveRoids.LastSwing.resistAmount = 0
CleveRoids.LastSwing.targetGuid = targetGuid
end
CleveRoids.QueueActionUpdate()
return
end
-- ========================================================================
-- RESIST (missInfo 2) - Full resist tracking
-- ========================================================================
if missInfo == MISSINFO_RESIST then
if CleveRoids.debug then
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cffff9900[SPELL_MISS]|r RESIST - %s fully resisted by %s",
spellName or "?", targetName or "?")
)
end
CleveRoids.SetResistState("full", targetGuid)
return
end
-- ========================================================================
-- PARRY (missInfo 4) - Populate combat log reasons for completeness
-- ========================================================================
if missInfo == MISSINFO_PARRY then
if CleveRoids.debug then
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cff8888ff[SPELL_MISS]|r PARRY - %s parried by %s",
spellName or "?", targetName or "?")
)
end
-- Update LastSwing tracking for parried yellow abilities
if CleveRoids.LastSwing then
CleveRoids.LastSwing.timestamp = GetTime()
CleveRoids.LastSwing.hitInfo = HITINFO_MISS
CleveRoids.LastSwing.victimState = VICTIMSTATE_PARRY
CleveRoids.LastSwing.damage = 0
CleveRoids.LastSwing.blockedAmount = 0
CleveRoids.LastSwing.absorbAmount = 0
CleveRoids.LastSwing.resistAmount = 0
CleveRoids.LastSwing.targetGuid = targetGuid
end
CleveRoids.QueueActionUpdate()
return
end
end
-- Process SPELL_MISS_OTHER: another unit's spell missed (player might be the target)
-- Parameters: spellId, casterGuid, targetGuid, missInfo
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")
if not playerGUID or targetGuid ~= playerGUID then return end
-- DODGE: Enemy spell dodged by player → Revenge proc
if missInfo == MISSINFO_DODGE then
if CleveRoids.reactiveSpells and CleveRoids.reactiveSpells["Revenge"] then
CleveRoids.SetReactiveProc("Revenge", 4.0, nil)
CleveRoids.QueueActionUpdate()
if CleveRoids.debug then
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00[SPELL_MISS]|r Revenge proc - player dodged enemy spell")
end
end
end
-- PARRY: Enemy spell parried by player → Riposte + Revenge proc
if missInfo == MISSINFO_PARRY then
if CleveRoids.reactiveSpells and CleveRoids.reactiveSpells["Riposte"] then
CleveRoids.SetReactiveProc("Riposte", 4.0, casterGuid)
CleveRoids.QueueActionUpdate()
if CleveRoids.debug then
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00[SPELL_MISS]|r Riposte proc - player parried enemy spell")
end
end
if CleveRoids.reactiveSpells and CleveRoids.reactiveSpells["Revenge"] then
CleveRoids.SetReactiveProc("Revenge", 4.0, nil)
CleveRoids.QueueActionUpdate()
if CleveRoids.debug then
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00[SPELL_MISS]|r Revenge proc - player parried enemy spell")
end
end
end
-- BLOCK: Enemy spell blocked by player → Revenge proc
if missInfo == MISSINFO_BLOCK then
if CleveRoids.reactiveSpells and CleveRoids.reactiveSpells["Revenge"] then
CleveRoids.SetReactiveProc("Revenge", 4.0, nil)
CleveRoids.QueueActionUpdate()
if CleveRoids.debug then
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00[SPELL_MISS]|r Revenge proc - player blocked enemy spell")
end
end
end
end
-- Register combat log event for reactive proc tracking (FALLBACK)
-- CHAT_MSG_SPELL_SELF_DAMAGE is needed for yellow ability dodge/parry/block detection
-- (e.g., "Your Mortal Strike was dodged by Target.") - only auto-attack dodges come through COMBAT_SELF_MISSES
@@ -8221,6 +8551,22 @@ if GetNampowerVersion then
end
end
-- Check for Nampower v2.31+ SPELL_MISS events (direct miss type information)
local hasSpellMissEvents = false
if GetNampowerVersion then
local npMajor, npMinor = GetNampowerVersion()
if npMajor > 2 or (npMajor == 2 and npMinor >= 31) then
hasSpellMissEvents = true
CleveRoids.usingSpellMissEvents = true
reactiveFrame:RegisterEvent("SPELL_MISS_SELF")
reactiveFrame:RegisterEvent("SPELL_MISS_OTHER")
if CleveRoids.debug then
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00[Nampower]|r Using SPELL_MISS events for immunity/reactive detection (v2.31+)")
end
end
end
-- Check for Nampower v2.25+ SPELL_GO events (LastSwing tracking for yellow attack misses)
-- NOTE: SPELL_GO only provides hit/miss counts, not miss types, so Overpower still needs
-- combat log text parsing. SPELL_GO is used as a gate to avoid parsing combat log on every
@@ -8261,6 +8607,21 @@ else
end
reactiveFrame:SetScript("OnEvent", function()
-- ========================================================================
-- NAMPOWER v2.31+ SPELL_MISS EVENTS (preferred when available)
-- ========================================================================
if event == "SPELL_MISS_SELF" then
-- Player's spell missed: arg1=spellId, arg2=targetGuid, arg3=missInfo
ProcessSpellMissSelf(arg1, arg2, arg3)
return
end
if event == "SPELL_MISS_OTHER" then
-- Other unit's spell missed: arg1=spellId, arg2=casterGuid, arg3=targetGuid, arg4=missInfo
ProcessSpellMissOther(arg1, arg2, arg3, arg4)
return
end
-- ========================================================================
-- NAMPOWER v2.24+ AUTO_ATTACK EVENTS (preferred when available)
-- ========================================================================
@@ -8358,7 +8719,10 @@ reactiveFrame:SetScript("OnEvent", function()
-- Gate combat log parsing: set miss window so CHAT_MSG_SPELL_SELF_DAMAGE
-- only calls ParseReactiveCombatLog when we know a yellow miss occurred.
-- This avoids parsing every successful spell hit message.
CleveRoids._yellowMissWindow = GetTime()
-- Skip when SPELL_MISS handles dodge/parry detection directly (v2.31+)
if not hasSpellMissEvents then
CleveRoids._yellowMissWindow = GetTime()
end
if CleveRoids.debug then
local spellName = SpellInfo and SpellInfo(spellId) or tostring(spellId)
@@ -8380,6 +8744,10 @@ reactiveFrame:SetScript("OnEvent", function()
-- distinguish dodge (Overpower) from parry/block/resist. But we only parse when SPELL_GO
-- told us a miss occurred, avoiding overhead on every successful spell hit.
if event == "CHAT_MSG_SPELL_SELF_DAMAGE" then
-- With SPELL_MISS events (v2.31+): dodge/parry/block handled directly, skip combat log
if hasSpellMissEvents then
return
end
-- Yellow ability avoidance (e.g., "Your Mortal Strike was dodged by Target")
-- With SPELL_GO events: only parse when a yellow miss was recently detected.
-- Without SPELL_GO: always parse (fallback for older Nampower).