mirror of
https://github.com/brues-code/SuperCleveRoidMacros.git
synced 2026-09-16 03:38:00 +00:00
fix some blizzard commands missed in the macro checker, updates for nampower 2.33
This commit is contained in:
+204
-102
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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).
|
||||
|
||||
Reference in New Issue
Block a user