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