This commit is contained in:
Jrc13245
2026-02-07 22:00:10 -05:00
parent 4c00ade625
commit edeec89e7f
3 changed files with 164 additions and 66 deletions
+86 -25
View File
@@ -835,6 +835,23 @@ function CleveRoids.FindAllCasterAuraByName(targetGuid, searchName)
if not targetData then return nil, nil end
local now = GetTime()
-- Direct spell ID lookup: O(1) hash table access instead of iterating all entries
local searchID = tonumber(searchName)
if searchID then
local auraData = targetData[searchID]
if auraData and auraData.start and auraData.duration then
local remaining = auraData.duration + auraData.start - now
if remaining > 0 then
return remaining, auraData.casterGuid
else
targetData[searchID] = nil
end
end
return nil, nil
end
-- Name-based search (existing behavior)
local searchLower = string.lower(searchName)
for spellId, auraData in pairs(targetData) do
@@ -2997,7 +3014,9 @@ function CleveRoids.ValidateAura(unit, args, isbuff)
end
-- PERFORMANCE: Cache lowercased search name to avoid repeated string.lower calls
local searchName = args.name and _string_lower(args.name)
-- Support spell ID matching: [mybuff:17941] matches by spell ID instead of name
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
@@ -3020,12 +3039,20 @@ function CleveRoids.ValidateAura(unit, args, isbuff)
if not texture then break end
if current_spellID and searchName then
-- PERFORMANCE: Use cached lowercase spell name lookup
local lowerName = GetLowercaseSpellName(current_spellID)
if lowerName and lowerName == searchName then
found = true
break
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
end
end
end
@@ -3033,7 +3060,7 @@ function CleveRoids.ValidateAura(unit, args, isbuff)
end
-- Overflow handling: when searching DEBUFFS on non-players, also scan BUFFS
if not isbuff and not isPlayer and not found and searchName then
if not isbuff and not isPlayer and not found and (searchID or searchName) then
i = 1
while true do
local texture
@@ -3044,11 +3071,18 @@ function CleveRoids.ValidateAura(unit, args, isbuff)
if not texture then break end
if current_spellID then
-- PERFORMANCE: Use cached lowercase spell name lookup
local lowerName = GetLowercaseSpellName(current_spellID)
if lowerName and lowerName == searchName then
found = true
break
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
@@ -3154,12 +3188,15 @@ function CleveRoids.ValidateUnitDebuff(unit, args)
-- Convert underscores to spaces for matching (e.g., "Thunder_Clap" -> "Thunder Clap")
args.name = string.gsub(args.name, "_", " ")
-- Support spell ID matching: [debuff:9904] matches by spell ID instead of name
local searchID = tonumber(args.name)
local found = false
local texture, stacks, spellID, remaining
local i
-- PERFORMANCE: For non-SuperWoW, early return if no texture registered
if not CleveRoids.hasSuperwow and not CleveRoids.auraTextures[args.name] then
if not searchID and not CleveRoids.hasSuperwow and not CleveRoids.auraTextures[args.name] then
return false
end
@@ -3176,7 +3213,13 @@ function CleveRoids.ValidateUnitDebuff(unit, args)
if not guid then return false end
-- PERFORMANCE: Use cached spell name -> ID mapping instead of iterating every call
local matchingSpellIDs = GetSpellIDsForName(args.name) or {}
-- For spell ID input, use the ID directly instead of name->ID lookup
local matchingSpellIDs
if searchID then
matchingSpellIDs = {searchID}
else
matchingSpellIDs = GetSpellIDsForName(args.name) or {}
end
-- Check tracking table for ANY rank of this spell: Did player cast this? Is timer valid?
if matchingSpellIDs and table.getn(matchingSpellIDs) > 0 then
@@ -3272,9 +3315,14 @@ function CleveRoids.ValidateUnitDebuff(unit, args)
if not tex then break end
if debuffSpellID then
-- PERFORMANCE: Use cached spell name lookup
local baseName, fullName = GetSpellNames(debuffSpellID)
if baseName and (baseName == args.name or fullName == args.name) 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
@@ -3298,9 +3346,14 @@ function CleveRoids.ValidateUnitDebuff(unit, args)
if not tex then break end
if buffSpellID then
-- PERFORMANCE: Use cached spell name lookup
local baseName, fullName = GetSpellNames(buffSpellID)
if baseName and (baseName == args.name or fullName == args.name) 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
@@ -3324,8 +3377,12 @@ function CleveRoids.ValidateUnitDebuff(unit, args)
texture, stacks, spellID, remaining = CleveRoids.GetPlayerAura(i, false)
if not texture then break end
if CleveRoids.hasSuperwow then
-- PERFORMANCE: Use cached spell name lookup
if searchID then
if spellID == searchID then
found = true
break
end
elseif CleveRoids.hasSuperwow then
local baseName, fullName = GetSpellNames(spellID)
if baseName and (baseName == args.name or fullName == args.name) then
found = true
@@ -3345,8 +3402,12 @@ function CleveRoids.ValidateUnitDebuff(unit, args)
texture, stacks, spellID, remaining = CleveRoids.GetPlayerAura(i, true)
if not texture then break end
if CleveRoids.hasSuperwow then
-- PERFORMANCE: Use cached spell name lookup
if searchID then
if spellID == searchID then
found = true
break
end
elseif CleveRoids.hasSuperwow then
local baseName, fullName = GetSpellNames(spellID)
if baseName and (baseName == args.name or fullName == args.name) then
found = true
+9
View File
@@ -5558,6 +5558,15 @@ function CleveRoids.Frame:SPELL_CAST_EVENT()
-- Track pending cast for SPELL_GO correlation (reactive ability detection)
-- Keyed by spellId so concurrent casts don't overwrite each other
CleveRoids.pendingCasts = CleveRoids.pendingCasts or {}
-- Clean up consumed entries older than 5 seconds (lightweight, runs per-cast)
local cleanupTime = GetTime() - 5
for id, entry in pairs(CleveRoids.pendingCasts) do
if entry.consumed and entry.consumedAt and entry.consumedAt < cleanupTime then
CleveRoids.pendingCasts[id] = nil
end
end
CleveRoids.pendingCasts[spellId] = {
castType = castType,
targetGuid = targetGuid,
+69 -41
View File
@@ -8003,11 +8003,11 @@ function CleveRoids.ParseReactiveCombatLog()
local _, targetGUID = UnitExists("target")
-- Check each reactive ability's trigger patterns
-- NOTE: Overpower (enemy_dodge) ALWAYS uses combat log text parsing, even when SPELL_GO
-- events are available. SPELL_GO only provides hit/miss COUNTS, not miss TYPES, so it
-- cannot distinguish dodge (Overpower proc) from parry/block/resist (no Overpower proc).
for spellName, config in pairs(reactivePatterns) do
-- Skip outgoing dodge patterns when SPELL_GO_SELF handles yellow attack miss detection
if CleveRoids.usingSpellGoEvents and config.type == "enemy_dodge" then
-- Overpower handled by SPELL_GO_SELF (hit/miss binary, no text parsing needed)
elseif CleveRoids.reactiveSpells and CleveRoids.reactiveSpells[spellName] then
if CleveRoids.reactiveSpells and CleveRoids.reactiveSpells[spellName] then
for _, pattern in ipairs(config.patterns) do
if strfind(message, pattern) then
-- Found a trigger event (works in any stance)
@@ -8016,7 +8016,6 @@ function CleveRoids.ParseReactiveCombatLog()
CleveRoids.SetReactiveProc(spellName, duration, guid)
-- For Overpower (enemy dodge), also update LastSwing so [lastswing:dodge] works
-- (Only reached when SPELL_GO is NOT available - combat log fallback)
if config.type == "enemy_dodge" and CleveRoids.LastSwing then
CleveRoids.LastSwing.timestamp = GetTime()
CleveRoids.LastSwing.victimState = VICTIMSTATE_DODGE_REACTIVE
@@ -8198,28 +8197,36 @@ if GetNampowerVersion then
end
end
-- Check for Nampower v2.25+ SPELL_GO events (replaces combat log for yellow attack miss detection)
-- 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
-- successful spell hit - only parse when SPELL_GO reports a miss.
local hasSpellGoEvents = false
if GetNampowerVersion then
local npMajor, npMinor = GetNampowerVersion()
if npMajor > 2 or (npMajor == 2 and npMinor >= 25) then
hasSpellGoEvents = true
CleveRoids.usingSpellGoEvents = true
reactiveFrame:RegisterEvent("SPELL_GO_SELF")
if CleveRoids.debug then
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00[Nampower]|r Using SPELL_GO events for yellow attack miss detection (v2.25+)")
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00[Nampower]|r Using SPELL_GO events for yellow attack LastSwing tracking (v2.25+)")
end
end
end
-- Register combat log events for reactive abilities
-- When SPELL_GO + AUTO_ATTACK are both available, outgoing dodges are handled natively;
-- combat log only needed for incoming avoidance (Riposte/Revenge from enemy abilities)
if hasAutoAttackEvents and hasSpellGoEvents then
-- Outgoing dodge detection (Overpower) uses combat log text parsing because SPELL_GO only
-- provides hit/miss counts, not miss types (dodge vs parry vs block vs resist). However,
-- with SPELL_GO available, CHAT_MSG_SPELL_SELF_DAMAGE parsing is gated behind a miss window
-- so we skip parsing on every successful spell hit (see OnEvent handler below).
-- AUTO_ATTACK events handle white swing avoidance; combat log handles yellow ability dodges.
if hasAutoAttackEvents then
-- Incoming avoidance (Riposte/Revenge)
reactiveFrame:RegisterEvent("CHAT_MSG_COMBAT_CREATURE_VS_SELF_MISSES")
reactiveFrame:RegisterEvent("CHAT_MSG_COMBAT_CREATURE_VS_SELF_HITS")
-- Outgoing dodge detection (Overpower) - gated by SPELL_GO miss window when available
reactiveFrame:RegisterEvent("CHAT_MSG_COMBAT_SELF_MISSES")
reactiveFrame:RegisterEvent("CHAT_MSG_SPELL_SELF_DAMAGE")
else
-- Full combat log fallback
reactiveFrame:RegisterEvent("RAW_COMBATLOG")
@@ -8263,10 +8270,11 @@ reactiveFrame:SetScript("OnEvent", function()
end
-- ========================================================================
-- NAMPOWER v2.25+ SPELL_GO EVENTS (yellow attack hit/miss detection)
-- NAMPOWER v2.25+ SPELL_GO EVENTS (yellow attack LastSwing tracking)
-- ========================================================================
-- Two-step: SPELL_CAST_EVENT (success=1) → SPELL_GO_SELF (hit/miss outcome)
-- Replaces combat log text parsing for outgoing yellow ability misses.
-- Updates LastSwing state for yellow ability misses. Overpower detection still uses
-- combat log text parsing (SPELL_GO only has hit/miss counts, not types like dodge).
-- SPELL_GO_SELF params: itemId, spellId, casterGuid, targetGuid, castFlags, numTargetsHit, numTargetsMissed
if event == "SPELL_GO_SELF" then
local spellId = arg2
@@ -8276,12 +8284,16 @@ reactiveFrame:SetScript("OnEvent", function()
-- Step 1: Validate against SPELL_CAST_EVENT (confirms player-initiated, successful cast)
local pending = CleveRoids.pendingCasts and CleveRoids.pendingCasts[spellId]
if not pending then
return -- No matching SPELL_CAST_EVENT with success=1, skip
if not pending or pending.consumed then
return -- No matching SPELL_CAST_EVENT with success=1, or already processed
end
-- Clear the pending cast (consumed)
CleveRoids.pendingCasts[spellId] = nil
-- Mark pending cast as consumed with timestamp instead of deleting immediately.
-- Other handlers (libdebuff SPELL_GO, AURA_CAST, ComboPointTracker) on different
-- frames may still need to read this data. Stale entries are cleaned up on next
-- SPELL_CAST_EVENT for the same spellId (overwritten) or expire naturally.
pending.consumed = true
pending.consumedAt = GetTime()
-- Skip channels and targeting spells (not melee/ranged attacks)
-- CastType: NORMAL=1, NON_GCD=2, ON_SWING=3, CHANNEL=4, TARGETING=5, TARGETING_NON_GCD=6
@@ -8289,7 +8301,11 @@ reactiveFrame:SetScript("OnEvent", function()
return
end
-- Step 2: Check hit/miss outcome - binary check covers dodge, parry, resist, etc.
-- Step 2: Check hit/miss outcome for LastSwing tracking
-- NOTE: SPELL_GO only provides hit/miss COUNTS, not miss TYPES (dodge vs parry vs
-- block vs resist). Overpower detection is handled by combat log text parsing which
-- can distinguish "dodged" from other avoidance types. We only update LastSwing here
-- as a generic miss indicator (combat log overwrites with specific type if dodge).
if numMissed >= 1 and numHit == 0 then
-- Use current target GUID if SPELL_GO targetGuid is empty
local procTarget = targetGuid
@@ -8301,22 +8317,9 @@ reactiveFrame:SetScript("OnEvent", function()
end
end
-- Overpower: enemy avoided our yellow attack
if CleveRoids.reactiveSpells and CleveRoids.reactiveSpells["Overpower"] then
CleveRoids.SetReactiveProc("Overpower", 4.0, procTarget)
CleveRoids.QueueActionUpdate()
if CleveRoids.debug then
local spellName = SpellInfo and SpellInfo(spellId) or tostring(spellId)
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cff00ff00[SPELL_GO]|r Overpower proc - %s missed (hit=%d, miss=%d, castType=%s)",
spellName, numHit, numMissed, tostring(pending.castType))
)
end
end
-- Update LastSwing for yellow miss (victimState unknown from SPELL_GO, use UNAFFECTED)
-- Don't overwrite AUTO_ATTACK data from the same frame (on-swing abilities fire both)
-- Combat log handler will overwrite with specific type (dodge/parry/etc.) if available
if CleveRoids.LastSwing and CleveRoids.LastSwing.timestamp ~= GetTime() then
CleveRoids.LastSwing.timestamp = GetTime()
CleveRoids.LastSwing.hitInfo = HITINFO_MISS
@@ -8327,21 +8330,46 @@ reactiveFrame:SetScript("OnEvent", function()
CleveRoids.LastSwing.resistAmount = 0
CleveRoids.LastSwing.targetGuid = procTarget
end
-- 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()
if CleveRoids.debug then
local spellName = SpellInfo and SpellInfo(spellId) or tostring(spellId)
DEFAULT_CHAT_FRAME:AddMessage(
string.format("|cff00ff00[SPELL_GO]|r Yellow miss detected - %s (hit=%d, miss=%d, castType=%s) - deferring to combat log for type",
spellName, numHit, numMissed, tostring(pending.castType))
)
end
end
return
end
-- ========================================================================
-- COMBAT LOG FALLBACK FOR REACTIVE ABILITIES
-- COMBAT LOG TEXT PARSING FOR REACTIVE ABILITIES
-- ========================================================================
-- Only used for incoming avoidance (Riposte/Revenge) from enemy abilities.
-- Outgoing dodges handled by SPELL_GO_SELF (v2.25+) or AUTO_ATTACK (white swings).
if event == "RAW_COMBATLOG" or
event == "CHAT_MSG_COMBAT_SELF_MISSES" or
event == "CHAT_MSG_SPELL_SELF_DAMAGE" or
event == "CHAT_MSG_COMBAT_CREATURE_VS_SELF_MISSES" or
event == "CHAT_MSG_COMBAT_CREATURE_VS_SELF_HITS" then
-- Handles: Overpower (outgoing dodge), Riposte (incoming parry), Revenge (incoming avoid).
-- SPELL_GO provides hit/miss counts but not types, so combat log is still needed to
-- 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
-- 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).
if hasSpellGoEvents then
local missWindow = CleveRoids._yellowMissWindow
if not missWindow or (GetTime() - missWindow) > 1.0 then
return -- No recent yellow miss from SPELL_GO, skip parsing
end
end
CleveRoids.ParseReactiveCombatLog()
elseif event == "RAW_COMBATLOG" or
event == "CHAT_MSG_COMBAT_SELF_MISSES" or
event == "CHAT_MSG_COMBAT_CREATURE_VS_SELF_MISSES" or
event == "CHAT_MSG_COMBAT_CREATURE_VS_SELF_HITS" then
CleveRoids.ParseReactiveCombatLog()
end
end)