From b9ce2b07c3dd3c6afa4073a1ef1bfb4688926b84 Mon Sep 17 00:00:00 2001 From: Wurmschwanz Date: Thu, 1 Oct 2026 23:32:25 +0200 Subject: [PATCH] Add files via upload --- BlizzNameplatesPlus.toc | 1 + core/config.lua | 64 ++++ core/options.lua | 230 ++++++++++++- modules/classcolors.lua | 1 + modules/personalnameplate.lua | 613 ++++++++++++++++++++++++++++++++++ modules/scale.lua | 31 +- modules/visualguard.lua | 3 + 7 files changed, 936 insertions(+), 7 deletions(-) create mode 100644 modules/personalnameplate.lua diff --git a/BlizzNameplatesPlus.toc b/BlizzNameplatesPlus.toc index 14d8d4f..11b18a2 100644 --- a/BlizzNameplatesPlus.toc +++ b/BlizzNameplatesPlus.toc @@ -44,5 +44,6 @@ modules\castbar.lua modules\guiddebug.lua modules\tankmode.lua modules\healthpercent.lua +modules\personalnameplate.lua modules\combopoints.lua modules\commands.lua diff --git a/core/config.lua b/core/config.lua index a2db030..5aa9cbe 100644 --- a/core/config.lua +++ b/core/config.lua @@ -79,6 +79,14 @@ BNP.defaults = BNP.defaults or { questPlateIconSize = 16, questPlateXOffset = 10, questPlateYOffset = -7, + personalNameplate = false, + personalNameplateCombatOnly = true, + personalNameplateClassColor = false, + personalNameplateHealthText = "both", + personalNameplateDebuffs = false, + personalNameplateDebuffYOffset = 0, + personalNameplateScale = 1.0, + personalNameplateYOffset = -90, } function BNP:InitConfig() @@ -236,6 +244,14 @@ function BNP:InitConfig() if BNP_DB.questPlateIconSize == nil then BNP_DB.questPlateIconSize = self.defaults.questPlateIconSize end if BNP_DB.questPlateXOffset == nil then BNP_DB.questPlateXOffset = self.defaults.questPlateXOffset end if BNP_DB.questPlateYOffset == nil then BNP_DB.questPlateYOffset = self.defaults.questPlateYOffset end + if BNP_DB.personalNameplate == nil then BNP_DB.personalNameplate = self.defaults.personalNameplate end + if BNP_DB.personalNameplateCombatOnly == nil then BNP_DB.personalNameplateCombatOnly = self.defaults.personalNameplateCombatOnly end + if BNP_DB.personalNameplateClassColor == nil then BNP_DB.personalNameplateClassColor = self.defaults.personalNameplateClassColor end + if BNP_DB.personalNameplateHealthText == nil then BNP_DB.personalNameplateHealthText = self.defaults.personalNameplateHealthText end + if BNP_DB.personalNameplateDebuffs == nil then BNP_DB.personalNameplateDebuffs = self.defaults.personalNameplateDebuffs end + if BNP_DB.personalNameplateDebuffYOffset == nil then BNP_DB.personalNameplateDebuffYOffset = self.defaults.personalNameplateDebuffYOffset end + if BNP_DB.personalNameplateScale == nil then BNP_DB.personalNameplateScale = self.defaults.personalNameplateScale end + if BNP_DB.personalNameplateYOffset == nil then BNP_DB.personalNameplateYOffset = self.defaults.personalNameplateYOffset end end function BNP:GetNameplateScale() @@ -699,6 +715,54 @@ end +function BNP:IsPersonalNameplateEnabled() + return BNP_DB and BNP_DB.personalNameplate == true +end + +function BNP:IsPersonalNameplateCombatOnly() + return not BNP_DB or BNP_DB.personalNameplateCombatOnly ~= false +end + + +function BNP:IsPersonalNameplateClassColorEnabled() + return BNP_DB and BNP_DB.personalNameplateClassColor == true +end + +function BNP:GetPersonalNameplateHealthTextMode() + local mode = (BNP_DB and BNP_DB.personalNameplateHealthText) or self.defaults.personalNameplateHealthText or "both" + if mode ~= "off" and mode ~= "percent" and mode ~= "hp" and mode ~= "both" then + mode = "both" + end + return mode +end + +function BNP:IsPersonalNameplateDebuffsEnabled() + return BNP_DB and BNP_DB.personalNameplateDebuffs == true +end + +function BNP:GetPersonalNameplateDebuffYOffset() + local value = (BNP_DB and tonumber(BNP_DB.personalNameplateDebuffYOffset)) or self.defaults.personalNameplateDebuffYOffset or 0 + if value < -50 then value = -50 end + if value > 50 then value = 50 end + if value >= 0 then return math.floor(value + 0.5) end + return math.ceil(value - 0.5) +end + +function BNP:GetPersonalNameplateScale() + local value = (BNP_DB and tonumber(BNP_DB.personalNameplateScale)) or self.defaults.personalNameplateScale or 1.0 + if value < 0.70 then value = 0.70 end + if value > 1.50 then value = 1.50 end + return value +end + +function BNP:GetPersonalNameplateYOffset() + local value = (BNP_DB and tonumber(BNP_DB.personalNameplateYOffset)) or self.defaults.personalNameplateYOffset or -90 + if value < -250 then value = -250 end + if value > 150 then value = 150 end + if value >= 0 then return math.floor(value + 0.5) end + return math.ceil(value - 0.5) +end + function BNP:AreQuestPlateIndicatorsEnabled() return not BNP_DB or BNP_DB.questPlateIndicators ~= false end diff --git a/core/options.lua b/core/options.lua index 2bbc2c0..2858db2 100644 --- a/core/options.lua +++ b/core/options.lua @@ -825,7 +825,7 @@ function BNP:CreateOptions() local frame = CreateFrame("Frame", "BNPOptionsFrame", UIParent) frame:SetWidth(470) - frame:SetHeight(710) + frame:SetHeight(comboOptionsClass and 735 or 710) -- Keep the top edge in roughly the same place and add the extra room -- downward. The Icons page uses the height for Totems, Raid Marks and -- Quest Indicators without crowding or clipping. @@ -842,6 +842,12 @@ function BNP:CreateOptions() frame:RegisterForDrag("LeftButton") frame:SetScript("OnDragStart", function() this:StartMoving() end) frame:SetScript("OnDragStop", function() this:StopMovingOrSizing() end) + frame:SetScript("OnShow", function() + if BNP.RefreshPersonalNameplate then BNP:RefreshPersonalNameplate() end + end) + frame:SetScript("OnHide", function() + if BNP.RefreshPersonalNameplate then BNP:RefreshPersonalNameplate() end + end) frame:Hide() local title = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge") @@ -1295,6 +1301,168 @@ function BNP:CreateOptions() frame.comboPointsYOffsetSlider = comboYOffset end + -- PERSONAL NAMEPLATE ----------------------------------------------------- + -- Clean two-column grid: visibility, appearance, text/debuff positioning, + -- then plate scale/position. Rogue/Druid get a little extra window height + -- so the same layout stays intact below the Combo Points section. + local personalSectionY = comboOptionsClass and -494 or -442 + CreateSection(nameplatesPage, "Personal Nameplate", personalSectionY) + + -- Row 1: visibility. + local personalNameplate = CreateCheck(nameplatesPage, "Enable Personal Nameplate", personalSectionY - 22, function() + BNP_DB.personalNameplate = this:GetChecked() and true or false + if BNP.RefreshPersonalNameplate then BNP:RefreshPersonalNameplate() end + if frame.UpdateDependentControls then frame:UpdateDependentControls() end + end, 22) + personalNameplate:SetScript("OnEnter", function() + GameTooltip:SetOwner(this, "ANCHOR_RIGHT") + GameTooltip:SetText("Personal Nameplate", 1, 0.82, 0) + GameTooltip:AddLine("Shows your own Health and Mana/Rage/Energy below the character using BNP's classic nameplate style.", 1, 1, 1, true) + GameTooltip:Show() + end) + personalNameplate:SetScript("OnLeave", function() GameTooltip:Hide() end) + frame.personalNameplateCheck = personalNameplate + + local personalCombatOnly = CreateCheck(nameplatesPage, "Combat Only", personalSectionY - 22, function() + BNP_DB.personalNameplateCombatOnly = this:GetChecked() and true or false + if BNP.RefreshPersonalNameplate then BNP:RefreshPersonalNameplate() end + end, 246) + personalCombatOnly:SetScript("OnEnter", function() + GameTooltip:SetOwner(this, "ANCHOR_RIGHT") + GameTooltip:SetText("Combat Only", 1, 0.82, 0) + GameTooltip:AddLine("Only shows the Personal Nameplate while you are in combat. The options window keeps a preview visible for positioning.", 1, 1, 1, true) + GameTooltip:Show() + end) + personalCombatOnly:SetScript("OnLeave", function() GameTooltip:Hide() end) + frame.personalNameplateCombatOnlyCheck = personalCombatOnly + + -- Row 2: appearance / aura toggles. + local personalClassColor = CreateCheck(nameplatesPage, "Class Color", personalSectionY - 48, function() + BNP_DB.personalNameplateClassColor = this:GetChecked() and true or false + if BNP.RefreshPersonalNameplate then BNP:RefreshPersonalNameplate() end + end, 22) + personalClassColor:SetScript("OnEnter", function() + GameTooltip:SetOwner(this, "ANCHOR_RIGHT") + GameTooltip:SetText("Class Color", 1, 0.82, 0) + GameTooltip:AddLine("Colors your Personal Nameplate health bar by class. Off uses the normal green health bar.", 1, 1, 1, true) + GameTooltip:Show() + end) + personalClassColor:SetScript("OnLeave", function() GameTooltip:Hide() end) + frame.personalNameplateClassColorCheck = personalClassColor + + local personalDebuffs = CreateCheck(nameplatesPage, "Show Debuffs", personalSectionY - 48, function() + BNP_DB.personalNameplateDebuffs = this:GetChecked() and true or false + if BNP.RefreshPersonalNameplate then BNP:RefreshPersonalNameplate() end + end, 246) + personalDebuffs:SetScript("OnEnter", function() + GameTooltip:SetOwner(this, "ANCHOR_RIGHT") + GameTooltip:SetText("Show Debuffs", 1, 0.82, 0) + GameTooltip:AddLine("Shows short combat debuffs on you above the Personal Nameplate. Long debuffs over 60 seconds are ignored.", 1, 1, 1, true) + GameTooltip:Show() + end) + personalDebuffs:SetScript("OnLeave", function() GameTooltip:Hide() end) + frame.personalNameplateDebuffsCheck = personalDebuffs + + -- Row 3: text mode on the left, debuff offset on the right. + local personalHealthTextLabel = nameplatesPage:CreateFontString(nil, "OVERLAY", "GameFontNormal") + personalHealthTextLabel:SetPoint("TOPLEFT", nameplatesPage, "TOPLEFT", 28, personalSectionY - 72) + personalHealthTextLabel:SetText("Health Text") + frame.personalNameplateHealthTextLabel = personalHealthTextLabel + + local personalHealthTextDropdown = CreateFrame("Frame", "BNPPersonalHealthTextDropdown", nameplatesPage, "UIDropDownMenuTemplate") + personalHealthTextDropdown:SetPoint("TOPLEFT", nameplatesPage, "TOPLEFT", 10, personalSectionY - 82) + UIDropDownMenu_SetWidth(142, personalHealthTextDropdown) + + local personalDebuffYOffset = CreateSlider(nameplatesPage, "Debuff Y Offset", -50, 50, 1, personalSectionY - 82, 246, 150) + personalDebuffYOffset:SetScript("OnValueChanged", function() + if not BNP_DB then return end + local value = RoundSignedInteger(this:GetValue()) + BNP_DB.personalNameplateDebuffYOffset = value + getglobal(this:GetName() .. "Text"):SetText("Debuff Y Offset: " .. (value > 0 and "+" or "") .. value) + if BNP.RefreshPersonalNameplate then BNP:RefreshPersonalNameplate() end + end) + personalDebuffYOffset:SetScript("OnEnter", function() + GameTooltip:SetOwner(this, "ANCHOR_RIGHT") + GameTooltip:SetText("Personal Debuff Y Offset", 1, 0.82, 0) + GameTooltip:AddLine("Moves the Personal Nameplate debuff row up or down without moving the plate itself.", 1, 1, 1, true) + GameTooltip:Show() + end) + personalDebuffYOffset:SetScript("OnLeave", function() GameTooltip:Hide() end) + frame.personalNameplateDebuffYOffsetSlider = personalDebuffYOffset + + local personalHealthModeLabels = { + off = "Off", + percent = "Percent", + hp = "HP", + both = "HP + Percent", + } + + local function SetPersonalHealthTextMode(mode) + if not personalHealthModeLabels[mode] then mode = "both" end + BNP_DB.personalNameplateHealthText = mode + UIDropDownMenu_SetSelectedValue(personalHealthTextDropdown, mode) + UIDropDownMenu_SetText(personalHealthModeLabels[mode], personalHealthTextDropdown) + if BNP.RefreshPersonalNameplate then BNP:RefreshPersonalNameplate() end + end + + UIDropDownMenu_Initialize(personalHealthTextDropdown, function() + local modes = { "off", "percent", "hp", "both" } + local n + for n = 1, table.getn(modes) do + local mode = modes[n] + local info = {} + info.text = personalHealthModeLabels[mode] + info.value = mode + info.func = function() SetPersonalHealthTextMode(this.value) end + info.checked = (BNP:GetPersonalNameplateHealthTextMode() == mode) + UIDropDownMenu_AddButton(info) + end + end) + personalHealthTextDropdown:SetScript("OnEnter", function() + GameTooltip:SetOwner(this, "ANCHOR_RIGHT") + GameTooltip:SetText("Personal Health Text", 1, 0.82, 0) + GameTooltip:AddLine("Choose Off, Percent, HP, or HP + Percent for the text centered on your Personal Nameplate.", 1, 1, 1, true) + GameTooltip:Show() + end) + personalHealthTextDropdown:SetScript("OnLeave", function() GameTooltip:Hide() end) + frame.personalNameplateHealthTextDropdown = personalHealthTextDropdown + frame.SetPersonalHealthTextMode = SetPersonalHealthTextMode + + -- Row 4: plate size and position, aligned to the same two columns. + local personalScale = CreateSlider(nameplatesPage, "Scale", 0.70, 1.50, 0.05, personalSectionY - 116, 28, 150) + personalScale:SetScript("OnValueChanged", function() + if not BNP_DB then return end + local value = Round(this:GetValue(), 0.05) + BNP_DB.personalNameplateScale = value + getglobal(this:GetName() .. "Text"):SetText("Scale: " .. string.format("%.2f", value)) + if BNP.RefreshPersonalNameplate then BNP:RefreshPersonalNameplate() end + end) + personalScale:SetScript("OnEnter", function() + GameTooltip:SetOwner(this, "ANCHOR_RIGHT") + GameTooltip:SetText("Personal Scale", 1, 0.82, 0) + GameTooltip:AddLine("Changes the size of the Personal Nameplate only.", 1, 1, 1, true) + GameTooltip:Show() + end) + personalScale:SetScript("OnLeave", function() GameTooltip:Hide() end) + frame.personalNameplateScaleSlider = personalScale + + local personalYOffset = CreateSlider(nameplatesPage, "Y Offset", -250, 150, 5, personalSectionY - 116, 246, 150) + personalYOffset:SetScript("OnValueChanged", function() + if not BNP_DB then return end + local value = RoundSignedInteger(this:GetValue() / 5) * 5 + BNP_DB.personalNameplateYOffset = value + getglobal(this:GetName() .. "Text"):SetText("Y Offset: " .. (value > 0 and "+" or "") .. value) + if BNP.RefreshPersonalNameplate then BNP:RefreshPersonalNameplate() end + end) + personalYOffset:SetScript("OnEnter", function() + GameTooltip:SetOwner(this, "ANCHOR_RIGHT") + GameTooltip:SetText("Personal Y Offset", 1, 0.82, 0) + GameTooltip:AddLine("Moves the Personal Nameplate up or down below your character.", 1, 1, 1, true) + GameTooltip:Show() + end) + personalYOffset:SetScript("OnLeave", function() GameTooltip:Hide() end) + frame.personalNameplateYOffsetSlider = personalYOffset + -- AURAS TAB -------------------------------------------------------------- local aurasPage = frame.pages.auras CreateSection(aurasPage, "Debuffs", -4) @@ -2103,6 +2271,31 @@ function BNP:CreateOptions() SetCheckEnabled(self.darkComboPointBorderCheck, comboEnabled) SetCheckEnabled(self.darkNameplateBorderCheck, not (BNP.IsNameplateBorderHidden and BNP:IsNameplateBorderHidden())) + local personalEnabled = BNP.IsPersonalNameplateEnabled and BNP:IsPersonalNameplateEnabled() or false + SetCheckEnabled(self.personalNameplateCombatOnlyCheck, personalEnabled) + SetCheckEnabled(self.personalNameplateClassColorCheck, personalEnabled) + SetCheckEnabled(self.personalNameplateDebuffsCheck, personalEnabled) + if self.personalNameplateHealthTextDropdown then + self.personalNameplateHealthTextDropdown:SetAlpha(personalEnabled and 1.0 or 0.45) + if personalEnabled then + if UIDropDownMenu_EnableDropDown then UIDropDownMenu_EnableDropDown(self.personalNameplateHealthTextDropdown) end + else + if UIDropDownMenu_DisableDropDown then UIDropDownMenu_DisableDropDown(self.personalNameplateHealthTextDropdown) end + end + end + if self.personalNameplateHealthTextLabel then + self.personalNameplateHealthTextLabel:SetTextColor(personalEnabled and 1 or 0.5, personalEnabled and 0.82 or 0.5, personalEnabled and 0 or 0.5) + end + local personalSliders = { self.personalNameplateScaleSlider, self.personalNameplateYOffsetSlider, self.personalNameplateDebuffYOffsetSlider } + local personalIndex + for personalIndex = 1, table.getn(personalSliders) do + local personalSlider = personalSliders[personalIndex] + if personalSlider then + personalSlider:SetAlpha(personalEnabled and 1.0 or 0.45) + if personalSlider.EnableMouse then personalSlider:EnableMouse(personalEnabled) end + end + end + local invertTankEnabled = BNP:IsTankModeEnabled() and BNP:AreTankModeColorsInverted() local function SetTankSwatchEnabled(swatch, enabled) if not swatch then return end @@ -2258,6 +2451,41 @@ function BNP:SyncOptions() frame.comboPointsYOffsetSlider:SetValue(value) getglobal(frame.comboPointsYOffsetSlider:GetName() .. "Text"):SetText("Combo Point Y Offset: " .. (value > 0 and "+" or "") .. value) end + if frame.personalNameplateCheck then + frame.personalNameplateCheck:SetChecked(self.IsPersonalNameplateEnabled and self:IsPersonalNameplateEnabled() or false) + end + if frame.personalNameplateCombatOnlyCheck then + local combatOnly = true + if self.IsPersonalNameplateCombatOnly then combatOnly = self:IsPersonalNameplateCombatOnly() end + frame.personalNameplateCombatOnlyCheck:SetChecked(combatOnly) + end + if frame.personalNameplateClassColorCheck then + frame.personalNameplateClassColorCheck:SetChecked(self.IsPersonalNameplateClassColorEnabled and self:IsPersonalNameplateClassColorEnabled() or false) + end + if frame.personalNameplateHealthTextDropdown then + local mode = self.GetPersonalNameplateHealthTextMode and self:GetPersonalNameplateHealthTextMode() or "both" + local labels = { off = "Off", percent = "Percent", hp = "HP", both = "HP + Percent" } + UIDropDownMenu_SetSelectedValue(frame.personalNameplateHealthTextDropdown, mode) + UIDropDownMenu_SetText(labels[mode] or "HP + Percent", frame.personalNameplateHealthTextDropdown) + end + if frame.personalNameplateDebuffsCheck then + frame.personalNameplateDebuffsCheck:SetChecked(self.IsPersonalNameplateDebuffsEnabled and self:IsPersonalNameplateDebuffsEnabled() or false) + end + if frame.personalNameplateScaleSlider then + local value = self.GetPersonalNameplateScale and self:GetPersonalNameplateScale() or 1.0 + frame.personalNameplateScaleSlider:SetValue(value) + getglobal(frame.personalNameplateScaleSlider:GetName() .. "Text"):SetText("Scale: " .. string.format("%.2f", value)) + end + if frame.personalNameplateYOffsetSlider then + local value = self.GetPersonalNameplateYOffset and self:GetPersonalNameplateYOffset() or -90 + frame.personalNameplateYOffsetSlider:SetValue(value) + getglobal(frame.personalNameplateYOffsetSlider:GetName() .. "Text"):SetText("Y Offset: " .. (value > 0 and "+" or "") .. value) + end + if frame.personalNameplateDebuffYOffsetSlider then + local value = self.GetPersonalNameplateDebuffYOffset and self:GetPersonalNameplateDebuffYOffset() or 0 + frame.personalNameplateDebuffYOffsetSlider:SetValue(value) + getglobal(frame.personalNameplateDebuffYOffsetSlider:GetName() .. "Text"):SetText("Debuff Y Offset: " .. (value > 0 and "+" or "") .. value) + end if frame.debuffYOffsetSlider then local value = self:GetDebuffYOffset() frame.debuffYOffsetSlider:SetValue(value) diff --git a/modules/classcolors.lua b/modules/classcolors.lua index fc0f54b..f00b871 100644 --- a/modules/classcolors.lua +++ b/modules/classcolors.lua @@ -168,6 +168,7 @@ function BNP:RefreshClassColors() end end end + if self.RefreshPersonalNameplateStyle then self:RefreshPersonalNameplateStyle() end end local function EnforceClassColor(plate) diff --git a/modules/personalnameplate.lua b/modules/personalnameplate.lua new file mode 100644 index 0000000..30177ea --- /dev/null +++ b/modules/personalnameplate.lua @@ -0,0 +1,613 @@ +BNP = BNP or {} + +-- Personal Nameplate ------------------------------------------------------- +-- A lightweight player resource display that visually follows BNP's classic +-- nameplate styling. It is screen-space UI (not a projected world nameplate), +-- so it stays stable beneath the player and does not interfere with Blizzard's +-- nameplate discovery/click handling. + +local FRAME_WIDTH = 128 +local FRAME_HEIGHT = 45 +local HEALTH_WIDTH = 104 +local HEALTH_HEIGHT = 9 +local POWER_WIDTH = 104 +local POWER_HEIGHT = 5 +local PERSONAL_DEBUFF_MAX_DURATION = 60 +local PERSONAL_DEBUFF_MAX_ICONS = 6 +local PERSONAL_DEBUFF_SPACING = 1 +-- Match BNP health text anchoring: healthbar CENTER +3px. The personal frame +-- is wider than the healthbar because of the level medallion, so centering +-- debuffs on the whole frame makes the row look shifted to the right. +local PERSONAL_HEALTH_TEXT_X = 3 +local PERSONAL_HEALTH_TEXT_Y = 1 +local PERSONAL_POWER_TEXT_Y = 1 +local PERSONAL_CONTENT_CENTER_X = (5 + HEALTH_WIDTH / 2 + PERSONAL_HEALTH_TEXT_X) - (FRAME_WIDTH / 2) +local STATUS_TEXTURE = "Interface\\TargetingFrame\\UI-StatusBar" +local BORDER_TEXTURE = "Interface\\AddOns\\BlizzNameplatesPlus\\media\\classic_nameplate_border" +local FONT = "Fonts\\FRIZQT__.TTF" + +local classFallback = { + WARRIOR = { 0.78, 0.61, 0.43 }, + MAGE = { 0.41, 0.80, 0.94 }, + ROGUE = { 1.00, 0.96, 0.41 }, + DRUID = { 1.00, 0.49, 0.04 }, + HUNTER = { 0.67, 0.83, 0.45 }, + SHAMAN = { 0.00, 0.44, 0.87 }, + PRIEST = { 1.00, 1.00, 1.00 }, + WARLOCK = { 0.58, 0.51, 0.79 }, + PALADIN = { 0.96, 0.55, 0.73 }, +} + +local powerColors = { + [0] = { 0.00, 0.45, 1.00 }, -- Mana + [1] = { 1.00, 0.12, 0.12 }, -- Rage + [2] = { 1.00, 0.50, 0.25 }, -- Focus (custom clients/classes) + [3] = { 1.00, 0.85, 0.10 }, -- Energy + [4] = { 0.00, 0.70, 1.00 }, -- Happiness / fallback legacy resource +} + +local personalFrame +local inCombat = false +local updateElapsed = 0 +local debuffScanElapsed = 0 + +local function FormatValue(value) + value = tonumber(value) or 0 + if value < 0 then value = 0 end + if value >= 1000000 then + local shown = math.floor((value / 1000000) * 10 + 0.5) / 10 + return tostring(shown) .. "m" + elseif value >= 10000 then + return tostring(math.floor(value / 1000 + 0.5)) .. "k" + end + return tostring(math.floor(value + 0.5)) +end + +local function CreateSolidTexture(parent, layer) + local texture = parent:CreateTexture(nil, layer or "BACKGROUND") + texture:SetTexture("Interface\\ChatFrame\\ChatFrameBackground") + return texture +end + +local function SetThinBorderColor(frame, r, g, b, a) + if not frame then return end + local edges = frame.BNPEdges + if not edges then return end + local i + for i = 1, table.getn(edges) do + edges[i]:SetVertexColor(r, g, b, a or 1) + end +end + +local function CreateThinBorder(parent) + local edges = {} + local top = CreateSolidTexture(parent, "OVERLAY") + top:SetPoint("TOPLEFT", parent, "TOPLEFT", 0, 0) + top:SetPoint("TOPRIGHT", parent, "TOPRIGHT", 0, 0) + top:SetHeight(1) + table.insert(edges, top) + + local bottom = CreateSolidTexture(parent, "OVERLAY") + bottom:SetPoint("BOTTOMLEFT", parent, "BOTTOMLEFT", 0, 0) + bottom:SetPoint("BOTTOMRIGHT", parent, "BOTTOMRIGHT", 0, 0) + bottom:SetHeight(1) + table.insert(edges, bottom) + + local left = CreateSolidTexture(parent, "OVERLAY") + left:SetPoint("TOPLEFT", parent, "TOPLEFT", 0, 0) + left:SetPoint("BOTTOMLEFT", parent, "BOTTOMLEFT", 0, 0) + left:SetWidth(1) + table.insert(edges, left) + + local right = CreateSolidTexture(parent, "OVERLAY") + right:SetPoint("TOPRIGHT", parent, "TOPRIGHT", 0, 0) + right:SetPoint("BOTTOMRIGHT", parent, "BOTTOMRIGHT", 0, 0) + right:SetWidth(1) + table.insert(edges, right) + + parent.BNPEdges = edges +end + +local function CreatePersonalFrame() + if personalFrame then return personalFrame end + + local frame = CreateFrame("Frame", "BNPPersonalNameplate", UIParent) + frame:SetWidth(FRAME_WIDTH) + frame:SetHeight(FRAME_HEIGHT) + frame:SetFrameStrata("MEDIUM") + frame:SetFrameLevel(8) + frame:EnableMouse(false) + frame:Hide() + + -- Main health bar, sized to sit inside BNP's classic nameplate border art. + local health = CreateFrame("StatusBar", nil, frame) + health:SetWidth(HEALTH_WIDTH) + health:SetHeight(HEALTH_HEIGHT) + health:SetPoint("TOPLEFT", frame, "TOPLEFT", 5, -19) + health:SetStatusBarTexture(STATUS_TEXTURE) + health:SetMinMaxValues(0, 1) + health:SetValue(1) + frame.healthbar = health + + local healthBG = CreateSolidTexture(health, "BACKGROUND") + healthBG:SetAllPoints(health) + healthBG:SetVertexColor(0, 0, 0, 1) + frame.healthBG = healthBG + + -- Put border art on a tiny higher-level overlay frame. Child StatusBars can + -- otherwise render above parent textures on old clients and eat the rim. + local overlay = CreateFrame("Frame", nil, frame) + overlay:SetAllPoints(frame) + overlay:SetFrameLevel(frame:GetFrameLevel() + 4) + overlay:EnableMouse(false) + frame.overlay = overlay + + local border = overlay:CreateTexture(nil, "ARTWORK") + border:SetTexture(BORDER_TEXTURE) + border:SetWidth(128) + border:SetHeight(32) + border:SetPoint("TOPLEFT", frame, "TOPLEFT", 0, 0) + frame.border = border + + local healthText = health:CreateFontString(nil, "OVERLAY") + healthText:SetFont(FONT, 8, "OUTLINE") + healthText:SetPoint("CENTER", health, "CENTER", PERSONAL_HEALTH_TEXT_X, PERSONAL_HEALTH_TEXT_Y) + healthText:SetTextColor(1, 1, 1) + frame.healthText = healthText + + -- BNP's classic border contains the small round level medallion at the right. + local levelText = overlay:CreateFontString(nil, "OVERLAY") + levelText:SetFont(FONT, 8, "OUTLINE") + levelText:SetPoint("CENTER", frame, "TOPLEFT", 115, -24) + levelText:SetTextColor(1, 0.82, 0) + frame.levelText = levelText + + -- Slim power bar below the health nameplate. The 1px rim uses the same + -- gold/dark language as BNP's border while keeping the resource bar compact. + local powerHolder = CreateFrame("Frame", nil, frame) + powerHolder:SetWidth(108) + powerHolder:SetHeight(9) + powerHolder:SetPoint("TOPLEFT", frame, "TOPLEFT", 3, -34) + CreateThinBorder(powerHolder) + frame.powerHolder = powerHolder + + local powerBG = CreateSolidTexture(powerHolder, "BACKGROUND") + powerBG:SetPoint("TOPLEFT", powerHolder, "TOPLEFT", 1, -1) + powerBG:SetPoint("BOTTOMRIGHT", powerHolder, "BOTTOMRIGHT", -1, 1) + powerBG:SetVertexColor(0, 0, 0, 0.78) + frame.powerBG = powerBG + + local power = CreateFrame("StatusBar", nil, powerHolder) + power:SetWidth(POWER_WIDTH) + power:SetHeight(POWER_HEIGHT) + power:SetPoint("CENTER", powerHolder, "CENTER", 0, 0) + power:SetStatusBarTexture(STATUS_TEXTURE) + power:SetMinMaxValues(0, 1) + power:SetValue(1) + frame.powerbar = power + + local powerText = power:CreateFontString(nil, "OVERLAY") + powerText:SetFont(FONT, 7, "OUTLINE") + powerText:SetPoint("CENTER", power, "CENTER", 0, PERSONAL_POWER_TEXT_Y) + powerText:SetTextColor(1, 1, 1) + frame.powerText = powerText + + -- Short player debuffs sit centered above the personal plate. They use the + -- same compact icon + countdown language as BNP's normal aura row. + local debuffHolder = CreateFrame("Frame", nil, frame) + debuffHolder:SetWidth(FRAME_WIDTH) + debuffHolder:SetHeight(22) + debuffHolder:SetPoint("BOTTOM", frame, "TOP", PERSONAL_CONTENT_CENTER_X, 1) + debuffHolder:SetFrameLevel(frame:GetFrameLevel() + 6) + debuffHolder:EnableMouse(false) + debuffHolder.icons = {} + frame.debuffHolder = debuffHolder + + local i + for i = 1, PERSONAL_DEBUFF_MAX_ICONS do + local icon = CreateFrame("Frame", nil, debuffHolder) + icon:SetWidth(18) + icon:SetHeight(18) + icon:SetFrameLevel(debuffHolder:GetFrameLevel()) + icon:EnableMouse(false) + + local iconBG = CreateSolidTexture(icon, "BACKGROUND") + iconBG:SetPoint("TOPLEFT", icon, "TOPLEFT", -1, 1) + iconBG:SetPoint("BOTTOMRIGHT", icon, "BOTTOMRIGHT", 1, -1) + iconBG:SetVertexColor(0, 0, 0, 0.95) + + local texture = icon:CreateTexture(nil, "ARTWORK") + texture:SetAllPoints(icon) + icon.texture = texture + + local timer = icon:CreateFontString(nil, "OVERLAY") + timer:SetFont(FONT, 8, "OUTLINE") + timer:SetPoint("CENTER", icon, "CENTER", 0, 0) + timer:SetTextColor(1, 1, 1) + icon.timer = timer + + local stack = icon:CreateFontString(nil, "OVERLAY") + stack:SetFont(FONT, 7, "OUTLINE") + stack:SetPoint("BOTTOMRIGHT", icon, "BOTTOMRIGHT", -1, 1) + stack:SetTextColor(1, 1, 1) + stack:SetText("") + icon.stack = stack + + icon:Hide() + debuffHolder.icons[i] = icon + end + debuffHolder:Hide() + + personalFrame = frame + BNP.personalNameplateFrame = frame + return frame +end + +local function GetPlayerHealthColor() + if BNP.IsPersonalNameplateClassColorEnabled and BNP:IsPersonalNameplateClassColorEnabled() then + local _, class = UnitClass("player") + if RAID_CLASS_COLORS and class and RAID_CLASS_COLORS[class] then + local c = RAID_CLASS_COLORS[class] + return c.r, c.g, c.b + end + local c = class and classFallback[class] + if c then return c[1], c[2], c[3] end + end + return 0.10, 0.95, 0.10 +end + +local function UpdateStyle(frame) + if not frame then return end + + local dark = BNP.IsDarkNameplateBorderEnabled and BNP:IsDarkNameplateBorderEnabled() + local hidden = BNP.IsNameplateBorderHidden and BNP:IsNameplateBorderHidden() + local blackBG = BNP.IsBlackHealthbarBackgroundEnabled and BNP:IsBlackHealthbarBackgroundEnabled() + + if hidden then + frame.border:Hide() + else + frame.border:Show() + if dark then + frame.border:SetVertexColor(0.38, 0.38, 0.38, 1) + else + frame.border:SetVertexColor(1, 1, 1, 1) + end + end + + local edge = dark and 0.32 or 0.78 + local edgeG = dark and 0.32 or 0.60 + local edgeB = dark and 0.32 or 0.08 + SetThinBorderColor(frame.powerHolder, edge, edgeG, edgeB, hidden and 0 or 1) + + if blackBG then + frame.healthBG:SetVertexColor(0, 0, 0, 1) + else + frame.healthBG:SetVertexColor(0, 0, 0, 1) + end + + if hidden then frame.levelText:Hide() else frame.levelText:Show() end + + local hr, hg, hb = GetPlayerHealthColor() + frame.healthbar:SetStatusBarColor(hr, hg, hb) +end + +local function UpdatePosition(frame) + if not frame then return end + local scale = BNP.GetPersonalNameplateScale and BNP:GetPersonalNameplateScale() or 1.0 + local y = BNP.GetPersonalNameplateYOffset and BNP:GetPersonalNameplateYOffset() or -90 + + frame:SetScale(scale) + frame:ClearAllPoints() + frame:SetPoint("CENTER", UIParent, "CENTER", 0, y) + + if frame.debuffHolder then + local debuffY = BNP.GetPersonalNameplateDebuffYOffset and BNP:GetPersonalNameplateDebuffYOffset() or 0 + frame.debuffHolder:ClearAllPoints() + frame.debuffHolder:SetPoint("BOTTOM", frame, "TOP", PERSONAL_CONTENT_CENTER_X, 1 + debuffY) + end +end + +local function GetPersonalDebuffIconSize() + local size = BNP.GetIconSize and tonumber(BNP:GetIconSize()) or 18 + if not size then size = 18 end + if size < 12 then size = 12 end + if size > 20 then size = 20 end + return size +end + +local function FormatDebuffTimer(remaining) + remaining = tonumber(remaining) + if not remaining or remaining <= 0 then return "" end + if remaining >= 10 then return tostring(math.ceil(remaining)) end + return string.format("%.1f", remaining) +end + +local function ReadPlayerDebuff(index) + if type(C_UnitAuras) == "table" and type(C_UnitAuras.GetDebuffDataByIndex) == "function" then + local ok, aura = pcall(C_UnitAuras.GetDebuffDataByIndex, "player", index) + if not ok or not aura then return nil end + return aura.icon, aura.applications or aura.count or 0, aura.duration, aura.expirationTime, aura.name, aura.spellId or aura.spellID + end + + if type(C_UnitAuras) == "table" and type(C_UnitAuras.UnitDebuff) == "function" then + local name, icon, count, dispelType, duration, expirationTime, source, isStealable, nameplateShowPersonal, spellID = C_UnitAuras.UnitDebuff("player", index) + if not name then return nil end + return icon, count or 0, duration, expirationTime, name, spellID + end + + -- Legacy UnitDebuff on pure 1.12 usually has no reliable duration. Read it + -- only when duration/expiration are supplied by the custom client; otherwise + -- skip it rather than accidentally showing multi-minute effects. + if UnitDebuff then + local texture, count, dispelType, spellID, duration, expirationTime = UnitDebuff("player", index) + if not texture then return nil end + return texture, count or 0, duration, expirationTime, nil, spellID + end + return nil +end + +local function HidePersonalDebuffs(frame) + if not frame or not frame.debuffHolder then return end + local i + for i = 1, table.getn(frame.debuffHolder.icons or {}) do + local icon = frame.debuffHolder.icons[i] + icon.timer:SetText("") + icon.stack:SetText("") + icon:Hide() + end + frame.debuffHolder:Hide() +end + +local function UpdatePersonalDebuffs(frame, forceScan) + if not frame or not frame.debuffHolder then return end + if not (BNP.IsPersonalNameplateDebuffsEnabled and BNP:IsPersonalNameplateDebuffsEnabled()) then + frame.BNPPersonalDebuffEntries = nil + HidePersonalDebuffs(frame) + return + end + + local now = GetTime() + local entries = frame.BNPPersonalDebuffEntries + if forceScan or not entries then + entries = {} + local index + for index = 1, 64 do + local texture, count, duration, expirationTime, name, spellID = ReadPlayerDebuff(index) + if not texture then break end + + duration = tonumber(duration) or 0 + expirationTime = tonumber(expirationTime) or 0 + if duration > 0 and duration <= PERSONAL_DEBUFF_MAX_DURATION and expirationTime > now then + table.insert(entries, { + texture = texture, + count = tonumber(count) or 0, + duration = duration, + expirationTime = expirationTime, + name = name, + spellID = spellID, + }) + if table.getn(entries) >= PERSONAL_DEBUFF_MAX_ICONS then break end + end + end + frame.BNPPersonalDebuffEntries = entries + end + + local visible = 0 + local i + for i = 1, table.getn(entries) do + if entries[i].expirationTime and entries[i].expirationTime > now then + visible = visible + 1 + end + end + + if visible < 1 then + HidePersonalDebuffs(frame) + return + end + + local size = GetPersonalDebuffIconSize() + local totalWidth = visible * size + (visible - 1) * PERSONAL_DEBUFF_SPACING + local startX = -totalWidth / 2 + local displayIndex = 0 + for i = 1, table.getn(entries) do + local entry = entries[i] + if entry.expirationTime and entry.expirationTime > now then + displayIndex = displayIndex + 1 + local icon = frame.debuffHolder.icons[displayIndex] + if icon then + if icon.BNPLastSize ~= size then + icon:SetWidth(size) + icon:SetHeight(size) + icon.timer:SetFont(FONT, math.max(7, math.floor(size * 0.44 + 0.5)), "OUTLINE") + icon.stack:SetFont(FONT, math.max(6, math.floor(size * 0.38 + 0.5)), "OUTLINE") + icon.BNPLastSize = size + end + icon:ClearAllPoints() + icon:SetPoint("LEFT", frame.debuffHolder, "CENTER", startX + (displayIndex - 1) * (size + PERSONAL_DEBUFF_SPACING), 0) + if icon.BNPLastTexture ~= entry.texture then + icon.texture:SetTexture(entry.texture) + icon.BNPLastTexture = entry.texture + end + icon.timer:SetText(FormatDebuffTimer(entry.expirationTime - now)) + icon.stack:SetText(entry.count > 1 and tostring(entry.count) or "") + icon:Show() + end + end + end + + for i = displayIndex + 1, table.getn(frame.debuffHolder.icons) do + local icon = frame.debuffHolder.icons[i] + icon.timer:SetText("") + icon.stack:SetText("") + icon:Hide() + end + frame.debuffHolder:Show() +end + +local function UpdateValues(frame) + if not frame then return end + + local health = tonumber(UnitHealth("player")) or 0 + local maxHealth = tonumber(UnitHealthMax("player")) or 0 + if maxHealth < 1 then maxHealth = 1 end + if health < 0 then health = 0 end + if health > maxHealth then health = maxHealth end + frame.healthbar:SetMinMaxValues(0, maxHealth) + frame.healthbar:SetValue(health) + + -- Personal Health Text mirrors BNP's regular nameplate display modes, but + -- remains independently configurable so the player HUD does not have to + -- match enemy nameplates. + local healthMode = BNP.GetPersonalNameplateHealthTextMode and BNP:GetPersonalNameplateHealthTextMode() or "both" + local healthDisplay = nil + if healthMode == "percent" then + local pct = math.floor((health / maxHealth) * 100 + 0.5) + if pct < 0 then pct = 0 end + if pct > 100 then pct = 100 end + healthDisplay = tostring(pct) .. "%" + elseif healthMode == "hp" then + healthDisplay = FormatValue(health) + elseif healthMode == "both" then + local pct = math.floor((health / maxHealth) * 100 + 0.5) + if pct < 0 then pct = 0 end + if pct > 100 then pct = 100 end + healthDisplay = FormatValue(health) .. " | " .. tostring(pct) .. "%" + end + + if healthDisplay then + frame.healthText:SetText(healthDisplay) + frame.healthText:Show() + else + frame.healthText:SetText("") + frame.healthText:Hide() + end + + local power = tonumber(UnitMana("player")) or 0 + local maxPower = tonumber(UnitManaMax("player")) or 0 + if maxPower < 1 then maxPower = 1 end + if power < 0 then power = 0 end + if power > maxPower then power = maxPower end + frame.powerbar:SetMinMaxValues(0, maxPower) + frame.powerbar:SetValue(power) + frame.powerText:SetText(FormatValue(power) .. " / " .. FormatValue(maxPower)) + + local powerType = 0 + if UnitManaType then powerType = tonumber(UnitManaType("player")) or 0 end + local c = powerColors[powerType] or powerColors[0] + frame.powerbar:SetStatusBarColor(c[1], c[2], c[3]) + + local level = UnitLevel and UnitLevel("player") or nil + if level and level > 0 then frame.levelText:SetText(tostring(level)) else frame.levelText:SetText("") end +end + +local function OptionsPreviewActive() + return BNP.optionsFrame and BNP.optionsFrame.IsShown and BNP.optionsFrame:IsShown() +end + +local function ShouldShow() + if not (BNP.IsPersonalNameplateEnabled and BNP:IsPersonalNameplateEnabled()) then + return false + end + + if not (BNP.IsPersonalNameplateCombatOnly and BNP:IsPersonalNameplateCombatOnly()) then + return true + end + + -- Keep it visible while the options window is open so Scale/Y Offset can be + -- adjusted outside combat without needing a training dummy. + return inCombat or OptionsPreviewActive() +end + +local function Refresh() + local frame = CreatePersonalFrame() + UpdatePosition(frame) + UpdateStyle(frame) + UpdateValues(frame) + + if ShouldShow() then + frame:Show() + UpdatePersonalDebuffs(frame, true) + else + frame:Hide() + end +end + +function BNP:RefreshPersonalNameplate() + Refresh() +end + +function BNP:RefreshPersonalNameplateStyle() + if not personalFrame then return end + UpdateStyle(personalFrame) + UpdateValues(personalFrame) + if personalFrame:IsShown() then UpdatePersonalDebuffs(personalFrame, true) end +end + +local events = CreateFrame("Frame") +events:RegisterEvent("PLAYER_ENTERING_WORLD") +events:RegisterEvent("PLAYER_REGEN_DISABLED") +events:RegisterEvent("PLAYER_REGEN_ENABLED") +events:RegisterEvent("UNIT_HEALTH") +events:RegisterEvent("UNIT_MANA") +events:RegisterEvent("UNIT_AURA") +events:RegisterEvent("PLAYER_LEVEL_UP") + +events:SetScript("OnEvent", function() + if event == "PLAYER_REGEN_DISABLED" then + inCombat = true + Refresh() + return + elseif event == "PLAYER_REGEN_ENABLED" then + inCombat = false + Refresh() + return + elseif event == "PLAYER_ENTERING_WORLD" then + if UnitAffectingCombat then + inCombat = UnitAffectingCombat("player") and true or false + else + inCombat = false + end + Refresh() + return + end + + if (event == "UNIT_HEALTH" or event == "UNIT_MANA" or event == "UNIT_AURA") and arg1 and arg1 ~= "player" then + return + end + + if personalFrame then + UpdateValues(personalFrame) + UpdateStyle(personalFrame) + if event == "UNIT_AURA" and personalFrame:IsShown() then UpdatePersonalDebuffs(personalFrame, true) end + end +end) + +local updater = CreateFrame("Frame") +updater:SetScript("OnUpdate", function() + updateElapsed = updateElapsed + arg1 + if updateElapsed < 0.10 then return end + updateElapsed = 0 + + if not personalFrame then return end + + -- Cheap 10 Hz fallback for custom 1.12 clients that do not emit every modern + -- player-resource event consistently. It only runs value work while visible. + local shouldShow = ShouldShow() + if shouldShow then + if not personalFrame:IsShown() then + personalFrame:Show() + debuffScanElapsed = 0.50 + end + UpdateValues(personalFrame) + + debuffScanElapsed = debuffScanElapsed + 0.10 + local forceScan = false + if debuffScanElapsed >= 0.50 then + debuffScanElapsed = 0 + forceScan = true + end + UpdatePersonalDebuffs(personalFrame, forceScan) + elseif personalFrame:IsShown() then + personalFrame:Hide() + end +end) diff --git a/modules/scale.lua b/modules/scale.lua index ae74a72..e4b028b 100644 --- a/modules/scale.lua +++ b/modules/scale.lua @@ -179,10 +179,23 @@ end -- receives a real "mouseover" unit even though clicks are forwarded to the -- original Blizzard nameplate. local function GetPlateMouseoverUnit(plate) - if not plate or not plate.GetName then return nil end + if not plate then return nil end + -- Prefer ClassicAPI's exact nameplateN token when it is available. Besides + -- being stable for the lifetime of the projected plate, this is a normal + -- unit token and gives GameTooltip the same reaction/hostility information + -- it receives from Blizzard unit frames. + local classicToken = plate.BNPClassicUnitToken + if classicToken and UnitExists then + local existsOK, exists = pcall(UnitExists, classicToken) + if existsOK and exists then return classicToken end + end + + -- SuperWoW fallback: frame:GetName(1) returns the GUID attached to the + -- nameplate. SuperWoW extends unit-taking functions to accept that GUID. + if not plate.GetName then return nil end local ok, unit = pcall(function() return plate:GetName(1) end) - if not ok or not unit then return nil end + if not ok or not unit or unit == "" then return nil end if UnitExists then local existsOK, exists = pcall(UnitExists, unit) @@ -195,17 +208,23 @@ end local function ShowPlateTooltip(wrapper, unit) if not wrapper or not unit or not GameTooltip or not GameTooltip.SetUnit then return end - -- SetMouseoverUnit() has already made "mouseover" a valid unit. Prefer - -- that token so tooltip addons see the same unit surface as mouseover macros. if GameTooltip_SetDefaultAnchor then GameTooltip_SetDefaultAnchor(GameTooltip, wrapper) elseif GameTooltip.SetOwner then GameTooltip:SetOwner(wrapper, "ANCHOR_CURSOR") end - local ok = pcall(function() GameTooltip:SetUnit("mouseover") end) + -- Do NOT build the tooltip from the synthetic "mouseover" token. + -- SetMouseoverUnit() is still required for mouseover macros/addons, but on + -- some SuperWoW/ClassicAPI combinations the synthetic token can briefly + -- expose stale reaction data. That is visible as a hostile NPC name + -- flickering between yellow and red in GameTooltip. Feed GameTooltip the + -- exact plate unit instead (nameplateN when possible, GUID otherwise), just + -- like established custom unit-frame implementations do. + local ok = pcall(function() GameTooltip:SetUnit(unit) end) if not ok then - pcall(function() GameTooltip:SetUnit(unit) end) + -- Compatibility fallback for older clients that cannot tooltip a GUID. + pcall(function() GameTooltip:SetUnit("mouseover") end) end if GameTooltip.Show then GameTooltip:Show() end diff --git a/modules/visualguard.lua b/modules/visualguard.lua index 36e3b39..8c35545 100644 --- a/modules/visualguard.lua +++ b/modules/visualguard.lua @@ -174,6 +174,7 @@ function BNP:RefreshNameplateBorderStyle() if self.RefreshEliteDragonVisibility then self:RefreshEliteDragonVisibility() end + if self.RefreshPersonalNameplateStyle then self:RefreshPersonalNameplateStyle() end end function BNP:RefreshNameplateLevelVisibility() @@ -185,6 +186,7 @@ function BNP:RefreshNameplateLevelVisibility() if BNP.RefreshImmunityLayoutForPlate then BNP:RefreshImmunityLayoutForPlate(plate) end end end + if self.RefreshPersonalNameplateStyle then self:RefreshPersonalNameplateStyle() end end function BNP:RefreshHealthbarBackground() @@ -192,6 +194,7 @@ function BNP:RefreshHealthbarBackground() for plate in pairs(BNP.plates or {}) do ApplyHealthbarBackground(plate) end + if self.RefreshPersonalNameplateStyle then self:RefreshPersonalNameplateStyle() end end if BNP.libnameplate then