Files
BlizzNameplatesPlus/modules/names.lua
T
2026-09-30 19:07:47 +02:00

324 lines
11 KiB
Lua

BNP = BNP or {}
local function CaptureDefaultNameFontSize(plate)
if BNP.defaultNameFontSize then return end
if not plate or not plate.name or not plate.name.GetFont then return end
local _, size = plate.name:GetFont()
if size then BNP.defaultNameFontSize = size end
end
-- Vanilla's native name FontString can be recolored directly by the client
-- (for example when threat/aggro changes). Lua method wrappers are not enough
-- to stop those internal updates. When Custom Name Color is enabled, BNP draws
-- its own FontString directly above the native name instead. The Blizzard text
-- may still change color underneath, but the visible BNP copy stays stable.
local function EnsureCustomNameOverlay(plate)
if not plate or not plate.name then return nil end
if plate.BNPCustomNameOverlay then return plate.BNPCustomNameOverlay end
if not plate.CreateFontString then return nil end
local parent = plate.name.GetParent and plate.name:GetParent() or plate
local overlay = parent:CreateFontString(nil, "OVERLAY")
if not overlay then return nil end
overlay:ClearAllPoints()
overlay:SetPoint("CENTER", plate.name, "CENTER", 0, 0)
overlay:Hide()
plate.BNPCustomNameOverlay = overlay
return overlay
end
-- The custom name uses a separate FontString so Blizzard cannot recolor it.
-- A newly-created FontString does not automatically inherit the native
-- nameplate text shadow, so mirror that presentation here as well.
local function SyncCustomNameShadow(source, target)
if not source or not target then return end
local shadowCopied = false
if source.GetShadowColor and target.SetShadowColor then
local r, g, b, a = source:GetShadowColor()
if r ~= nil and g ~= nil and b ~= nil then
target:SetShadowColor(r, g, b, a or 1)
shadowCopied = true
end
end
-- Vanilla/older clients or replacement FontStrings may not expose the
-- shadow getters. Keep the classic Blizzard-style drop shadow in that case.
if not shadowCopied and target.SetShadowColor then
target:SetShadowColor(0, 0, 0, 1)
end
local offsetCopied = false
if source.GetShadowOffset and target.SetShadowOffset then
local x, y = source:GetShadowOffset()
if x ~= nil and y ~= nil then
target:SetShadowOffset(x, y)
offsetCopied = true
end
end
if not offsetCopied and target.SetShadowOffset then
target:SetShadowOffset(1, -1)
end
end
local function SyncCustomNameOverlay(plate)
if not plate or not plate.name then return end
local enabled
if BNP.IsCustomNameColorEnabled then
enabled = BNP:IsCustomNameColorEnabled()
else
enabled = BNP_DB and BNP_DB.customNameColor == true
end
local overlay = plate.BNPCustomNameOverlay
if not enabled then
if overlay then overlay:Hide() end
if plate.BNPNameHiddenByCustomColor then
plate.BNPNameHiddenByCustomColor = nil
if not plate.BNPNameHidden and plate.name.Show then
plate.name:Show()
end
end
plate.BNPCustomNameColorApplied = nil
return
end
overlay = EnsureCustomNameOverlay(plate)
if not overlay then return end
-- Mirror only presentation data that belongs to the native name. Keeping the
-- copy anchored to plate.name means Name Y Offset and nameplate scaling keep
-- working automatically without a second positioning system.
if plate.name.GetFont and overlay.SetFont then
local font, size, flags = plate.name:GetFont()
if font and size then overlay:SetFont(font, size, flags) end
end
SyncCustomNameShadow(plate.name, overlay)
if plate.name.GetText and overlay.SetText then
overlay:SetText(plate.name:GetText() or "")
end
local r, g, b = 1, 1, 1
if BNP.GetNameColor then r, g, b = BNP:GetNameColor() end
overlay:SetTextColor(r, g, b, 1)
if plate.BNPNameHidden then
overlay:Hide()
if plate.name.Hide then plate.name:Hide() end
else
overlay:Show()
-- Hide Blizzard's native name while the custom copy is active. This is
-- the important part: the client is free to recolor the hidden native
-- FontString internally, but there is no red frame left underneath that
-- can flash through between Lua updates.
if plate.name.Hide then plate.name:Hide() end
plate.BNPNameHiddenByCustomColor = true
end
plate.BNPCustomNameColorApplied = true
end
local function ApplyNameColor(plate)
SyncCustomNameOverlay(plate)
end
local function ApplyNameFont(plate)
if not plate or not plate.name or not plate.name.GetFont or not plate.name.SetFont then return end
CaptureDefaultNameFontSize(plate)
-- Nil means the user has never touched the new size control. Keep the
-- exact current Blizzard/skin-addon font size for backwards compatibility.
local configured = BNP_DB and tonumber(BNP_DB.nameFontSize)
if not configured then return end
local font, currentSize, flags = plate.name:GetFont()
if not font then return end
local size = BNP.GetNameFontSize and BNP:GetNameFontSize() or configured
if currentSize ~= size then
-- Keep the font face and flags exactly as they currently are. Only the
-- point size belongs to BNP, so DarkMode/font addons remain compatible.
plate.name:SetFont(font, size, flags)
end
end
function BNP:RefreshNameAppearance()
local plate
for plate in pairs(BNP.plates or {}) do
if plate then
ApplyNameFont(plate)
ApplyNameColor(plate)
if BNP.ApplyNameFontYOffset then BNP:ApplyNameFontYOffset(plate) end
end
end
end
-- Independent name visibility controls for Blizzard nameplates.
-- BNP only hides the name FontString; health bars, level text, raid icons,
-- auras, immunities and click behavior remain untouched.
local function ResolvePlateType(plate)
if not plate or not plate.GetName then return nil end
local token = plate:GetName(1)
if not token then return nil end
local exists = UnitExists(token)
if not exists then return nil end
if UnitIsPlayer and UnitIsPlayer(token) then
return "player"
end
-- Keep player-controlled pets separate from the NPC/Mob name filter. A pet
-- plate should not suddenly lose its name just because "Hide NPC/Mob Names"
-- is enabled.
if UnitPlayerControlled and UnitPlayerControlled(token) then
return "pet"
end
return "npc"
end
local function ShouldHide(unitType)
if unitType == "player" then
if BNP.HidePlayerNamesEnabled then return BNP:HidePlayerNamesEnabled() end
return BNP_DB and BNP_DB.hidePlayerNames and true or false
elseif unitType == "npc" then
if BNP.HideNPCNamesEnabled then return BNP:HideNPCNamesEnabled() end
return BNP_DB and BNP_DB.hideNPCNames and true or false
end
return false
end
-- Authoritative query used by aura/immunity layout. Do not rely only on the
-- transient BNPNameHidden marker: on recycled/projected Vanilla nameplates the
-- name FontString and aura module callbacks can update in a different order.
function BNP:IsPlateNameHidden(plate)
if not plate then return false end
-- This helper is queried by the aura renderer at 20 Hz. In the default state
-- (both hide-name options off) return immediately without resolving a unit
-- token for every visible plate.
if not BNP_DB or (not BNP_DB.hidePlayerNames and not BNP_DB.hideNPCNames) then
return false
end
local unitType = ResolvePlateType(plate) or plate.BNPNameUnitType
if unitType then
return ShouldHide(unitType)
end
-- If identity is momentarily unresolved, keep the last BNP-owned state so a
-- recycled plate does not jump vertically for a single scan.
return plate.BNPNameHidden and true or false
end
local function RefreshPlateAuraLayout(plate)
if not plate then return end
if BNP.RefreshAuraLayoutForPlate then BNP:RefreshAuraLayoutForPlate(plate) end
if BNP.RefreshImmunityLayoutForPlate then BNP:RefreshImmunityLayoutForPlate(plate) end
end
local function RestoreIfBNPHid(plate)
if not plate or not plate.name then return end
if plate.BNPNameHidden then
plate.name:Show()
plate.BNPNameHidden = nil
end
SyncCustomNameOverlay(plate)
end
local function UpdatePlateName(plate, resetIdentity)
if not plate or not plate.name then return end
ApplyNameFont(plate)
ApplyNameColor(plate)
if BNP.ApplyNameFontYOffset then BNP:ApplyNameFontYOffset(plate) end
if resetIdentity then
-- Blizzard reuses nameplate frames. Never carry a hidden name from the
-- previous unit into a newly shown plate while its projected token is not
-- resolved yet.
plate.BNPNameUnitType = nil
RestoreIfBNPHid(plate)
end
local unitType = ResolvePlateType(plate)
if unitType then
plate.BNPNameUnitType = unitType
else
unitType = plate.BNPNameUnitType
end
if not unitType then return end
if ShouldHide(unitType) then
if not plate.BNPNameHidden then
plate.name:Hide()
plate.BNPNameHidden = true
if plate.BNPCustomNameOverlay then plate.BNPCustomNameOverlay:Hide() end
elseif plate.name.IsShown and plate.name:IsShown() then
-- The client may re-show Blizzard regions while updating/recycling a
-- plate. Re-enforce only names BNP explicitly owns as hidden.
plate.name:Hide()
if plate.BNPCustomNameOverlay then plate.BNPCustomNameOverlay:Hide() end
end
else
RestoreIfBNPHid(plate)
end
end
function BNP:RefreshNameVisibility()
local plate
for plate in pairs(BNP.plates or {}) do
if plate and plate.IsShown and plate:IsShown() then
UpdatePlateName(plate, false)
-- Re-anchor immediately when the option is toggled instead of waiting
-- for the aura module's next identity/update cycle.
RefreshPlateAuraLayout(plate)
elseif plate then
RestoreIfBNPHid(plate)
plate.BNPNameUnitType = nil
end
end
end
if BNP.libnameplate then
table.insert(BNP.libnameplate.OnInit, function(plate)
local current = plate or this
if not current then return end
UpdatePlateName(current, true)
end)
table.insert(BNP.libnameplate.OnShow, function(plate)
local current = plate or this
if not current then return end
UpdatePlateName(current, true)
end)
-- libnameplate updates are already throttled to 0.10s. This is deliberate:
-- if Blizzard re-shows the name FontString, the configured hidden state is
-- restored without adding a per-frame OnUpdate to every nameplate.
table.insert(BNP.libnameplate.OnUpdate, function(plate)
local current = plate or this
if not current or not current:IsShown() then return end
-- Both hide options are disabled by default. In that common case avoid
-- UnitExists/UnitIsPlayer work for every visible plate on every 0.10s pass.
-- If BNP previously hid this specific name, still run once to restore it.
local anyHideEnabled = BNP_DB and (BNP_DB.hidePlayerNames or BNP_DB.hideNPCNames)
local customColorEnabled = BNP_DB and BNP_DB.customNameColor == true
if not anyHideEnabled and not customColorEnabled and not current.BNPNameHidden and not current.BNPCustomNameColorApplied then return end
UpdatePlateName(current, false)
end)
end