NanamiPlates_CombatLog = {} local NP = NanamiPlates local SpellDB = NanamiPlates_SpellDB local Auras = NanamiPlates_Auras local string_gsub = string.gsub local string_gfind = string.gfind local string_find = string.find local string_sub = string.sub local GetTime = GetTime local UnitExists = UnitExists local UnitName = UnitName local UnitGUID = UnitGUID local superwow_active = NP.superwow_active local castTracker local recentMeleeHits local function InitReferences() castTracker = NP.castTracker recentMeleeHits = NP.recentMeleeHits end local function cmatch(str, pattern) if not str or not pattern then return nil end local pat = string_gsub(pattern, "%%%d?%$?s", "(.+)") pat = string_gsub(pat, "%%%d?%$?d", "(%d+)") for a, b, c, d in string_gfind(str, pat) do return a, b, c, d end return nil end local castIcons = { ["Fireball"] = "Interface\\Icons\\Spell_Fire_FlameBolt", ["Frostbolt"] = "Interface\\Icons\\Spell_Frost_FrostBolt02", ["Shadow Bolt"] = "Interface\\Icons\\Spell_Shadow_ShadowBolt", ["Greater Heal"] = "Interface\\Icons\\Spell_Holy_GreaterHeal", ["Flash Heal"] = "Interface\\Icons\\Spell_Holy_FlashHeal", ["Lightning Bolt"] = "Interface\\Icons\\Spell_Nature_Lightning", ["Chain Lightning"] = "Interface\\Icons\\Spell_Nature_ChainLightning", ["Healing Wave"] = "Interface\\Icons\\Spell_Nature_MagicImmunity", ["Fear"] = "Interface\\Icons\\Spell_Shadow_Possession", ["Polymorph"] = "Interface\\Icons\\Spell_Nature_Polymorph", ["Smite"] = "Interface\\Icons\\Spell_Holy_HolySmite", ["Mind Blast"] = "Interface\\Icons\\Spell_Shadow_UnholyFrenzy", ["Holy Light"] = "Interface\\Icons\\Spell_Holy_HolyLight", ["Starfire"] = "Interface\\Icons\\Spell_Arcane_StarFire", ["Wrath"] = "Interface\\Icons\\Spell_Nature_AbolishMagic", ["Entangling Roots"] = "Interface\\Icons\\Spell_Nature_StrangleVines", ["Moonfire"] = "Interface\\Icons\\Spell_Nature_StarFall", ["Regrowth"] = "Interface\\Icons\\Spell_Nature_ResistNature", ["Rejuvenation"] = "Interface\\Icons\\Spell_Nature_Rejuvenation", } local function ParseCastStart(msg) if not msg then return end if not castTracker then castTracker = NP.castTracker end if not castTracker then return end local unit, spell = nil, nil for u, s in string_gfind(msg, "(.+) begins to cast (.+)%.") do unit, spell = u, s end if not unit then for u, s in string_gfind(msg, "(.+) begins to perform (.+)%.") do unit, spell = u, s end end if unit and spell then if not castTracker[unit] then castTracker[unit] = {} end table.insert(castTracker[unit], { spell = spell, startTime = GetTime(), duration = 2000, icon = castIcons[spell], }) end local interruptedUnit = nil for u in string_gfind(msg, "(.+)'s .+ is interrupted%.") do interruptedUnit = u end if not interruptedUnit then for u in string_gfind(msg, "(.+)'s .+ fails%.") do interruptedUnit = u end end if interruptedUnit and castTracker[interruptedUnit] then table.remove(castTracker[interruptedUnit], 1) end end local function ParseAttackHit(msg) if not msg then return end local attacker, victim = nil, nil if string_sub(msg, 1, 8) == "You hit " then local forPos = string_find(msg, " for ") if forPos then victim = string_sub(msg, 9, forPos - 1) attacker = "You" end elseif string_sub(msg, 1, 9) == "You crit " then local forPos = string_find(msg, " for ") if forPos then victim = string_sub(msg, 10, forPos - 1) attacker = "You" end end if attacker == "You" and victim and Auras then Auras:SealHandler(attacker, victim) end if not recentMeleeHits then recentMeleeHits = NP.recentMeleeHits end if not recentMeleeHits then return end if attacker == "You" and victim then recentMeleeHits[victim] = GetTime() if superwow_active and UnitExists("target") and UnitName("target") == victim then local guid = UnitGUID and UnitGUID("target") if guid then recentMeleeHits[guid] = GetTime() end end end if not victim then for a, v in string_gfind(msg, "(.+) hits (.-) for %d+%.") do attacker, victim = a, v break end end if not victim then for a, v in string_gfind(msg, "(.+) crits (.-) for %d+%.") do attacker, victim = a, v break end end if attacker == "You" and victim and recentMeleeHits then recentMeleeHits[victim] = GetTime() if superwow_active and UnitExists("target") and UnitName("target") == victim then local guid = UnitGUID and UnitGUID("target") if guid then recentMeleeHits[guid] = GetTime() end end end if attacker and victim and Auras then Auras:SealHandler(attacker, victim) end end -- Handle spell-related combat log events function NanamiPlates_CombatLog.HandleSpellEvent(evnt, msg) if not msg then return end InitReferences() if evnt == "CHAT_MSG_SPELL_AURA_GONE_OTHER" or evnt == "CHAT_MSG_SPELL_AURA_GONE_SELF" then local target, effect for t, e in string_gfind(msg, "(.+) is no longer afflicted by (.+)%.") do target, effect = t, e end if not target then for e in string_gfind(msg, "(.+) fades from .+%.") do effect = e end end if effect and SpellDB and SpellDB.objects then for unit, levels in pairs(SpellDB.objects) do for lvl, effects in pairs(levels) do if effects[effect] then effects[effect] = nil end end end end return end if evnt == "CHAT_MSG_SPELL_FAILED_LOCALPLAYER" then if SpellDB then SpellDB:RemovePending() end return end -- Check for pending spell resolution if SpellDB and SpellDB.pending and SpellDB.pending[3] then local effect = SpellDB.pending[3] -- Check if spell was resisted/missed/etc local removePatterns = NP.REMOVE_PENDING_PATTERNS if removePatterns then for _, pattern in ipairs(removePatterns) do if cmatch(msg, pattern) then SpellDB:RemovePending() return end end end -- Check for spell hit/application if string_find(msg, effect) then local affTarget = nil for t in string_gfind(msg, "(.+) is afflicted by " .. effect) do affTarget = t end if affTarget or string_find(msg, "Your " .. effect) then SpellDB:PersistPending(effect) if SpellDB.OWNER_BOUND_DEBUFFS and SpellDB.OWNER_BOUND_DEBUFFS[effect] then local targetName = affTarget or (UnitExists("target") and UnitName("target")) if targetName then SpellDB:TrackOwnerBoundDebuff(targetName, effect) if superwow_active and UnitExists("target") and UnitName("target") == targetName then local guid = UnitGUID and UnitGUID("target") if guid then SpellDB:TrackOwnerBoundDebuff(guid, effect) end end end end end end end -- Fallback: track debuffs via "afflicted by" messages using recentCasts. -- Handles instant-cast DOTs (e.g. Corruption) whose pending was overwritten -- by a subsequent cast before the combat log confirmed application. do local affTarget, affEffect for t, e in string_gfind(msg, "(.+) is afflicted by (.+)%.") do affTarget, affEffect = t, e end if affEffect and SpellDB and SpellDB.recentCasts then if SpellDB.WARLOCK_CURSES and SpellDB.WARLOCK_CURSES[affEffect] then local hasMalediction = SpellDB.HasMalediction and SpellDB:HasMalediction() for otherCurse, _ in pairs(SpellDB.WARLOCK_CURSES) do if otherCurse ~= affEffect then local otherRecent = SpellDB.recentCasts[otherCurse] local thisRecent = SpellDB.recentCasts[affEffect] if otherRecent and thisRecent and otherRecent.time > thisRecent.time then if hasMalediction and SpellDB:CanCursesCoexist(affEffect, otherCurse) then -- skip: these two curses can coexist else return end end end end end local recent = SpellDB.recentCasts[affEffect] if recent and (GetTime() - recent.time) < 4 then SpellDB:RefreshEffect(affTarget, 0, affEffect, recent.duration, true) if superwow_active and UnitExists("target") and UnitName("target") == affTarget then local guid = UnitGUID and UnitGUID("target") if guid then SpellDB:RefreshEffect(guid, 0, affEffect, recent.duration, true) end end if SpellDB.OWNER_BOUND_DEBUFFS and SpellDB.OWNER_BOUND_DEBUFFS[affEffect] then SpellDB:TrackOwnerBoundDebuff(affTarget, affEffect, recent.duration) if superwow_active and UnitExists("target") and UnitName("target") == affTarget then local guid = UnitGUID and UnitGUID("target") if guid then SpellDB:TrackOwnerBoundDebuff(guid, affEffect, recent.duration) end end end if Auras and Auras.timers then Auras.timers[affTarget .. "_" .. affEffect] = nil end end end end -- Parse cast starts for non-SuperWoW fallback if NP.SPELL_DAMAGE_EVENTS and NP.SPELL_DAMAGE_EVENTS[evnt] then ParseCastStart(msg) end -- Holy Strike handler for Paladin if evnt == "CHAT_MSG_SPELL_SELF_DAMAGE" and Auras then Auras:HolyStrikeHandler(msg) end end function NanamiPlates_CombatLog.HandleCombatEvent(evnt, msg) if not msg then return end InitReferences() ParseAttackHit(msg) end NanamiPlates_CombatLog.cmatch = cmatch NanamiPlates_CombatLog.castIcons = castIcons NanamiPlates_CombatLog.ParseCastStart = ParseCastStart NanamiPlates_CombatLog.ParseAttackHit = ParseAttackHit NanamiPlates_CombatLog.InitReferences = InitReferences NanamiPlates.CombatLog = NanamiPlates_CombatLog