Add files via upload

This commit is contained in:
Wurmschwanz
2026-10-01 23:32:25 +02:00
committed by GitHub
parent a2672ef1d8
commit b9ce2b07c3
7 changed files with 936 additions and 7 deletions
+1
View File
@@ -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
+64
View File
@@ -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
+229 -1
View File
@@ -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)
+1
View File
@@ -168,6 +168,7 @@ function BNP:RefreshClassColors()
end
end
end
if self.RefreshPersonalNameplateStyle then self:RefreshPersonalNameplateStyle() end
end
local function EnforceClassColor(plate)
+613
View File
@@ -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)
+25 -6
View File
@@ -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
+3
View File
@@ -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