Files
Nanami-Plates/CombatLog.lua
2026-03-20 10:20:05 +08:00

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