Move nameplates onto ClassicAPI stable nameplate tokens

ClassicAPI now assigns retail-exact, per-plate-stable "nameplateN" tokens and
fires vanilla UNIT_* events for them, so the nameplate module can key off the
token instead of Nampower/SuperWoW GUID primitives:

- Events: UNIT_AURA_GUID/UNIT_FLAGS_GUID -> UNIT_AURA/UNIT_FLAGS, matched by
  the "nameplateN" token (guarded on the token prefix, since these also fire
  for target/party/raid). Registered unconditionally -- no GetUnitField gate.
- Health: GetUnitField(guid, "health"/"maxHealth") -> UnitHealth/UnitHealthMax
  on the plate's cached token. Same UNIT_FIELD_HEALTH the server broadcasts
  (real HP on Turtle; the ~= 100 scaled-vs-real guard is unchanged).
- Combat/target in GetCombatStateColor: GetUnitField("flags") + HasFlag ->
  UnitAffectingCombat(token); GetUnitField("target") + SuperWoW "<guid>target"
  -> the "nameplateNtarget" chain. Dropped the now-dead HasFlag,
  UNIT_FLAG_IN_COMBAT, and guidTargetTokenCache.
- GetCastInfo now takes the token directly; every caller already holds it, so
  the UnitTokenFromGUID(guid) round-trip is gone (unused module-wide now).

NAME_PLATE_UNIT_ADDED caches the token as plate.nameplate.unit alongside the
GUID; REMOVED clears both. GUID is retained only as the stable cache key
(debuffCache/threatMemory/combatColorCache). Only GetUnitField("npcFlags")
remains -- it has no token/vanilla equivalent.
This commit is contained in:
Brues
2026-07-24 20:09:42 -05:00
parent ce1c49fcbb
commit 63001b7b0c
+53 -63
View File
@@ -61,13 +61,11 @@ pfUI:RegisterModule("nameplates", function ()
local raidGuidCache = {} -- guid -> name (rebuilt on RAID_ROSTER_UPDATE/PARTY_MEMBERS_CHANGED)
-- Resolve a plate GUID to its cast/channel info via C_Spell. Returns a
-- Resolve a unit token to its cast/channel info via C_Spell. Returns a
-- compact struct (spellName / icon / startTime / endTime / duration /
-- isChannel) or nil when the unit isn't casting / the GUID can't map to a
-- live token.
local function GetCastInfo(guid)
if not guid then return nil end
local unit = UnitTokenFromGUID(guid)
-- isChannel) or nil when the unit isn't casting. Callers already hold the
-- nameplate token, so there's no GUID->token round-trip.
local function GetCastInfo(unit)
if not unit then return nil end
local name, _, texture, startMs, endMs, _, _, _, spellID = C_Spell.UnitCastingInfo(unit)
local isChannel
@@ -87,7 +85,6 @@ pfUI:RegisterModule("nameplates", function ()
}
end
local guidTargetTokenCache = {} -- guid -> "<guid>target" interned string
local debuffCache = {} -- guid -> { [spellID] = { start, duration } }
-- Reusable per-plate debuff display buffer (avoid GC churn from per-call table creation)
local debuffDisplayBuf = {} -- [i] = { effect, texture, stacks, dtype, duration, timeleft }
@@ -165,12 +162,6 @@ pfUI:RegisterModule("nameplates", function ()
-- cache default border color
local er, eg, eb, ea = GetStringColor(pfUI_config.appearance.border.color)
-- Vanilla Lua 5.0 bitwise check: math.mod(math.floor(value / flag), 2) ~= 0
local function HasFlag(flags, flag)
return math.mod(math.floor(flags / flag), 2) ~= 0
end
local UNIT_FLAG_IN_COMBAT = 524288 -- 0x00080000
local NULL_GUID = "0x0000000000000000"
local function RebuildRaidGuidCache()
@@ -189,10 +180,11 @@ pfUI:RegisterModule("nameplates", function ()
local combatColorCache = {} -- guid -> { color, expires }
local function GetCombatStateColor(guid)
local function GetCombatStateColor(guid, token)
-- PERF: Quick exit if player not in combat
if not UnitAffectingCombat("player") then return false end
if UnitCanAssist("player", guid) then return false end
if not token then return false end
if UnitCanAssist("player", token) then return false end
-- PERF: 0.2s throttle per guid - color changes are not time-critical
local now = frameState.now
@@ -201,24 +193,17 @@ pfUI:RegisterModule("nameplates", function ()
return cached.color
end
local flags = GetUnitField and GetUnitField(guid, "flags")
if not flags then return false end
if not HasFlag(flags, UNIT_FLAG_IN_COMBAT) then return false end
if not UnitAffectingCombat(token) then return false end
local mobTargetGuid = GetUnitField and GetUnitField(guid, "target")
-- The mob's current target via the nameplate token chain (ClassicAPI):
-- "nameplateNtarget" resolves to whatever this plate's unit is targeting,
-- so no GetUnitField("target") or SuperWoW "<guid>target" token needed.
local target = token .. "target"
local mobTargetGuid = UnitGUID(target)
local hasTarget = mobTargetGuid and mobTargetGuid ~= NULL_GUID
-- PERF: cache the SuperWoW-style "<guid>target" unit token. The concat
-- intern-hits Lua's string pool every call; caching once per guid
-- saves the hash+lookup. Cleared in NAME_PLATE_UNIT_REMOVED.
local target = guidTargetTokenCache[guid]
if not target then
target = guid .. "target"
guidTargetTokenCache[guid] = target
end
local color = false
local castInfo = GetCastInfo(guid)
local castInfo = GetCastInfo(token)
local isCasting = castInfo and castInfo.endTime and now < castInfo.endTime
local targetingPlayer = hasTarget and UnitIsUnit(target, "player")
@@ -466,10 +451,8 @@ nameplates:RegisterEvent("PARTY_MEMBERS_CHANGED")
nameplates:RegisterEvent("NAME_PLATE_CREATED")
nameplates:RegisterEvent("NAME_PLATE_UNIT_ADDED")
nameplates:RegisterEvent("NAME_PLATE_UNIT_REMOVED")
if GetUnitField then
nameplates:RegisterEvent("UNIT_FLAGS_GUID")
nameplates:RegisterEvent("UNIT_AURA_GUID")
end
nameplates:RegisterEvent("UNIT_AURA")
nameplates:RegisterEvent("UNIT_FLAGS")
nameplates:SetScript("OnEvent", function()
-- Stop event handling during logout to prevent crash 132
@@ -542,10 +525,12 @@ end
end
elseif event == "NAME_PLATE_UNIT_ADDED" then
-- arg1 = "nameplateN" unit token; resolve to GUID for cache keys
-- arg1 = "nameplateN" unit token. Cache the GUID for cache keys and the
-- token itself for token-based UnitX reads (stable per plate lifetime).
local plate = C_NamePlate.GetNamePlateForUnit(arg1)
if plate and plate.nameplate then
plate.nameplate.cachedGuid = UnitGUID(arg1)
plate.nameplate.unit = arg1
nameplates.OnShow(plate)
end
visiblePlateCount = visiblePlateCount + 1
@@ -558,31 +543,38 @@ end
if guid then
if debuffCache[guid] then debuffCache[guid] = nil end
if threatMemory[guid] then threatMemory[guid] = nil end
if guidTargetTokenCache[guid] then guidTargetTokenCache[guid] = nil end
if combatColorCache[guid] then combatColorCache[guid] = nil end
local plate = C_NamePlate.GetNamePlateForUnit(arg1)
if plate and plate.nameplate and plate.nameplate.cachedGuid == guid then
plate.nameplate.cachedGuid = nil
plate.nameplate.unit = nil
end
end
elseif event == "UNIT_FLAGS_GUID" then
-- Nampower: fires instantly when any unit's flags change (e.g. stun, combat enter/leave)
-- arg1 = guid — directly flag that nameplate for immediate update, bypassing throttle
local plate = C_NamePlate.GetNamePlateForGUID(arg1)
if plate and plate.nameplate then
plate.nameplate.eventcache = true
elseif event == "UNIT_FLAGS" then
-- ClassicAPI: fires with arg1 == "nameplateN" when a unit's flags change
-- (stun, combat enter/leave). Flag that plate for an immediate update,
-- bypassing the throttle. Guard on the token prefix -- UNIT_FLAGS also
-- fires for target/party/raid, which aren't ours to handle here.
if arg1 and strfind(arg1, "^nameplate") then
local plate = C_NamePlate.GetNamePlateForUnit(arg1)
if plate and plate.nameplate then
plate.nameplate.eventcache = true
end
end
elseif event == "UNIT_AURA_GUID" then
-- Nampower: fires when a unit's aura set changes (add/remove/modify).
-- arg1 = guid. Flag the matching plate so OnUpdate triggers a fresh
-- C_UnitAuras read on the next tick instead of waiting on the 0.5s
-- throttle covers expirations, dispels, refreshes, and stack changes
-- in one event.
local plate = C_NamePlate.GetNamePlateForGUID(arg1)
if plate and plate.nameplate then
plate.nameplate.auraUpdate = true
elseif event == "UNIT_AURA" then
-- ClassicAPI: fires with arg1 == "nameplateN" when a unit's aura set
-- changes (add/remove/modify). Flag the matching plate so OnUpdate does a
-- fresh C_UnitAuras read next tick instead of waiting on the 0.5s
-- throttle -- covers expirations, dispels, refreshes, and stack changes
-- in one event. Guard on the token prefix (UNIT_AURA also fires for
-- target/party/raid).
if arg1 and strfind(arg1, "^nameplate") then
local plate = C_NamePlate.GetNamePlateForUnit(arg1)
if plate and plate.nameplate then
plate.nameplate.auraUpdate = true
end
end
elseif event == "PLAYER_TARGET_CHANGED" then
@@ -986,16 +978,14 @@ end
-- always make sure to keep plate visible
plate:Show()
if target and cfg.targetglow then
plate.glow:Show() else plate.glow:Hide()
end
plate.glow:SetShown(target and cfg.targetglow)
-- target indicator
if cfg.outcombatstate then
local guid = plate.cachedGuid or ""
-- determine color based on combat state
local color = GetCombatStateColor(guid)
local color = GetCombatStateColor(guid, plate.unit)
if not color then color = combatstate.NONE end
-- set border color
@@ -1070,7 +1060,7 @@ end
if guild and C.nameplates.showguildname == "1" then
plate.guild:SetText(guild)
if guild == GetGuildInfo("player") then
if UnitIsInMyGuild(plate.unit) then
plate.guild:SetTextColor(0, 0.9, 0, 1)
else
plate.guild:SetTextColor(0.8, 0.8, 0.8, 1)
@@ -1089,15 +1079,15 @@ end
if cfg.showhp then
local rhp, rhpmax, estimated
local guid = plate.cachedGuid
if guid and GetUnitField then
local npHp = GetUnitField(guid, "health")
local npMaxHp = GetUnitField(guid, "maxHealth")
local unit = plate.unit
if unit then
local npHp = UnitHealth(unit)
local npMaxHp = UnitHealthMax(unit)
if npHp and npHp > 0 and npMaxHp and npMaxHp > 0 and npMaxHp ~= 100 then
rhp, rhpmax = npHp, npMaxHp
end
end
-- Fallback to existing methods
if not rhp then
if hpmax > 100 or (round(hpmax/100*hp) ~= hp) then
@@ -1148,7 +1138,7 @@ end
if cfg.barcombatstate then
local guid = plate.cachedGuid or ""
local color = GetCombatStateColor(guid)
local color = GetCombatStateColor(guid, plate.unit)
if color then
r, g, b, a = color.r, color.g, color.b, color.a
@@ -1300,7 +1290,7 @@ end
-- smooth animation without overloading the central loop.
local isCastingNonTarget = not target and nameplate.castbar and nameplate.castbar:IsShown()
if not isCastingNonTarget and not target and cfg.showcastbar and nameplate.cachedGuid then
local castInfo = GetCastInfo(nameplate.cachedGuid)
local castInfo = GetCastInfo(nameplate.unit)
if castInfo and castInfo.endTime > now then
isCastingNonTarget = true
end
@@ -1589,7 +1579,7 @@ end
-- Shared castbar update logic (used by both dedicated frame and central loop)
nameplates.UpdateCastbar = function(nameplate, now)
if not nameplate or not nameplate.castbar then return end
local castInfo = GetCastInfo(nameplate.cachedGuid)
local castInfo = GetCastInfo(nameplate.unit)
if not castInfo or castInfo.endTime < now then
nameplate.castbar.isShown = nil
nameplate.castbar.lastEndTime = nil