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

1403 lines
51 KiB
Lua

local NP = NanamiPlates
local Settings = NP.Settings
local Colors = NP.Colors
local THREAT_COLORS = NP.THREAT_COLORS
local SpellDB = NanamiPlates_SpellDB
local Scanner = NanamiPlates_Scanner
local Healthbar = NanamiPlates_Healthbar
local pairs = pairs
local ipairs = ipairs
local type = type
local tostring = tostring
local tonumber = tonumber
local string_find = string.find
local string_lower = string.lower
local string_format = string.format
local string_gfind = string.gfind
local string_gsub = string.gsub
local string_sub = string.sub
local math_floor = math.floor
local GetTime = GetTime
local UnitExists = UnitExists
local UnitName = UnitName
local UnitLevel = UnitLevel
local UnitClass = UnitClass
local UnitIsUnit = UnitIsUnit
local UnitCanAttack = UnitCanAttack
local UnitIsFriend = UnitIsFriend
local UnitIsEnemy = UnitIsEnemy
local UnitDebuff = UnitDebuff
local UnitGUID = UnitGUID
local CreateFrame = CreateFrame
local registry = NP.registry
local superwow_active = NP.superwow_active
local playerClass = NP.playerClass
local platecount = 0
local playerInCombat = false
local REGION_ORDER = { "border", "glow", "name", "level", "levelicon", "raidicon" }
local DEBUFF_UPDATE_INTERVAL = 0.1
-- Quest mob detection: scans the quest log for kill objectives
-- and checks pfQuest tooltip data for item drop mobs
local questMobCache = {}
local questCacheTimer = 0
local QUEST_CACHE_INTERVAL = 2
local questMonsterPattern
do
local raw = QUEST_MONSTERS_KILLED or "%s slain: %d/%d"
raw = string.gsub(raw, "([%(%)%.%+%-%?%[%]%^%$%%])", "%%%1")
raw = string.gsub(raw, "%%%%s", "(.+)")
raw = string.gsub(raw, "%%%%d", "(%%d+)")
questMonsterPattern = raw
end
local function RebuildQuestMobCache()
for k in pairs(questMobCache) do questMobCache[k] = nil end
local numEntries = GetNumQuestLogEntries()
if not numEntries or numEntries == 0 then return end
for qid = 1, numEntries do
local title, level, _, header = GetQuestLogTitle(qid)
if title and not header then
local objectives = GetNumQuestLeaderBoards(qid)
if objectives then
for i = 1, objectives do
local text, objType, done = GetQuestLogLeaderBoard(i, qid)
if text and objType == "monster" and not done then
local _, _, mobName, current, needed = string_find(text, questMonsterPattern)
if not mobName then
_, _, mobName, current, needed = string_find(text, "^(.+):%s*(%d+)/(%d+)")
end
if mobName and current and needed then
if not questMobCache[mobName] or not questMobCache[mobName].done then
questMobCache[mobName] = {
current = tonumber(current),
needed = tonumber(needed),
quest = title,
}
end
end
end
end
end
end
end
end
local function GetQuestMobInfo(plateName)
if questMobCache[plateName] then
return questMobCache[plateName]
end
if pfMap and pfMap.tooltips and pfMap.tooltips[plateName] then
if next(pfMap.tooltips[plateName]) then
return { tooltip = true }
end
end
return nil
end
local function GetRankNumber(rankStr)
if not rankStr then return 0 end
for num in string_gfind(rankStr, "(%d+)") do
return tonumber(num) or 0
end
return 0
end
local function ParseSpellName(spellString)
if not spellString then return nil, 0 end
for name, rank in string_gfind(spellString, "^(.+)%(Rank (%d+)%)$") do
return name, tonumber(rank) or 0
end
return spellString, 0
end
-- Direct spell tracking: immediately records effect on target at cast time.
-- Even if the spell is resisted, the debuff icon won't appear on the nameplate,
-- so no incorrect timer will be displayed.
local function ClearSingleCurseTracking(targetName, guid, curseName)
if not SpellDB or not curseName then return end
if SpellDB.objects and SpellDB.objects[targetName] then
for lvl, effects in pairs(SpellDB.objects[targetName]) do
if effects[curseName] then effects[curseName] = nil end
end
end
if guid and SpellDB.objects and SpellDB.objects[guid] then
for lvl, effects in pairs(SpellDB.objects[guid]) do
if effects[curseName] then effects[curseName] = nil end
end
end
if NanamiPlates_Auras and NanamiPlates_Auras.timers then
NanamiPlates_Auras.timers[targetName .. "_" .. curseName] = nil
if guid then NanamiPlates_Auras.timers[guid .. "_" .. curseName] = nil end
end
if SpellDB.ownerBoundCache then
if SpellDB.ownerBoundCache[targetName] then
SpellDB.ownerBoundCache[targetName][curseName] = nil
end
if guid and SpellDB.ownerBoundCache[guid] then
SpellDB.ownerBoundCache[guid][curseName] = nil
end
end
if SpellDB.recentCasts then
SpellDB.recentCasts[curseName] = nil
end
end
local function ClearCurseTracking(targetName, guid, exceptCurse)
if not SpellDB or not SpellDB.WARLOCK_CURSES then return end
for curse, _ in pairs(SpellDB.WARLOCK_CURSES) do
if curse ~= exceptCurse then
if SpellDB.objects and SpellDB.objects[targetName] then
for lvl, effects in pairs(SpellDB.objects[targetName]) do
if effects[curse] then effects[curse] = nil end
end
end
if guid and SpellDB.objects and SpellDB.objects[guid] then
for lvl, effects in pairs(SpellDB.objects[guid]) do
if effects[curse] then effects[curse] = nil end
end
end
if NanamiPlates_Auras and NanamiPlates_Auras.timers then
NanamiPlates_Auras.timers[targetName .. "_" .. curse] = nil
if guid then NanamiPlates_Auras.timers[guid .. "_" .. curse] = nil end
end
if SpellDB.ownerBoundCache then
if SpellDB.ownerBoundCache[targetName] then
SpellDB.ownerBoundCache[targetName][curse] = nil
end
if guid and SpellDB.ownerBoundCache[guid] then
SpellDB.ownerBoundCache[guid][curse] = nil
end
end
if SpellDB.recentCasts then
SpellDB.recentCasts[curse] = nil
end
end
end
end
local function TrackSpellCast(spellName, duration)
if not SpellDB or not spellName then return end
if not UnitExists("target") then return end
-- Translate localized spell name to English for DB consistency
local englishName = spellName
if SpellDB.localeMap and SpellDB.localeMap[spellName] then
englishName = SpellDB.localeMap[spellName]
elseif SpellDB.learnedLocale and SpellDB.learnedLocale[spellName] then
englishName = SpellDB.learnedLocale[spellName]
end
-- Record this cast for locale learning
if SpellDB.DEBUFFS and SpellDB.DEBUFFS[englishName] then
SpellDB.lastCastSpell = englishName
else
SpellDB.lastCastSpell = spellName
end
SpellDB.lastCastTime = GetTime()
-- Re-fetch duration with English name if original was localized
if englishName ~= spellName and (not duration or duration <= 0) then
duration = SpellDB:GetDuration(englishName, 0)
end
spellName = englishName
if not duration or duration <= 0 then return end
local targetName = UnitName("target")
local targetLevel = UnitLevel("target") or 0
local guid = UnitGUID and UnitGUID("target")
if SpellDB.WARLOCK_CURSES and SpellDB.WARLOCK_CURSES[spellName] then
if not SpellDB:HasMalediction() then
ClearCurseTracking(targetName, guid, spellName)
else
if spellName == "Curse of Doom" then
ClearCurseTracking(targetName, guid, spellName)
elseif spellName == SpellDB.WARLOCK_AGONY_CURSE then
-- Agony cast: clear only Doom tracking, keep other curses
ClearSingleCurseTracking(targetName, guid, "Curse of Doom")
elseif SpellDB.WARLOCK_LONG_CURSES and SpellDB.WARLOCK_LONG_CURSES[spellName] then
-- Long curse cast: clear other long curses + Doom, keep Agony
for curse, _ in pairs(SpellDB.WARLOCK_CURSES) do
if curse ~= spellName and curse ~= SpellDB.WARLOCK_AGONY_CURSE then
ClearSingleCurseTracking(targetName, guid, curse)
end
end
end
end
end
SpellDB:AddPending(targetName, targetLevel, spellName, duration)
SpellDB:RefreshEffect(targetName, targetLevel, spellName, duration, true)
if guid then
SpellDB:RefreshEffect(guid, targetLevel, spellName, duration, true)
end
if SpellDB.OWNER_BOUND_DEBUFFS and SpellDB.OWNER_BOUND_DEBUFFS[spellName] then
SpellDB:TrackOwnerBoundDebuff(targetName, spellName, duration)
if guid then SpellDB:TrackOwnerBoundDebuff(guid, spellName, duration) end
end
if NanamiPlates_Auras and NanamiPlates_Auras.timers then
NanamiPlates_Auras.timers[targetName .. "_" .. spellName] = nil
if guid then NanamiPlates_Auras.timers[guid .. "_" .. spellName] = nil end
end
-- Malediction auto-apply: casting Recklessness/Shadow/Elements also applies max rank Agony
if SpellDB:HasMalediction() and SpellDB.MALEDICTION_AUTO_AGONY
and SpellDB.MALEDICTION_AUTO_AGONY[spellName] then
local agonyName = SpellDB.WARLOCK_AGONY_CURSE
local agonyDuration = SpellDB:GetDuration(agonyName, 0)
if agonyDuration and agonyDuration > 0 then
SpellDB:RefreshEffect(targetName, targetLevel, agonyName, agonyDuration, true)
if guid then
SpellDB:RefreshEffect(guid, targetLevel, agonyName, agonyDuration, true)
end
if SpellDB.OWNER_BOUND_DEBUFFS and SpellDB.OWNER_BOUND_DEBUFFS[agonyName] then
SpellDB:TrackOwnerBoundDebuff(targetName, agonyName, agonyDuration)
if guid then SpellDB:TrackOwnerBoundDebuff(guid, agonyName, agonyDuration) end
end
if NanamiPlates_Auras and NanamiPlates_Auras.timers then
NanamiPlates_Auras.timers[targetName .. "_" .. agonyName] = nil
if guid then NanamiPlates_Auras.timers[guid .. "_" .. agonyName] = nil end
end
end
end
end
-- Spell cast hooks
local Original_CastSpell = CastSpell
CastSpell = function(spellId, bookType)
if SpellDB and spellId and bookType then
local spellName, rank = GetSpellName(spellId, bookType)
if spellName and UnitExists("target") and UnitCanAttack("player", "target") then
local duration = SpellDB:GetDuration(spellName, rank)
TrackSpellCast(spellName, duration)
end
end
return Original_CastSpell(spellId, bookType)
end
local Original_CastSpellByName = CastSpellByName
CastSpellByName = function(spellString, onSelf)
if SpellDB and spellString then
local spellName, rank = ParseSpellName(spellString)
if spellName and UnitExists("target") and UnitCanAttack("player", "target") then
local duration = SpellDB:GetDuration(spellName, rank)
TrackSpellCast(spellName, duration)
end
end
return Original_CastSpellByName(spellString, onSelf)
end
local Original_UseAction = UseAction
UseAction = function(slot, checkCursor, onSelf)
if SpellDB and slot then
local actionTexture = GetActionTexture(slot)
if GetActionText(slot) == nil and actionTexture ~= nil then
local spellName, rank = SpellDB:ScanAction(slot)
if spellName then
if SpellDB.textureToSpell then
local existing = SpellDB.textureToSpell[actionTexture]
if not existing or not SpellDB.DEBUFFS[existing] then
SpellDB.textureToSpell[actionTexture] = spellName
end
-- If spellName is localized and existing is English, learn mapping
if existing and SpellDB.DEBUFFS[existing] and spellName ~= existing then
if SpellDB.LearnLocale then
SpellDB:LearnLocale(spellName, existing)
end
spellName = existing
end
end
local duration = SpellDB:GetDuration(spellName, rank)
TrackSpellCast(spellName, duration)
end
end
end
return Original_UseAction(slot, checkCursor, onSelf)
end
if SpellDB then SpellDB:InitScanner() end
local function DisableObject(obj)
if not obj then return end
obj:Hide()
obj:SetAlpha(0)
if obj.SetWidth then obj:SetWidth(0.001) end
if obj.SetHeight then obj:SetHeight(0.001) end
end
local BORDER_PAD = 2
local function CreateNanamiBackdrop(frame)
local bgR, bgG, bgB, bgA = NP.GetThemeColor("panelBg", 0.1, 0.06, 0.1, 0.85)
local brR, brG, brB, brA = NP.GetThemeColor("panelBorder", 0.55, 0.30, 0.42, 0.9)
frame:SetBackdrop({
bgFile = "Interface\\Buttons\\WHITE8X8",
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
tile = false, tileSize = 0, edgeSize = 10,
insets = { left = BORDER_PAD, right = BORDER_PAD, top = BORDER_PAD, bottom = BORDER_PAD }
})
frame:SetBackdropColor(bgR, bgG, bgB, bgA)
frame:SetBackdropBorderColor(brR, brG, brB, brA)
end
local ARROW_TEXTURE = "Interface\\AddOns\\Nanami-Plates\\img\\arrow"
local ARROW_TEXCOORDS = {
{0, 0.5, 0, 0.5},
{0.5, 1, 0, 0.5},
{0, 0.5, 0.5, 1},
{0.5, 1, 0.5, 1},
}
NP.ARROW_TEXCOORDS = ARROW_TEXCOORDS
local function SetArrowTexCoords(f)
if not f or not f.tex then return end
local style = Settings.targetArrowStyle or 1
local coords = ARROW_TEXCOORDS[style] or ARROW_TEXCOORDS[1]
if f.side == "RIGHT" then
f.tex:SetTexCoord(coords[2], coords[1], coords[3], coords[4])
else
f.tex:SetTexCoord(coords[1], coords[2], coords[3], coords[4])
end
end
local function UpdateArrowPosition(f, healthBG)
if not f or not healthBG then return end
local offset = Settings.targetArrowOffset or 0
f:ClearAllPoints()
if f.side == "LEFT" then
f:SetPoint("RIGHT", healthBG, "LEFT", -offset, 0)
else
f:SetPoint("LEFT", healthBG, "RIGHT", offset, 0)
end
end
local function CreateArrowIndicator(parent, healthBG, side)
local size = Settings.targetArrowSize or 24
local f = CreateFrame("Frame", nil, parent)
f:SetWidth(size)
f:SetHeight(size)
f:SetFrameLevel(parent:GetFrameLevel() + 10)
f.side = side
f.healthBG = healthBG
UpdateArrowPosition(f, healthBG)
local tex = f:CreateTexture(nil, "OVERLAY")
tex:SetTexture(ARROW_TEXTURE)
tex:SetAllPoints(f)
f.tex = tex
SetArrowTexCoords(f)
f:Hide()
return f
end
function NP.UpdateAllArrows()
local size = Settings.targetArrowSize or 24
for frame, np in pairs(registry) do
if np.targetArrowL then
np.targetArrowL:SetWidth(size)
np.targetArrowL:SetHeight(size)
SetArrowTexCoords(np.targetArrowL)
UpdateArrowPosition(np.targetArrowL, np.targetArrowL.healthBG)
end
if np.targetArrowR then
np.targetArrowR:SetWidth(size)
np.targetArrowR:SetHeight(size)
SetArrowTexCoords(np.targetArrowR)
UpdateArrowPosition(np.targetArrowR, np.targetArrowR.healthBG)
end
end
end
local function HandleNamePlate(frame)
if registry[frame] then return end
platecount = platecount + 1
local plateName = "NanamiPlate" .. platecount
local r1, r2, r3, r4, r5, r6 = frame:GetRegions()
local regions = {r1, r2, r3, r4, r5, r6}
local original = {}
for i, key in ipairs(REGION_ORDER) do
original[key] = regions[i]
end
local children = { frame:GetChildren() }
original.healthbar = children[1]
if children[2] and children[2].GetObjectType and children[2]:GetObjectType() == "StatusBar" then
original.castbar = children[2]
end
original.name = original.name or r3
original.level = original.level or r4
-- Shrink original frame to reduce game engine's anti-overlap stacking
-- Original ~35px, lower = more overlap allowed, higher = more separation
frame:SetHeight(14)
-- Create overlay
local np = CreateFrame("Button", plateName, frame)
np:SetAllPoints(frame)
np:SetFrameLevel(frame:GetFrameLevel() + 1)
np:EnableMouse(false)
local rawStrata = np:GetFrameStrata()
local VALID_STRATA = { BACKGROUND=1, LOW=1, MEDIUM=1, HIGH=1, DIALOG=1, FULLSCREEN=1, FULLSCREEN_DIALOG=1, TOOLTIP=1 }
np._defaultStrata = VALID_STRATA[rawStrata] and rawStrata or "BACKGROUND"
np.original = original
-- Health bar background frame (also contains mana bar)
local healthBG = CreateFrame("Frame", nil, np)
healthBG:SetHeight(Settings.healthbarHeight + BORDER_PAD * 2)
healthBG:SetWidth(Settings.healthbarWidth + BORDER_PAD * 2)
healthBG:SetPoint("CENTER", np, "CENTER", 0, Settings.nameplateYOffset or 0)
CreateNanamiBackdrop(healthBG)
np.healthBG = healthBG
-- Health bar (single anchor + explicit size, so mana can fit below)
local health = CreateFrame("StatusBar", plateName .. "Health", healthBG)
health:SetStatusBarTexture(NP.GetTexture())
health:SetPoint("TOPLEFT", healthBG, "TOPLEFT", BORDER_PAD, -BORDER_PAD)
health:SetWidth(Settings.healthbarWidth)
health:SetHeight(Settings.healthbarHeight)
health:SetMinMaxValues(0, 1)
health:SetValue(1)
np.health = health
local healthBGTex = health:CreateTexture(nil, "BACKGROUND")
healthBGTex:SetAllPoints(health)
healthBGTex:SetTexture(NP.GetTexture())
healthBGTex:SetVertexColor(0.15, 0.15, 0.15, 0.8)
np.healthBGTex = healthBGTex
-- Target highlight: additive StatusBar overlay that brightens only the filled portion
local targetGlow = CreateFrame("StatusBar", nil, healthBG)
targetGlow:SetAllPoints(health)
targetGlow:SetStatusBarTexture(NP.GetTexture())
targetGlow:SetFrameLevel(health:GetFrameLevel() + 1)
targetGlow:SetMinMaxValues(0, 1)
targetGlow:SetValue(1)
targetGlow:SetAlpha(0.35)
targetGlow:Hide()
np.targetGlow = targetGlow
local glowRegions = { targetGlow:GetRegions() }
for _, reg in ipairs(glowRegions) do
if reg and reg.SetBlendMode then reg:SetBlendMode("ADD") end
end
-- Mana/Energy/Rage bar (inside healthBG, below health bar)
local manaBar = CreateFrame("StatusBar", plateName .. "Mana", healthBG)
manaBar:SetStatusBarTexture(NP.GetTexture())
manaBar:SetPoint("TOPLEFT", health, "BOTTOMLEFT", 0, -1)
manaBar:SetWidth(Settings.healthbarWidth)
manaBar:SetHeight(Settings.manabarHeight or 3)
manaBar:SetMinMaxValues(0, 1)
manaBar:SetValue(1)
manaBar:SetStatusBarColor(0, 0, 1, 1)
manaBar:Hide()
np.manaBar = manaBar
local manaBGTex = manaBar:CreateTexture(nil, "BACKGROUND")
manaBGTex:SetAllPoints(manaBar)
manaBGTex:SetTexture(NP.GetTexture())
manaBGTex:SetVertexColor(0.05, 0.05, 0.1, 0.8)
-- Thin separator line between health and mana
local brR, brG, brB = NP.GetThemeColor("panelBorder", 0.55, 0.30, 0.42, 0.9)
local manaSep = healthBG:CreateTexture(nil, "ARTWORK")
manaSep:SetTexture("Interface\\Buttons\\WHITE8X8")
manaSep:SetHeight(1)
manaSep:SetPoint("TOPLEFT", health, "BOTTOMLEFT", 0, 0)
manaSep:SetPoint("TOPRIGHT", health, "BOTTOMRIGHT", 0, 0)
manaSep:SetVertexColor(brR, brG, brB, 0.5)
manaSep:Hide()
np.manaSep = manaSep
-- Health text
local healthText = health:CreateFontString(nil, "OVERLAY")
healthText:SetFont(NP.GetFont(), Settings.healthFontSize, NP.GetFontOutline())
healthText:SetPoint("TOPRIGHT", health, "TOPRIGHT", -2, 0)
healthText:SetPoint("BOTTOMRIGHT", health, "BOTTOMRIGHT", -2, 0)
healthText:SetJustifyV("MIDDLE")
healthText:SetJustifyH("RIGHT")
healthText:SetTextColor(1, 1, 1, 1)
np.healthText = healthText
-- Name text
local name = np:CreateFontString(nil, "OVERLAY")
name:SetFont(NP.GetFont(), Settings.nameFontSize, NP.GetFontOutline())
name:SetPoint("BOTTOM", healthBG, "TOP", 0, 2)
name:SetTextColor(1, 1, 1, 1)
np.name = name
-- Level text (on health bar so it renders above the bar fill)
local level = health:CreateFontString(nil, "OVERLAY")
level:SetFont(NP.GetFont(), Settings.levelFontSize, NP.GetFontOutline())
level:SetPoint("TOPLEFT", health, "TOPLEFT", 2, 0)
level:SetPoint("BOTTOMLEFT", health, "BOTTOMLEFT", 2, 0)
level:SetJustifyV("MIDDLE")
level:SetJustifyH("LEFT")
level:SetTextColor(1, 1, 0.6, 1)
np.level = level
-- Raid icon
local raidIcon = np:CreateTexture(nil, "OVERLAY")
raidIcon:SetTexture("Interface\\TargetingFrame\\UI-RaidTargetingIcons")
raidIcon:SetWidth(16)
raidIcon:SetHeight(16)
raidIcon:SetPoint("LEFT", healthBG, "RIGHT", 3, 0)
raidIcon:Hide()
np.raidIcon = raidIcon
-- Castbar background (anchors dynamically below healthBG)
local castbarBG = CreateFrame("Frame", nil, np)
castbarBG:SetHeight(Settings.castbarHeight + 2)
castbarBG:SetWidth(Settings.healthbarWidth + 2)
castbarBG:SetPoint("TOP", healthBG, "BOTTOM", 0, -1)
CreateNanamiBackdrop(castbarBG)
castbarBG:Hide()
np.castbarBG = castbarBG
-- Castbar
local castbar = CreateFrame("StatusBar", plateName .. "Castbar", castbarBG)
castbar:SetStatusBarTexture(NP.GetTexture())
castbar:SetPoint("TOPLEFT", castbarBG, "TOPLEFT", 1, -1)
castbar:SetPoint("BOTTOMRIGHT", castbarBG, "BOTTOMRIGHT", -1, 1)
castbar:SetMinMaxValues(0, 1)
castbar:SetValue(0)
castbar:SetStatusBarColor(Settings.castbarColor[1], Settings.castbarColor[2], Settings.castbarColor[3], Settings.castbarColor[4])
np.castbar = castbar
local castbarBGTex = castbar:CreateTexture(nil, "BACKGROUND")
castbarBGTex:SetAllPoints(castbar)
castbarBGTex:SetTexture(NP.GetTexture())
castbarBGTex:SetVertexColor(0.1, 0.1, 0.1, 0.8)
local castbarText = castbar:CreateFontString(nil, "OVERLAY")
castbarText:SetFont(NP.GetFont(), Settings.healthFontSize - 1, NP.GetFontOutline())
castbarText:SetPoint("CENTER", castbar, "CENTER", 0, 0)
castbarText:SetTextColor(1, 1, 1, 1)
np.castbarText = castbarText
local castbarTimer = castbar:CreateFontString(nil, "OVERLAY")
castbarTimer:SetFont(NP.GetFont(), Settings.healthFontSize - 1, NP.GetFontOutline())
castbarTimer:SetPoint("RIGHT", castbar, "RIGHT", -2, 0)
castbarTimer:SetTextColor(1, 1, 1, 1)
np.castbarTimer = castbarTimer
-- Castbar icon (anchored to castbar, not healthbar, to avoid chevron overlap)
local castbarIcon = castbarBG:CreateTexture(nil, "OVERLAY")
castbarIcon:SetWidth(Settings.castbarHeight + 8)
castbarIcon:SetHeight(Settings.castbarHeight + 8)
castbarIcon:SetPoint("RIGHT", castbarBG, "LEFT", -2, 0)
castbarIcon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
castbarIcon:Hide()
np.castbarIcon = castbarIcon
-- Create aura frames (filled in by Auras module)
if NanamiPlates_Auras then
NanamiPlates_Auras:CreateDebuffFrames(np)
end
-- Create combo point frames (filled in by ComboPoints module)
if NanamiPlates_ComboPoints then
NanamiPlates_ComboPoints:CreateComboPointFrames(np)
end
-- Quest mob indicator icon (yellow "!" to the left of the name)
local questIcon = np:CreateTexture(nil, "OVERLAY")
questIcon:SetTexture("Interface\\GossipFrame\\AvailableQuestIcon")
questIcon:SetWidth(12)
questIcon:SetHeight(12)
questIcon:SetPoint("RIGHT", name, "LEFT", -1, 0)
questIcon:Hide()
np.questIcon = questIcon
-- Quest progress text (e.g. "3/8" to the right of the name)
local questText = np:CreateFontString(nil, "OVERLAY")
questText:SetFont(NP.GetFont(), Settings.nameFontSize - 1, NP.GetFontOutline())
questText:SetPoint("LEFT", name, "RIGHT", 2, 0)
questText:SetTextColor(1, 0.82, 0, 1)
questText:Hide()
np.questText = questText
-- Target arrow indicators >> [healthbar] <<
np.targetArrowL = CreateArrowIndicator(np, healthBG, "LEFT")
np.targetArrowR = CreateArrowIndicator(np, healthBG, "RIGHT")
-- Hide original elements thoroughly (keep healthbar color intact for unit type detection)
for _, key in ipairs(REGION_ORDER) do
if original[key] then DisableObject(original[key]) end
end
if original.healthbar then
local hbRegions = { original.healthbar:GetRegions() }
for _, reg in ipairs(hbRegions) do
if reg and reg.Hide then reg:Hide() end
if reg and reg.SetAlpha then reg:SetAlpha(0) end
end
original.healthbar:ClearAllPoints()
original.healthbar:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 0, 0)
original.healthbar:SetWidth(0.001)
original.healthbar:SetHeight(0.001)
end
if original.castbar then
original.castbar:SetAlpha(0.001)
local cbRegions = { original.castbar:GetRegions() }
for _, reg in ipairs(cbRegions) do
if reg and reg.SetAlpha then reg:SetAlpha(0) end
end
end
-- Tracking state
np.isTarget = false
np.lastDebuffUpdate = 0
np.unitstr = nil
np.plateName = nil
np._lastFriendly = nil
np._lastManaShowing = nil
np._lastFriendly_resize = nil
frame.nameplate = np
registry[frame] = np
-- OnShow: reset and re-hide original elements
local oldOnShow = frame:GetScript("OnShow")
frame:SetScript("OnShow", function()
if oldOnShow then oldOnShow() end
this:SetHeight(14)
local nameplate = this.nameplate
if nameplate then
Healthbar.ResetCache(nameplate)
nameplate.isTarget = false
nameplate._isTargetStrata = nil
nameplate.lastDebuffUpdate = 0
nameplate.unitstr = nil
nameplate._lastFriendly = nil
nameplate._lastManaShowing = nil
nameplate._lastFriendly_resize = nil
nameplate._lastAlpha = nil
if nameplate._defaultStrata then
nameplate:SetFrameStrata(nameplate._defaultStrata)
end
nameplate.castbarBG:Hide()
nameplate.castbarIcon:Hide()
nameplate.manaBar:Hide()
if nameplate.manaSep then nameplate.manaSep:Hide() end
if nameplate.targetArrowL then nameplate.targetArrowL:Hide() end
if nameplate.targetArrowR then nameplate.targetArrowR:Hide() end
if nameplate.comboPoints then
for i = 1, 5 do
if nameplate.comboPoints[i] then nameplate.comboPoints[i]:Hide() end
end
end
if nameplate.questIcon then nameplate.questIcon:Hide() end
if nameplate.questText then nameplate.questText:Hide() end
-- Re-hide original elements (Blizzard resets them on show)
local orig = nameplate.original
for _, key in ipairs(REGION_ORDER) do
if orig[key] then
orig[key]:Hide()
if orig[key].SetAlpha then orig[key]:SetAlpha(0) end
end
end
if orig.healthbar then
orig.healthbar:SetWidth(0.001)
orig.healthbar:SetHeight(0.001)
local hbRegions = { orig.healthbar:GetRegions() }
for _, reg in ipairs(hbRegions) do
if reg and reg.Hide then reg:Hide() end
end
end
if orig.castbar then
orig.castbar:SetAlpha(0.001)
local cbRegions = { orig.castbar:GetRegions() }
for _, reg in ipairs(cbRegions) do
if reg and reg.SetAlpha then reg:SetAlpha(0) end
end
end
if Healthbar.ShouldSkipNameplate(this, nameplate, nameplate.original, Settings) then
nameplate:Hide()
else
nameplate:Show()
end
end
end)
-- OnHide: cleanup
local oldOnHide = frame:GetScript("OnHide")
frame:SetScript("OnHide", function()
if oldOnHide then oldOnHide() end
local nameplate = this.nameplate
if nameplate then
nameplate.castbarBG:Hide()
nameplate.castbarIcon:Hide()
nameplate.manaBar:Hide()
if nameplate.manaSep then nameplate.manaSep:Hide() end
if nameplate.targetArrowL then nameplate.targetArrowL:Hide() end
if nameplate.targetArrowR then nameplate.targetArrowR:Hide() end
if nameplate.questIcon then nameplate.questIcon:Hide() end
if nameplate.questText then nameplate.questText:Hide() end
end
end)
end
local function ShortenNumber(n)
if n >= 1000000 then
return string_format("%.1fM", n / 1000000)
elseif n >= 10000 then
return string_format("%.1fK", n / 1000)
else
return tostring(n)
end
end
local function FormatHealthText(current, max, format)
if not format or format == 0 then return "" end
if max == 0 then return "" end
local pct = math_floor(current / max * 100 + 0.5)
if format == 1 then return pct .. "%" end
if format == 2 then return ShortenNumber(current) end
if format == 3 then return ShortenNumber(current) .. " (" .. pct .. "%)" end
if format == 4 then return ShortenNumber(current) .. "/" .. ShortenNumber(max) end
if format == 5 then return ShortenNumber(current) .. "/" .. ShortenNumber(max) .. " " .. pct .. "%" end
return pct .. "%"
end
local function GetLevelDiffColor(unitLevel)
local playerLevel = UnitLevel("player") or 60
local diff = unitLevel - playerLevel
if diff >= 5 then return 1, 0, 0
elseif diff >= 3 then return 1, 0.5, 0
elseif diff >= -2 then return 1, 1, 0
elseif diff >= -4 - math_floor(playerLevel / 10) then return 0.25, 0.75, 0.25
else return 0.5, 0.5, 0.5
end
end
local function HideOriginalElements(original)
for _, key in ipairs(REGION_ORDER) do
local obj = original[key]
if obj and obj.IsShown and obj:IsShown() then
obj:Hide()
if obj.SetAlpha then obj:SetAlpha(0) end
end
end
if original.castbar and original.castbar:GetAlpha() > 0.01 then
original.castbar:SetAlpha(0.001)
end
end
local function UpdateNamePlate(frame)
local np = frame.nameplate
if not np then return end
if not np:IsShown() then return end
local original = np.original
if not original or not original.healthbar then return end
-- Keep original frame small to reduce anti-overlap stacking jumps
if frame:GetHeight() > 16 then
frame:SetHeight(14)
end
-- Continuously enforce hiding of original elements
HideOriginalElements(original)
-- Unit name
local plateName = ""
if original.name and original.name.GetText then
plateName = original.name:GetText() or ""
end
np.plateName = plateName
np.name:SetText(plateName)
-- SuperWoW unit detection
local unitstr = nil
local hasValidGUID = false
if superwow_active and frame.GetName then
unitstr = frame:GetName(1)
if unitstr and UnitExists(unitstr) then
hasValidGUID = true
else
unitstr = nil
end
end
np.unitstr = unitstr
-- Unit type detection
local isHostile, isNeutral, isFriendly, origR, origG, origB = Healthbar.DetectUnitType(np, original)
Healthbar.CheckUnitChange(np, plateName, isNeutral)
-- Override neutral→hostile when the mob has entered combat (e.g. after player attacks it)
if (isNeutral or np.wasNeutral) and unitstr and hasValidGUID
and UnitAffectingCombat and UnitAffectingCombat(unitstr) then
isHostile = true
isNeutral = false
isFriendly = false
np.wasNeutral = false
end
local isFriendlyStyle = isFriendly
-- Tapped detection: mob tagged by another player outside our group (unlootable)
local isTapped = false
if unitstr and hasValidGUID and UnitIsTapped then
if UnitIsTapped(unitstr) and (not UnitIsTappedByPlayer or not UnitIsTappedByPlayer(unitstr)) then
isTapped = true
end
end
-- Color fallback: hostile/neutral/friendly all have B≈0; tapped grey has B>0
if not isTapped and not isHostile and not isNeutral and origB > 0.1 and origR > 0.15 then
isTapped = true
end
if isTapped then
isFriendlyStyle = false
end
-- Health values
local hp = original.healthbar:GetValue() or 0
local _, hpmax = original.healthbar:GetMinMaxValues()
hpmax = hpmax or 1
if hpmax == 0 then hpmax = 1 end
np.health:SetMinMaxValues(0, hpmax)
np.health:SetValue(hp)
if np.targetGlow then
np.targetGlow:SetMinMaxValues(0, hpmax)
np.targetGlow:SetValue(hp)
end
-- Dimensions (only update when style changes)
local hHeight, hWidth, hFontSize, lFontSize, nFontSize, hTextFormat
if isFriendlyStyle then
hHeight = Settings.friendHealthbarHeight
hWidth = Settings.friendHealthbarWidth
hFontSize = Settings.friendHealthFontSize
lFontSize = Settings.friendLevelFontSize
nFontSize = Settings.friendNameFontSize
hTextFormat = Settings.friendHealthTextFormat
else
hHeight = Settings.healthbarHeight
hWidth = Settings.healthbarWidth
hFontSize = Settings.healthFontSize
lFontSize = Settings.levelFontSize
nFontSize = Settings.nameFontSize
hTextFormat = Settings.healthTextFormat
end
if np._lastFriendly ~= isFriendlyStyle then
np._lastFriendly = isFriendlyStyle
np.health:SetWidth(hWidth)
np.health:SetHeight(hHeight)
np.name:SetFont(NP.GetFont(), nFontSize, NP.GetFontOutline())
np.level:SetFont(NP.GetFont(), lFontSize, NP.GetFontOutline())
np.healthText:SetFont(NP.GetFont(), hFontSize, NP.GetFontOutline())
end
-- Health text
np.healthText:SetText(FormatHealthText(hp, hpmax, hTextFormat))
-- Level text and classification
local unitLevel = nil
local isBoss = original.levelicon and original.levelicon.IsShown and original.levelicon:IsShown()
local levelText = (original.level and original.level.GetText) and original.level:GetText() or nil
if levelText then
unitLevel = tonumber(levelText)
end
local classification = "normal"
if isBoss then
classification = "worldboss"
elseif isTarget and UnitExists("target") and UnitClassification then
classification = UnitClassification("target") or "normal"
elseif unitstr and UnitExists(unitstr) and UnitClassification then
classification = UnitClassification(unitstr) or "normal"
elseif original.border and original.border.GetVertexColor then
local br, bg, bb = original.border:GetVertexColor()
if br and bg and bb and br > 0.6 and bg > 0.5 and bb < 0.3 then
classification = "elite"
end
end
if classification == "worldboss" then
np.level:SetText("??")
np.level:SetTextColor(1, 0, 0)
elseif levelText then
if classification == "rareelite" then
np.level:SetText(levelText .. "+")
np.level:SetTextColor(0.8, 0.8, 0.8)
elseif classification == "elite" then
np.level:SetText(levelText .. "+")
if unitLevel then
local lr, lg, lb = GetLevelDiffColor(unitLevel)
np.level:SetTextColor(lr, lg, lb)
else
np.level:SetTextColor(1, 1, 0.6)
end
elseif classification == "rare" then
np.level:SetText(levelText)
np.level:SetTextColor(0.8, 0.8, 0.8)
else
np.level:SetText(levelText)
if unitLevel then
local lr, lg, lb = GetLevelDiffColor(unitLevel)
np.level:SetTextColor(lr, lg, lb)
else
np.level:SetTextColor(1, 1, 0.6)
end
end
end
-- Raid icon
if original.raidicon and original.raidicon.IsShown and original.raidicon:IsShown() then
local ux, uy = original.raidicon:GetTexCoord()
np.raidIcon:SetTexCoord(ux, ux + 0.25, uy, uy + 0.25)
np.raidIcon:Show()
else
np.raidIcon:Hide()
end
-- Quest mob indicator
if Settings.showQuestIcon and not isFriendlyStyle then
local questInfo = GetQuestMobInfo(plateName)
if questInfo then
if not np.questIcon:IsShown() then np.questIcon:Show() end
if questInfo.current and questInfo.needed then
np.questText:SetText(questInfo.current .. "/" .. questInfo.needed)
if not np.questText:IsShown() then np.questText:Show() end
else
if np.questText:IsShown() then np.questText:Hide() end
end
else
if np.questIcon:IsShown() then np.questIcon:Hide() end
if np.questText:IsShown() then np.questText:Hide() end
end
else
if np.questIcon:IsShown() then np.questIcon:Hide() end
if np.questText:IsShown() then np.questText:Hide() end
end
-- Health bar color
local barR, barG, barB = origR, origG, origB
if isTapped then
barR, barG, barB = Colors.tapped[1], Colors.tapped[2], Colors.tapped[3]
elseif isHostile then
barR, barG, barB = Colors.hostile[1], Colors.hostile[2], Colors.hostile[3]
elseif isNeutral or Healthbar.WasNeutral(np) then
barR, barG, barB = Colors.neutral[1], Colors.neutral[2], Colors.neutral[3]
elseif isFriendly then
barR, barG, barB = Colors.friendly[1], Colors.friendly[2], Colors.friendly[3]
else
barR, barG, barB = Colors.hostile[1], Colors.hostile[2], Colors.hostile[3]
end
-- Class colors for players
if unitstr and hasValidGUID and UnitIsPlayer(unitstr) then
local _, unitClass = UnitClass(unitstr)
if unitClass and Colors.class[unitClass] then
barR = Colors.class[unitClass][1]
barG = Colors.class[unitClass][2]
barB = Colors.class[unitClass][3]
end
end
-- Threat coloring (overrides basic colors for hostile mobs)
if isHostile and not isTapped then
local threatColor = nil
local mobGUID = unitstr
if NanamiPlates_Threat then
local hasData, playerHasAggro, otherName, otherPct = NanamiPlates_Threat.GetTWTankModeThreat(mobGUID, plateName)
if hasData then
local role = NP.playerRole or "DPS"
if role == "TANK" then
if playerHasAggro then
threatColor = THREAT_COLORS.TANK.AGGRO
elseif otherPct and otherPct > 70 then
threatColor = THREAT_COLORS.TANK.LOSING_AGGRO
elseif otherName and NP.TANK_CLASSES and NP.GetPlayerClassByName then
local otherClass = NP.GetPlayerClassByName(otherName)
if otherClass and NP.TANK_CLASSES[otherClass] then
threatColor = THREAT_COLORS.TANK.OTHER_TANK
else
threatColor = THREAT_COLORS.TANK.NO_AGGRO
end
else
threatColor = THREAT_COLORS.TANK.NO_AGGRO
end
else
if playerHasAggro then
threatColor = THREAT_COLORS.DPS.AGGRO
elseif otherPct and otherPct > 70 then
threatColor = THREAT_COLORS.DPS.HIGH_THREAT
else
threatColor = THREAT_COLORS.DPS.NO_AGGRO
end
end
end
end
-- Stun override
if unitstr and hasValidGUID then
for _, stunEffect in ipairs(NP.STUN_EFFECTS) do
local data = SpellDB and SpellDB:FindEffectData(unitstr, 0, stunEffect)
if data and data.start and data.duration then
if data.start + data.duration > GetTime() then
threatColor = THREAT_COLORS.STUN
break
end
end
end
end
if threatColor then
barR, barG, barB = threatColor[1], threatColor[2], threatColor[3]
end
end
np.health:SetStatusBarColor(barR, barG, barB, 1)
-- Sync target glow with final bar color (must happen here, not in UpdateTarget,
-- because the bar color may have changed this frame due to combat/threat)
if np.targetGlow and np.targetGlow:IsShown() then
np.targetGlow:SetStatusBarColor(barR, barG, barB, 1)
local lum = 0.299 * barR + 0.587 * barG + 0.114 * barB
local glowAlpha = 0.75
if lum > 0.5 then
glowAlpha = 0.75 * math.max(0.15, (1.0 - lum) * 2)
end
np.targetGlow:SetAlpha(glowAlpha)
end
-- Target detection
local isTarget = false
if UnitExists("target") then
if unitstr and hasValidGUID then
isTarget = UnitIsUnit(unitstr, "target")
else
isTarget = (UnitName("target") == plateName) and (not UnitIsFriend("player", "target") == not isFriendly)
end
end
np.isTarget = isTarget
-- Target nameplate always on top via FrameStrata
if isTarget and not np._isTargetStrata then
np._isTargetStrata = true
np:SetFrameStrata("HIGH")
elseif not isTarget and np._isTargetStrata then
np._isTargetStrata = false
np:SetFrameStrata(np._defaultStrata)
end
-- Target highlight
if NanamiPlates_Target then
NanamiPlates_Target.UpdateTarget(np, isTarget)
end
-- Non-target alpha (only update on change)
local wantAlpha = (UnitExists("target") and not isTarget) and (Settings.nonTargetAlpha or 0.6) or 1
if np._lastAlpha ~= wantAlpha then
np._lastAlpha = wantAlpha
np:SetAlpha(wantAlpha)
end
-- Mana/Energy/Rage bar update (inside healthBG)
local manaShowing = false
local manaH = Settings.manabarHeight or 3
if Settings.showManaBar and unitstr and hasValidGUID and UnitMana and UnitManaMax then
local manaMax = UnitManaMax(unitstr)
if manaMax and manaMax > 0 then
local manaCur = UnitMana(unitstr) or 0
local powerType = UnitPowerType and UnitPowerType(unitstr) or 0
local powerColor = Colors.power[powerType] or Colors.power[0]
if not np.manaBar:IsShown() then
np.manaBar:SetWidth(hWidth)
np.manaBar:SetHeight(manaH)
np.manaBar:Show()
np.manaSep:Show()
end
np.manaBar:SetMinMaxValues(0, manaMax)
np.manaBar:SetValue(manaCur)
np.manaBar:SetStatusBarColor(powerColor[1], powerColor[2], powerColor[3], 1)
manaShowing = true
end
end
if not manaShowing and np.manaBar:IsShown() then
np.manaBar:Hide()
np.manaSep:Hide()
end
-- Resize healthBG only when mana visibility or style changes
local needResize = (np._lastManaShowing ~= manaShowing) or (np._lastFriendly_resize ~= isFriendlyStyle)
if needResize then
np._lastManaShowing = manaShowing
np._lastFriendly_resize = isFriendlyStyle
if manaShowing then
np.healthBG:SetHeight(hHeight + manaH + 1 + BORDER_PAD * 2)
else
np.healthBG:SetHeight(hHeight + BORDER_PAD * 2)
end
np.healthBG:SetWidth(hWidth + BORDER_PAD * 2)
local cbHeight = isFriendlyStyle and (Settings.friendCastbarHeight or 6) or (Settings.castbarHeight or 10)
np.castbarBG:SetWidth(hWidth + 2)
np.castbarBG:SetHeight(cbHeight + 2)
end
local castShowing = false
local casting = nil
local castNow = GetTime()
-- Method 1: castDB by GUID (UNIT_CASTEVENT, unitstr IS the GUID from GetName(1))
local castDB = NP.castDB
if hasValidGUID and unitstr and castDB[unitstr] then
local cast = castDB[unitstr]
if cast.startTime + (cast.duration / 1000) > castNow then
casting = cast
else
castDB[unitstr] = nil
end
end
-- Method 2: UnitCastingInfo / UnitChannelInfo (SuperWoW 1.5+)
if not casting and superwow_active and hasValidGUID and unitstr then
if UnitCastingInfo then
local spell, _, _, texture, startTime, endTime = UnitCastingInfo(unitstr)
if spell then
casting = {
spell = spell,
startTime = startTime / 1000,
duration = endTime - startTime,
icon = texture
}
end
end
if not casting and UnitChannelInfo then
local spell, _, _, texture, startTime, endTime = UnitChannelInfo(unitstr)
if spell then
casting = {
spell = spell,
startTime = startTime / 1000,
duration = endTime - startTime,
icon = texture,
channel = true
}
end
end
end
-- Method 3: Fallback to original Blizzard castbar visibility
if not casting and original.castbar then
local origCBVisible = original.castbar.IsVisible and original.castbar:IsVisible()
if not origCBVisible then
origCBVisible = original.castbar.IsShown and original.castbar:IsShown()
end
if origCBVisible then
local castVal = original.castbar:GetValue() or 0
local castMin, castMax = original.castbar:GetMinMaxValues()
if castMax and castMax > 0 then
np.castbar:SetMinMaxValues(castMin, castMax)
np.castbar:SetValue(castVal)
np.castbarText:SetText("")
np.castbarTimer:SetText("")
np.castbarIcon:Hide()
np.castbarBG:Show()
castShowing = true
end
end
end
-- Render castbar from Method 1 or 2
if casting and casting.spell and not castShowing then
local start = casting.startTime
local duration = casting.duration
if castNow < start + (duration / 1000) then
np.castbar:SetMinMaxValues(0, duration)
if casting.channel then
np.castbar:SetValue(duration - (castNow - start) * 1000)
else
np.castbar:SetValue((castNow - start) * 1000)
end
np.castbarText:SetText(casting.spell)
local timeLeft = (start + (duration / 1000)) - castNow
np.castbarTimer:SetText(string_format("%.1f", timeLeft))
if casting.icon then
np.castbarIcon:SetTexture(casting.icon)
np.castbarIcon:Show()
else
np.castbarIcon:Hide()
end
np.castbarBG:Show()
castShowing = true
end
end
if not castShowing then
np.castbarBG:Hide()
np.castbarIcon:Hide()
end
-- Debuff update (throttled)
local now = GetTime()
if now - np.lastDebuffUpdate >= DEBUFF_UPDATE_INTERVAL then
np.lastDebuffUpdate = now
local numDebuffs = 0
-- Debuff anchor: below castbar if visible, else below healthBG (which now includes mana)
if castShowing then
np.debuffAnchor = np.castbarBG
else
np.debuffAnchor = np.healthBG
end
if NanamiPlates_Auras and np.debuffs then
numDebuffs = NanamiPlates_Auras:UpdateDebuffs(np, unitstr, plateName, isTarget, hasValidGUID, superwow_active)
NanamiPlates_Auras:UpdateDebuffPositions(np, numDebuffs)
end
-- Combo points
if NanamiPlates_ComboPoints then
local numPoints = NanamiPlates_ComboPoints:UpdateComboPoints(np, isTarget)
NanamiPlates_ComboPoints:UpdateComboPointPositions(np, numDebuffs)
end
end
end
-- Main OnUpdate loop
local scanThrottle = 0
local SCAN_INTERVAL = 0.1
local function OnUpdate()
local now = GetTime()
-- Rebuild quest mob cache periodically
if now - questCacheTimer >= QUEST_CACHE_INTERVAL then
questCacheTimer = now
RebuildQuestMobCache()
end
-- Scan for new nameplates
if now - scanThrottle >= SCAN_INTERVAL then
scanThrottle = now
Scanner.ScanForNewNameplates(registry, HandleNamePlate)
end
-- Update all active nameplates
for frame, np in pairs(registry) do
if frame:IsShown() then
UpdateNamePlate(frame)
end
end
-- Cleanup debuff timers
if NanamiPlates_Auras then
NanamiPlates_Auras:CleanupTimers()
end
end
-- Event handler
local function OnEvent()
local evnt = event
if evnt == "ADDON_LOADED" and arg1 == "Nanami-Plates" then
NP:LoadSettings()
NP.Print("Loaded.")
elseif evnt == "PLAYER_ENTERING_WORLD" then
Scanner.Reset()
if NanamiPlates_Castbar.ClearCastData then
NanamiPlates_Castbar.ClearCastData()
end
NP.playerClassCache = {}
if SpellDB and SpellDB.ScanSpellbook then
SpellDB:ScanSpellbook()
end
NP:DetectTankSpec()
if NanamiPlates_Threat then
NanamiPlates_Threat.BroadcastTankMode(true)
end
elseif evnt == "CHARACTER_POINTS_CHANGED" then
NP:DetectTankSpec()
elseif evnt == "UNIT_CASTEVENT" then
if superwow_active and arg1 then
NanamiPlates_Castbar.HandleUnitCastEvent(arg1, arg2, arg3, arg4, arg5)
end
elseif evnt == "SPELLCAST_STOP" then
if SpellDB then SpellDB:PersistPending() end
elseif evnt == "CHAT_MSG_SPELL_FAILED_LOCALPLAYER" then
if SpellDB then SpellDB:RemovePending() end
elseif evnt == "QUEST_LOG_UPDATE" then
questCacheTimer = 0
elseif evnt == "PLAYER_TARGET_CHANGED" then
-- Force debuff update on all plates
for frame, np in pairs(registry) do
if frame:IsShown() then
np.lastDebuffUpdate = 0
end
end
elseif evnt == "PLAYER_REGEN_DISABLED" then
playerInCombat = true
elseif evnt == "PLAYER_REGEN_ENABLED" then
playerInCombat = false
NP.playerClassCache = {}
if SpellDB then
for k in pairs(SpellDB.objects) do
SpellDB.objects[k] = nil
end
end
elseif evnt == "PARTY_MEMBERS_CHANGED" or evnt == "RAID_ROSTER_UPDATE" then
NP.playerClassCache = {}
if NanamiPlates_Threat then
NanamiPlates_Threat.BroadcastTankMode(true)
end
elseif NP.SPELL_EVENTS[evnt] then
if NanamiPlates_CombatLog then
NanamiPlates_CombatLog.HandleSpellEvent(evnt, arg1)
end
elseif NP.COMBAT_EVENTS[evnt] then
if NanamiPlates_CombatLog then
NanamiPlates_CombatLog.HandleCombatEvent(evnt, arg1)
end
end
end
NP.EventFrame:SetScript("OnEvent", OnEvent)
NP.EventFrame:SetScript("OnUpdate", OnUpdate)