294 lines
11 KiB
Lua
294 lines
11 KiB
Lua
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
|