Files
BlizzNameplatesPlus/core/options.lua
T
2026-10-03 20:10:34 +02:00

3367 lines
148 KiB
Lua

BNP = BNP or {}
local _, optionsPlayerClass = UnitClass("player")
local comboOptionsClass = (optionsPlayerClass == "ROGUE" or optionsPlayerClass == "DRUID")
local function Round(value, step)
return math.floor((value / step) + 0.5) * step
end
local function RoundSignedInteger(value)
value = tonumber(value) or 0
if value >= 0 then return math.floor(value + 0.5) end
return math.ceil(value - 0.5)
end
local sliderCount = 0
local function CreateSlider(parent, label, minValue, maxValue, step, y, x, width)
sliderCount = sliderCount + 1
local slider = CreateFrame("Slider", "BNPOptionsSlider" .. sliderCount, parent, "OptionsSliderTemplate")
slider:SetPoint("TOPLEFT", parent, "TOPLEFT", x or 28, y)
slider:SetWidth(width or 220)
slider:SetHeight(16)
slider:SetMinMaxValues(minValue, maxValue)
slider:SetValueStep(step)
-- Modern BNP menu: keep the useful value/title, remove the noisy min/max
-- labels from Blizzard's old slider template.
local low = getglobal(slider:GetName() .. "Low")
local high = getglobal(slider:GetName() .. "High")
local title = getglobal(slider:GetName() .. "Text")
if low then low:SetText(""); low:Hide() end
if high then high:SetText(""); high:Hide() end
if title then
title:SetText(label)
title:SetTextColor(0.90, 0.92, 0.95)
end
return slider
end
local function CreateCheck(parent, label, y, onclick, x)
local check = CreateFrame("CheckButton", nil, parent, "UICheckButtonTemplate")
check:SetPoint("TOPLEFT", parent, "TOPLEFT", x or 22, y)
check:SetWidth(24)
check:SetHeight(24)
local text = parent:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
text:SetPoint("LEFT", check, "RIGHT", 4, 0)
text:SetText(label)
text:SetTextColor(0.90, 0.92, 0.95)
check.BNPLabel = text
check:SetScript("OnClick", onclick)
return check
end
local function SetColorSwatchColor(button, r, g, b)
if not button then return end
if button.BNPColorTexture then
button.BNPColorTexture:SetVertexColor(r or 1, g or 1, b or 1, 1)
elseif button.SetBackdropColor then
button:SetBackdropColor(r or 1, g or 1, b or 1, 1)
end
end
local function PrepareBNPColorPicker()
if not ColorPickerFrame then return end
-- BNP's options window lives on DIALOG. Put the Blizzard color picker on a
-- higher strata, but do NOT force a high frame level on the parent itself.
-- Raising only the parent frame level can leave Blizzard's native Okay /
-- Cancel buttons underneath its mouse layer on old 1.12 clients.
if ColorPickerFrame.SetFrameStrata then
ColorPickerFrame:SetFrameStrata("FULLSCREEN_DIALOG")
end
if ColorPickerFrame.SetMovable then
ColorPickerFrame:SetMovable(true)
end
if ColorPickerFrame.SetClampedToScreen then
ColorPickerFrame:SetClampedToScreen(true)
end
if ColorPickerFrame.EnableMouse then
ColorPickerFrame:EnableMouse(true)
end
-- Never let the picker become a modal keyboard owner. On some 1.12
-- clients the Blizzard color picker can otherwise swallow movement keys
-- and action-bar binds while it is visible. Mouse interaction remains
-- enabled so the wheel, buttons and background dragging still work.
if ColorPickerFrame.EnableKeyboard then
ColorPickerFrame:EnableKeyboard(false)
end
-- Remove the old separate drag header if this code is loaded over a build
-- that already created it during the same UI session.
if BNPColorPickerDragHandle then
BNPColorPickerDragHandle:Hide()
BNPColorPickerDragHandle:EnableMouse(false)
end
-- Do NOT make the ColorPickerFrame itself draggable with the left mouse
-- button. On the old client the color wheel/value slider are handled by the
-- ColorSelect frame itself, so a parent OnMouseDown steals the drag from the
-- color selector. Instead create transparent drag zones only over genuinely
-- unused background areas. This keeps the picker easy to move while the
-- color point and brightness slider remain fully interactive.
if not ColorPickerFrame.BNPDragInstalled then
ColorPickerFrame.BNPDragInstalled = true
local function CreatePickerDragZone(name)
local zone = CreateFrame("Frame", name, ColorPickerFrame)
zone:EnableMouse(true)
if zone.SetFrameLevel and ColorPickerFrame.GetFrameLevel then
zone:SetFrameLevel(ColorPickerFrame:GetFrameLevel() + 5)
end
zone:SetScript("OnMouseDown", function()
if arg1 == "LeftButton" and ColorPickerFrame.StartMoving then
ColorPickerFrame:StartMoving()
end
end)
zone:SetScript("OnMouseUp", function()
if ColorPickerFrame.StopMovingOrSizing then
ColorPickerFrame:StopMovingOrSizing()
end
end)
return zone
end
-- Top/title area: full width, safely above the actual color controls.
local top = CreatePickerDragZone("BNPColorPickerDragTop")
top:SetPoint("TOPLEFT", ColorPickerFrame, "TOPLEFT", 6, -4)
top:SetPoint("TOPRIGHT", ColorPickerFrame, "TOPRIGHT", -6, -4)
top:SetHeight(28)
-- Right-side empty panel around the preview swatch. This gives a second,
-- large place to grab the window without covering the wheel or slider.
local side = CreatePickerDragZone("BNPColorPickerDragSide")
side:SetPoint("TOPRIGHT", ColorPickerFrame, "TOPRIGHT", -6, -34)
side:SetWidth(58)
side:SetHeight(92)
ColorPickerFrame.BNPDragTop = top
ColorPickerFrame.BNPDragSide = side
end
-- Keep the three action buttons compact and separated so they stay fully
-- clickable on the old client. The default Blizzard button widths are a bit
-- too large once BNP adds Reset, which caused overlap in practice.
if ColorPickerOkayButton then
ColorPickerOkayButton:ClearAllPoints()
ColorPickerOkayButton:SetWidth(62)
ColorPickerOkayButton:SetHeight(18)
ColorPickerOkayButton:SetPoint("BOTTOMLEFT", ColorPickerFrame, "BOTTOMLEFT", 10, 12)
end
if ColorPickerCancelButton then
ColorPickerCancelButton:ClearAllPoints()
ColorPickerCancelButton:SetWidth(62)
ColorPickerCancelButton:SetHeight(18)
ColorPickerCancelButton:SetPoint("BOTTOMRIGHT", ColorPickerFrame, "BOTTOMRIGHT", -10, 12)
end
-- Reset lives directly on the picker and gets its own centered slot between
-- Okay and Cancel, so no button overlaps another one.
if not BNPColorPickerResetButton then
local reset = CreateFrame("Button", "BNPColorPickerResetButton", ColorPickerFrame, "UIPanelButtonTemplate")
reset:SetText("Reset")
reset:SetScript("OnClick", function()
local r = ColorPickerFrame.BNPResetR
local g = ColorPickerFrame.BNPResetG
local b = ColorPickerFrame.BNPResetB
local setter = ColorPickerFrame.BNPActiveColorSetter
local swatch = ColorPickerFrame.BNPActiveColorSwatch
if r == nil or g == nil or b == nil or not setter then return end
ColorPickerFrame:SetColorRGB(r, g, b)
setter(r, g, b)
if swatch and swatch.RefreshColor then swatch:RefreshColor() end
end)
reset:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetText("Reset Color", 1, 0.82, 0)
GameTooltip:AddLine(ColorPickerFrame.BNPResetTooltip or "Restores BNP's default color for this Invert Tank state.", 1, 1, 1, true)
GameTooltip:Show()
end)
reset:SetScript("OnLeave", function() GameTooltip:Hide() end)
end
BNPColorPickerResetButton:ClearAllPoints()
BNPColorPickerResetButton:SetWidth(46)
BNPColorPickerResetButton:SetHeight(18)
BNPColorPickerResetButton:SetPoint("BOTTOM", ColorPickerFrame, "BOTTOM", 0, 12)
end
local function CreateColorSwatch(parent, label, y, x, getColor, setColor, defaultR, defaultG, defaultB, tooltipLine, resetTooltip)
local text = parent:CreateFontString(nil, "OVERLAY", "GameFontNormal")
text:SetPoint("TOPLEFT", parent, "TOPLEFT", x or 22, y - 2)
text:SetText(label)
-- Use explicit textures instead of a backdrop here. This is more reliable on
-- old 1.12 clients and guarantees that the user actually sees a color box.
local swatch = CreateFrame("Button", nil, parent)
swatch:SetWidth(24)
swatch:SetHeight(24)
swatch:SetPoint("LEFT", text, "RIGHT", 8, 0)
local border = swatch:CreateTexture(nil, "BACKGROUND")
border:SetAllPoints(swatch)
border:SetTexture(0.10, 0.10, 0.10, 1)
local color = swatch:CreateTexture(nil, "ARTWORK")
color:SetPoint("TOPLEFT", swatch, "TOPLEFT", 3, -3)
color:SetPoint("BOTTOMRIGHT", swatch, "BOTTOMRIGHT", -3, 3)
color:SetTexture("Interface\\ChatFrame\\ChatFrameBackground")
swatch.BNPColorTexture = color
local shine = swatch:CreateTexture(nil, "OVERLAY")
shine:SetPoint("TOPLEFT", swatch, "TOPLEFT", 2, -2)
shine:SetWidth(18)
shine:SetHeight(1)
shine:SetTexture(0.85, 0.85, 0.85, 0.9)
function swatch:RefreshColor()
local r, g, b = getColor()
SetColorSwatchColor(self, r, g, b)
end
swatch:SetScript("OnClick", function()
local r, g, b = getColor()
local oldR, oldG, oldB = r, g, b
PrepareBNPColorPicker()
ColorPickerFrame:Hide()
ColorPickerFrame.BNPResetR = defaultR
ColorPickerFrame.BNPResetG = defaultG
ColorPickerFrame.BNPResetB = defaultB
ColorPickerFrame.BNPActiveColorSetter = setColor
ColorPickerFrame.BNPActiveColorSwatch = swatch
ColorPickerFrame.BNPResetTooltip = resetTooltip
ColorPickerFrame.func = nil
ColorPickerFrame.opacityFunc = nil
ColorPickerFrame.cancelFunc = nil
ColorPickerFrame.hasOpacity = false
ColorPickerFrame:SetColorRGB(r, g, b)
ColorPickerFrame.func = function()
local nr, ng, nb = ColorPickerFrame:GetColorRGB()
setColor(nr, ng, nb)
swatch:RefreshColor()
end
ColorPickerFrame.cancelFunc = function()
setColor(oldR, oldG, oldB)
swatch:RefreshColor()
end
ColorPickerFrame:Show()
-- Blizzard's OnShow may restore keyboard input on old clients, so force
-- it off after the frame is actually visible as well.
if ColorPickerFrame.EnableKeyboard then ColorPickerFrame:EnableKeyboard(false) end
if ColorPickerFrame.Raise then ColorPickerFrame:Raise() end
end)
swatch:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetText(label, 1, 0.82, 0)
GameTooltip:AddLine(tooltipLine or "Click the color box to choose the color used while Invert Tank Colors is enabled.", 1, 1, 1, true)
GameTooltip:AddLine("The color picker opens above this menu and can be dragged from any free background area.", 0.8, 0.8, 0.8, true)
GameTooltip:Show()
end)
swatch:SetScript("OnLeave", function() GameTooltip:Hide() end)
swatch.BNPLabel = text
swatch:RefreshColor()
return swatch, text
end
-- 1.12-safe emergency recorder ------------------------------------------------
--
-- Keep a compact recorder directly in this already proven options file. The
-- full diagnostic module replaces BNP.OpenAuraRecorder later when it loads.
-- If an old/private 1.12 client refuses that larger module, this fallback still
-- provides the window and the ClassicAPI aura data we actually need.
local emergency = BNP.emergencyAuraRecorder or {}
BNP.emergencyAuraRecorder = emergency
emergency.lines = emergency.lines or {}
emergency.active = false
local function EmergencyValue(value, limit)
if value == nil then return "nil" end
local text = tostring(value)
text = string.gsub(text, "\r", "\\r")
text = string.gsub(text, "\n", "\\n")
text = string.gsub(text, "\t", "\\t")
limit = limit or 240
if string.len(text) > limit then text = string.sub(text, 1, limit) .. "..." end
return text
end
local function EmergencyAppend(text)
text = EmergencyValue(text, 1300)
if string.len(table.concat(emergency.lines, "\n")) + string.len(text) > 60000 then
if not emergency.limitHit then
table.insert(emergency.lines, "[REPORT LIMIT REACHED - recording stopped]")
emergency.limitHit = true
emergency.active = false
end
return
end
table.insert(emergency.lines, text)
emergency.dirty = true
end
local function EmergencyFields(data)
if type(data) ~= "table" then return EmergencyValue(data) end
local fields = {}
local key, value
for key, value in pairs(data) do
local kind = type(value)
if kind ~= "table" and kind ~= "function" and kind ~= "userdata" then
table.insert(fields, EmergencyValue(key, 80) .. "=" .. EmergencyValue(value, 180))
end
end
table.sort(fields)
return table.concat(fields, " | ")
end
local function EmergencyGUID(unit)
local exists, guid = UnitExists(unit)
if exists and guid then return guid end
if UnitGUID then
local ok, value = pcall(UnitGUID, unit)
if ok then return value end
end
return nil
end
local function EmergencyWantedAura(aura)
if type(aura) ~= "table" then return true end
local spellID = tonumber(aura.spellId or aura.spellID)
local name = string.lower(tostring(aura.name or ""))
if spellID == 17794 or spellID == 17797 or spellID == 17798 or spellID == 17799 or spellID == 17800 then return true end
if name == "shadow vulnerability" or name == "schattenverwundbarkeit" then return true end
local playerGUID = EmergencyGUID("player")
if aura.sourceGUID and playerGUID then return aura.sourceGUID == playerGUID end
if aura.sourceUnit then return aura.sourceUnit == "player" end
return false
end
local function EmergencyIsShadowBolt(spellID, spellName)
local numeric = tonumber(spellID)
if numeric == 686 or numeric == 695 or numeric == 705 or numeric == 1088 or
numeric == 1106 or numeric == 7641 or numeric == 11659 or numeric == 11660 or
numeric == 11661 or numeric == 25307 then return true end
local name = string.lower(tostring(spellName or ""))
return name == "shadow bolt" or name == "schattenblitz"
end
local function EmergencyRefresh()
if not emergency.edit then return end
emergency.edit:SetText(table.concat(emergency.lines, "\n"))
emergency.edit:SetHeight(math.max(300, table.getn(emergency.lines) * 16 + 30))
if emergency.status then
if emergency.active then
emergency.status:SetText("RECORDING | Target: " .. EmergencyValue(UnitName("target") or "none", 100))
emergency.status:SetTextColor(0.2, 1, 0.35)
else
emergency.status:SetText("STOPPED | " .. tostring(table.getn(emergency.lines)) .. " lines")
emergency.status:SetTextColor(1, 0.82, 0)
end
end
emergency.dirty = false
end
local function EmergencySnapshot(reason, force)
if not UnitExists("target") then
if force then EmergencyAppend("AURA_SNAPSHOT | reason=" .. reason .. " | no target") end
return
end
local auraLines = {}
local signatureParts = {}
local count = 0
local classic = type(C_UnitAuras) == "table" and type(C_UnitAuras.GetDebuffDataByIndex) == "function"
local i
if classic then
for i = 1, 64 do
local ok, aura = pcall(C_UnitAuras.GetDebuffDataByIndex, "target", i)
if not ok then
EmergencyAppend("CLASSICAPI_ERROR | " .. EmergencyValue(aura, 800))
break
end
if not aura then break end
if EmergencyWantedAura(aura) then
count = count + 1
local fields = EmergencyFields(aura)
table.insert(signatureParts, fields)
table.insert(auraLines, " CLASSIC_AURA " .. tostring(i) .. " | " .. fields)
end
end
else
for i = 1, 64 do
local ok, texture, stacks, dispelType, spellID, r5, r6, r7, r8, r9, r10 = pcall(UnitDebuff, "target", i)
if not ok or not texture then break end
count = count + 1
local fields = "spellId=" .. EmergencyValue(spellID) ..
" | stacks=" .. EmergencyValue(stacks) ..
" | type=" .. EmergencyValue(dispelType) ..
" | texture=" .. EmergencyValue(texture, 160) ..
" | r5=" .. EmergencyValue(r5) .. " | r6=" .. EmergencyValue(r6) ..
" | r7=" .. EmergencyValue(r7) .. " | r8=" .. EmergencyValue(r8) ..
" | r9=" .. EmergencyValue(r9) .. " | r10=" .. EmergencyValue(r10)
table.insert(signatureParts, fields)
table.insert(auraLines, " LEGACY_AURA " .. tostring(i) .. " | " .. fields)
end
end
local signature = table.concat(signatureParts, "||")
if emergency.baselineOnly then
emergency.lastSignature = signature
return
end
if not force and signature == emergency.lastSignature then return end
emergency.lastSignature = signature
EmergencyAppend(
"[+" .. string.format("%.3f", GetTime() - (emergency.startedAt or GetTime())) .. "] AURA_SNAPSHOT" ..
" | reason=" .. EmergencyValue(reason) ..
" | guid=" .. EmergencyValue(EmergencyGUID("target")) ..
" | source=" .. (classic and "ClassicAPI" or "UnitDebuff") ..
" | count=" .. tostring(count)
)
if count == 0 then
EmergencyAppend(" AURA none")
else
for i = 1, table.getn(auraLines) do EmergencyAppend(auraLines[i]) end
end
end
local function EmergencyUnregister()
if emergency.eventFrame then pcall(emergency.eventFrame.UnregisterAllEvents, emergency.eventFrame) end
end
local function EmergencyStop(reason)
if not emergency.active then EmergencyRefresh() return end
EmergencySnapshot("final", true)
emergency.active = false
EmergencyUnregister()
if emergency.frame then emergency.frame:SetScript("OnUpdate", nil) end
EmergencyAppend("=== END | reason=" .. EmergencyValue(reason or "user") .. " ===")
EmergencyRefresh()
end
local function EmergencyStart()
EmergencyUnregister()
emergency.lines = {}
emergency.limitHit = false
emergency.lastSignature = nil
emergency.startedAt = GetTime()
emergency.elapsed = 0
emergency.active = true
if emergency.frame and emergency.onUpdate then emergency.frame:SetScript("OnUpdate", emergency.onUpdate) end
EmergencyAppend("BNP_CLASSICAPI_AURA_REPORT v2")
EmergencyAppend(
"ClassicAPI | version=" .. EmergencyValue(CLASSIC_API_VERSION or "not detected") ..
" | C_UnitAuras=" .. ((type(C_UnitAuras) == "table" and type(C_UnitAuras.GetDebuffDataByIndex) == "function") and "yes" or "no") ..
" | CopyToClipboard=" .. (CopyToClipboard and "yes" or "no")
)
EmergencyAppend("Aura filter | own auras + Shadow Vulnerability from any Warlock")
EmergencyAppend("Target | name=" .. EmergencyValue(UnitName("target")) .. " | guid=" .. EmergencyValue(EmergencyGUID("target")))
EmergencyAppend("Test | proc aura, refresh it while active, then let it expire")
EmergencyAppend("=== LIVE RECORDING ===")
local events = {
"UNIT_CASTEVENT", "UNIT_AURA", "PLAYER_TARGET_CHANGED",
"UNIT_SPELLCAST_SENT", "UNIT_SPELLCAST_START", "UNIT_SPELLCAST_STOP",
"UNIT_SPELLCAST_SUCCEEDED", "UNIT_SPELLCAST_CHANNEL_START", "UNIT_SPELLCAST_CHANNEL_STOP",
}
local i
for i = 1, table.getn(events) do pcall(emergency.eventFrame.RegisterEvent, emergency.eventFrame, events[i]) end
-- Learn the target's existing auras without printing them. Only changes
-- occurring after Start Recording belong in the diagnostic report.
emergency.baselineOnly = true
EmergencySnapshot("baseline", false)
emergency.baselineOnly = false
EmergencyRefresh()
end
local function EmergencyCreateWindow()
if emergency.frame then return emergency.frame end
local frame = CreateFrame("Frame", "BNPEmergencyAuraRecorderFrame", UIParent)
frame:SetWidth(720)
frame:SetHeight(520)
frame:SetPoint("CENTER", UIParent, "CENTER", 0, 20)
frame:SetFrameStrata("DIALOG")
frame:SetBackdrop({
bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background",
edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border",
tile = true, tileSize = 32, edgeSize = 32,
insets = { left = 11, right = 12, top = 12, bottom = 11 }
})
frame:SetMovable(true)
frame:EnableMouse(true)
frame:RegisterForDrag("LeftButton")
frame:SetScript("OnDragStart", function() this:StartMoving() end)
frame:SetScript("OnDragStop", function() this:StopMovingOrSizing() end)
frame:SetScript("OnHide", function() EmergencyStop("window closed") end)
emergency.frame = frame
local title = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
title:SetPoint("TOP", frame, "TOP", 0, -18)
title:SetText("Missing Spell / Aura Recorder")
local status = frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
status:SetPoint("TOPLEFT", frame, "TOPLEFT", 26, -50)
status:SetWidth(660)
status:SetJustifyH("LEFT")
emergency.status = status
local start = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
start:SetPoint("TOPLEFT", frame, "TOPLEFT", 26, -76)
start:SetWidth(126)
start:SetHeight(24)
start:SetText("Start Recording")
start:SetScript("OnClick", EmergencyStart)
local stop = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
stop:SetPoint("LEFT", start, "RIGHT", 8, 0)
stop:SetWidth(82)
stop:SetHeight(24)
stop:SetText("Stop")
stop:SetScript("OnClick", function() EmergencyStop("user") end)
local copy = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
copy:SetPoint("LEFT", stop, "RIGHT", 8, 0)
copy:SetWidth(110)
copy:SetHeight(24)
copy:SetText(CopyToClipboard and "Copy Report" or "Select All")
copy:SetScript("OnClick", function()
EmergencyRefresh()
local report = table.concat(emergency.lines, "\n")
if CopyToClipboard and pcall(CopyToClipboard, report) then
emergency.status:SetText("COPIED | Paste with Ctrl+V")
emergency.status:SetTextColor(0.2, 1, 0.35)
else
emergency.edit:SetFocus()
emergency.edit:HighlightText()
emergency.status:SetText("SELECTED | Press Ctrl+C")
end
end)
local close = CreateFrame("Button", nil, frame, "UIPanelCloseButton")
close:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -5, -5)
local scroll = CreateFrame("ScrollFrame", "BNPEmergencyAuraRecorderScroll", frame, "UIPanelScrollFrameTemplate")
scroll:SetPoint("TOPLEFT", frame, "TOPLEFT", 26, -112)
scroll:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -48, 30)
local edit = CreateFrame("EditBox", "BNPEmergencyAuraRecorderEdit", scroll)
edit:SetWidth(640)
edit:SetHeight(300)
edit:SetMultiLine(true)
edit:SetAutoFocus(false)
edit:SetFontObject(ChatFontNormal)
edit:SetMaxLetters(65000)
edit:SetScript("OnEscapePressed", function() this:ClearFocus() end)
scroll:SetScrollChild(edit)
emergency.edit = edit
emergency.eventFrame = CreateFrame("Frame", "BNPEmergencyAuraRecorderEvents")
emergency.eventFrame:SetScript("OnEvent", function()
if not emergency.active then return end
if event == "UNIT_AURA" and arg1 ~= "target" then return end
if event == "UNIT_CASTEVENT" and arg1 ~= EmergencyGUID("player") then return end
EmergencyAppend(
"[+" .. string.format("%.3f", GetTime() - emergency.startedAt) .. "] EVENT | " .. EmergencyValue(event) ..
" | a1=" .. EmergencyValue(arg1) .. " | a2=" .. EmergencyValue(arg2) ..
" | a3=" .. EmergencyValue(arg3) .. " | a4=" .. EmergencyValue(arg4) ..
" | a5=" .. EmergencyValue(arg5) .. " | a6=" .. EmergencyValue(arg6)
)
emergency.forceSnapshot = event == "PLAYER_TARGET_CHANGED" or
(event == "UNIT_CASTEVENT" and EmergencyIsShadowBolt(arg4)) or
(event == "UNIT_SPELLCAST_SENT" and EmergencyIsShadowBolt(arg4, arg5)) or
(event ~= "UNIT_SPELLCAST_SENT" and string.find(tostring(event or ""), "UNIT_SPELLCAST_", 1, true) and EmergencyIsShadowBolt(arg3, arg4))
end)
emergency.onUpdate = function()
if not emergency.active then return end
emergency.elapsed = (emergency.elapsed or 0) + arg1
if emergency.elapsed >= 0.12 then
emergency.elapsed = 0
EmergencySnapshot(emergency.forceSnapshot and "event" or "poll", emergency.forceSnapshot)
emergency.forceSnapshot = false
if emergency.dirty then EmergencyRefresh() end
end
end
return frame
end
-- This remains available even if the full recorder loads, so that module can
-- fall back when a private client rejects one of the larger window calls.
function BNP:OpenEmergencyAuraRecorder()
local frame = EmergencyCreateWindow()
frame:Show()
EmergencyRefresh()
end
function BNP:StartEmergencyAuraRecorder()
EmergencyStart()
end
-- This is intentionally defined before the large optional recorder module.
-- A successful module load overwrites it; otherwise the button still works.
function BNP:OpenAuraRecorder()
self:OpenEmergencyAuraRecorder()
end
local function BuildOtherDebuffsOptions(frame, otherPage, createSection)
createSection(otherPage, "Other Class Debuffs", -4)
local otherDebuffsEnabled = CreateCheck(otherPage, "Enable Other Class Debuffs", -28, function()
BNP_DB.otherDebuffs = this:GetChecked() and true or false
if BNP.RefreshOtherDebuffs then BNP:RefreshOtherDebuffs() end
if frame.RefreshOtherDebuffPage then frame:RefreshOtherDebuffPage() end
end, 22)
otherDebuffsEnabled:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetText("Other Class Debuffs", 1, 0.82, 0)
GameTooltip:AddLine("Shows selected non-CC debuffs applied by other players/classes. The feature is off by default and never changes BNP's own-cast tracking.", 1, 1, 1, true)
GameTooltip:Show()
end)
otherDebuffsEnabled:SetScript("OnLeave", function() GameTooltip:Hide() end)
frame.otherDebuffsEnabledCheck = otherDebuffsEnabled
local otherNote = otherPage:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
otherNote:SetPoint("TOPLEFT", otherPage, "TOPLEFT", 28, -58)
otherNote:SetWidth(390)
otherNote:SetJustifyH("LEFT")
otherNote:SetText("Select which non-CC foreign debuffs BNP should display. Crowd Control stays in BNP's dedicated CC system.")
local OTHER_TAB_W = 80
local OTHER_TAB_H = 20
local OTHER_TAB_GAP = 3
local OTHER_TAB_X = 5
local OTHER_TAB_Y = -90
frame.otherDebuffClassTabs = {}
frame.otherSelectedClass = "warrior"
local classOrder = BNP.OtherDebuffClassOrder or { "warrior", "rogue", "hunter", "mage", "warlock", "priest", "druid", "shaman", "paladin" }
local classLabels = BNP.OtherDebuffClassLabels or {}
local classColors = BNP.OtherDebuffClassColors or {}
local i
for i = 1, table.getn(classOrder) do
local classKey = classOrder[i]
local tab = CreateFrame("Button", nil, otherPage)
tab:SetWidth(OTHER_TAB_W)
tab:SetHeight(OTHER_TAB_H)
tab:EnableMouse(true)
if i <= 5 then
tab:SetPoint("TOPLEFT", otherPage, "TOPLEFT", OTHER_TAB_X + (i - 1) * (OTHER_TAB_W + OTHER_TAB_GAP), OTHER_TAB_Y)
else
tab:SetPoint("TOPLEFT", otherPage, "TOPLEFT", OTHER_TAB_X + (i - 6) * (OTHER_TAB_W + OTHER_TAB_GAP), OTHER_TAB_Y - OTHER_TAB_H - 3)
end
local color = classColors[classKey] or { 0.6, 0.6, 0.6 }
local bg = tab:CreateTexture(nil, "BACKGROUND")
bg:SetAllPoints(tab)
bg:SetTexture(color[1] * 0.4, color[2] * 0.4, color[3] * 0.4, 0.85)
tab.BNPBG = bg
local text = tab:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
text:SetPoint("CENTER", tab, "CENTER", 0, 0)
text:SetText(classLabels[classKey] or classKey)
text:SetTextColor(color[1], color[2], color[3])
tab.BNPText = text
tab.BNPClassKey = classKey
tab:SetScript("OnClick", function()
frame.otherSelectedClass = this.BNPClassKey
if frame.RefreshOtherDebuffPage then frame:RefreshOtherDebuffPage() end
end)
tab:SetScript("OnEnter", function()
local c = classColors[this.BNPClassKey] or { 0.6, 0.6, 0.6 }
if this.BNPBG then this.BNPBG:SetTexture(c[1] * 0.6, c[2] * 0.6, c[3] * 0.6, 1) end
end)
tab:SetScript("OnLeave", function()
local c = classColors[this.BNPClassKey] or { 0.6, 0.6, 0.6 }
local selected = frame.otherSelectedClass == this.BNPClassKey
if this.BNPBG then
if selected then this.BNPBG:SetTexture(c[1] * 0.55, c[2] * 0.55, c[3] * 0.55, 1)
else this.BNPBG:SetTexture(c[1] * 0.4, c[2] * 0.4, c[3] * 0.4, 0.85) end
end
end)
frame.otherDebuffClassTabs[classKey] = tab
end
local selectedClassLabel = otherPage:CreateFontString(nil, "OVERLAY", "GameFontNormal")
selectedClassLabel:SetPoint("TOPLEFT", otherPage, "TOPLEFT", 14, -147)
selectedClassLabel:SetText("Warrior")
frame.otherSelectedClassLabel = selectedClassLabel
local enableClassButton = CreateFrame("Button", nil, otherPage, "UIPanelButtonTemplate")
enableClassButton:SetWidth(92)
enableClassButton:SetHeight(22)
enableClassButton:SetPoint("TOPRIGHT", otherPage, "TOPRIGHT", -112, -140)
enableClassButton:SetText("Enable All")
enableClassButton:SetScript("OnClick", function()
BNP:SetAllOtherDebuffsEnabled(true)
if BNP.RefreshOtherDebuffs then BNP:RefreshOtherDebuffs() end
if frame.RefreshOtherDebuffPage then frame:RefreshOtherDebuffPage() end
end)
local disableClassButton = CreateFrame("Button", nil, otherPage, "UIPanelButtonTemplate")
disableClassButton:SetWidth(92)
disableClassButton:SetHeight(22)
disableClassButton:SetPoint("TOPRIGHT", otherPage, "TOPRIGHT", -14, -140)
disableClassButton:SetText("Disable All")
disableClassButton:SetScript("OnClick", function()
BNP:SetAllOtherDebuffsEnabled(false)
if BNP.RefreshOtherDebuffs then BNP:RefreshOtherDebuffs() end
if frame.RefreshOtherDebuffPage then frame:RefreshOtherDebuffPage() end
end)
frame.otherEnableClassButton = enableClassButton
frame.otherDisableClassButton = disableClassButton
frame.otherDebuffRows = {}
for i = 1, 12 do
local row = CreateFrame("Frame", nil, otherPage)
row:SetWidth(405)
row:SetHeight(28)
row:SetPoint("TOPLEFT", otherPage, "TOPLEFT", 14, -174 - ((i - 1) * 29))
local icon = row:CreateTexture(nil, "ARTWORK")
icon:SetWidth(20)
icon:SetHeight(20)
icon:SetPoint("LEFT", row, "LEFT", 2, 0)
icon:SetTexture("Interface\\Icons\\INV_Misc_QuestionMark")
row.BNPIcon = icon
local check = CreateFrame("CheckButton", nil, row, "UICheckButtonTemplate")
check:SetWidth(24)
check:SetHeight(24)
check:SetPoint("LEFT", icon, "RIGHT", 5, 0)
row.BNPCheck = check
local label = row:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
label:SetPoint("LEFT", check, "RIGHT", 3, 0)
label:SetWidth(300)
label:SetJustifyH("LEFT")
row.BNPLabel = label
check:SetScript("OnClick", function()
if not this.BNPDebuffKey then return end
BNP:SetOtherDebuffSelected(this.BNPDebuffKey, this:GetChecked() and true or false)
if BNP.RefreshOtherDebuffs then BNP:RefreshOtherDebuffs() end
end)
check:SetScript("OnEnter", function()
local def = this.BNPDebuffDef
if not def then return end
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetText(def.label or def.key, 1, 0.82, 0)
if def.description then GameTooltip:AddLine(def.description, 1, 1, 1, true) end
GameTooltip:Show()
end)
check:SetScript("OnLeave", function() GameTooltip:Hide() end)
frame.otherDebuffRows[i] = row
end
local emptyClassNote = otherPage:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
emptyClassNote:SetPoint("TOPLEFT", otherPage, "TOPLEFT", 20, -184)
emptyClassNote:SetWidth(380)
emptyClassNote:SetJustifyH("LEFT")
emptyClassNote:SetText("No shared Shaman debuffs are defined in the imported Cursive class list.")
emptyClassNote:Hide()
frame.otherEmptyClassNote = emptyClassNote
function frame:RefreshOtherDebuffPage()
if self.otherDebuffsEnabledCheck then
self.otherDebuffsEnabledCheck:SetChecked(BNP.AreOtherDebuffsEnabled and BNP:AreOtherDebuffsEnabled() or false)
end
local selected = self.otherSelectedClass or "warrior"
local defs = BNP.OtherDebuffDefs and BNP.OtherDebuffDefs[selected] or nil
local selectedColor = classColors[selected] or { 1, 0.82, 0 }
if self.otherSelectedClassLabel then
self.otherSelectedClassLabel:SetText(classLabels[selected] or selected)
self.otherSelectedClassLabel:SetTextColor(selectedColor[1], selectedColor[2], selectedColor[3])
end
local classKey, tab
for classKey, tab in pairs(self.otherDebuffClassTabs or {}) do
local c = classColors[classKey] or { 0.6, 0.6, 0.6 }
if tab.BNPBG then
if classKey == selected then tab.BNPBG:SetTexture(c[1] * 0.55, c[2] * 0.55, c[3] * 0.55, 1)
else tab.BNPBG:SetTexture(c[1] * 0.4, c[2] * 0.4, c[3] * 0.4, 0.85) end
end
end
local count = defs and table.getn(defs) or 0
if self.otherEmptyClassNote then
if count == 0 then self.otherEmptyClassNote:Show() else self.otherEmptyClassNote:Hide() end
end
local rowIndex
for rowIndex = 1, table.getn(self.otherDebuffRows or {}) do
local row = self.otherDebuffRows[rowIndex]
local def = defs and defs[rowIndex] or nil
if def then
row.BNPDef = def
row.BNPCheck.BNPDebuffKey = def.key
row.BNPCheck.BNPDebuffDef = def
row.BNPCheck:SetChecked(BNP.IsOtherDebuffSelected and BNP:IsOtherDebuffSelected(def.key) or false)
row.BNPIcon:SetTexture(BNP.GetOtherDebuffTexture and BNP:GetOtherDebuffTexture(def) or "Interface\\Icons\\INV_Misc_QuestionMark")
row.BNPLabel:SetText(def.label or def.key)
row.BNPLabel:SetTextColor(selectedColor[1], selectedColor[2], selectedColor[3])
row:Show()
else
row.BNPDef = nil
row.BNPCheck.BNPDebuffKey = nil
row.BNPCheck.BNPDebuffDef = nil
row:Hide()
end
end
end
end
function BNP:CreateOptions()
if self.optionsFrame then return end
local frame = CreateFrame("Frame", "BNPOptionsFrame", UIParent)
-- Resizable modern window. Size is saved per character so players using
-- different UI scales can keep the menu at the dimensions that suit them.
local minOptionsWidth, minOptionsHeight = 600, 430
local maxOptionsWidth = math.max(650, (UIParent:GetWidth() or 1024) - 40)
local maxOptionsHeight = math.max(438, (UIParent:GetHeight() or 768) - 40)
local savedWidth = BNP_DB and tonumber(BNP_DB.optionsWidth) or 650
local savedHeight = BNP_DB and tonumber(BNP_DB.optionsHeight) or 438
if savedWidth < minOptionsWidth then savedWidth = minOptionsWidth end
if savedHeight < minOptionsHeight then savedHeight = minOptionsHeight end
if savedWidth > maxOptionsWidth then savedWidth = maxOptionsWidth end
if savedHeight > maxOptionsHeight then savedHeight = maxOptionsHeight end
frame:SetWidth(savedWidth)
frame:SetHeight(savedHeight)
frame:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
frame:SetFrameStrata("DIALOG")
frame:SetBackdrop({
bgFile = "Interface\\ChatFrame\\ChatFrameBackground",
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
tile = true, tileSize = 16, edgeSize = 12,
insets = { left = 3, right = 3, top = 3, bottom = 3 }
})
frame:SetBackdropColor(0.035, 0.045, 0.060, 0.98)
frame:SetBackdropBorderColor(0.18, 0.24, 0.32, 1)
frame:SetMovable(true)
if frame.SetResizable then frame:SetResizable(true) end
if frame.SetMinResize then frame:SetMinResize(minOptionsWidth, minOptionsHeight) end
if frame.SetMaxResize then frame:SetMaxResize(maxOptionsWidth, maxOptionsHeight) end
if frame.SetClampedToScreen then frame:SetClampedToScreen(true) end
frame:EnableMouse(true)
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
if this.BNPRefreshResizableLayout then this:BNPRefreshResizableLayout() end
end)
frame:SetScript("OnHide", function()
if BNP.RefreshPersonalNameplate then BNP:RefreshPersonalNameplate() end
BNP_DB = BNP_DB or {}
BNP_DB.optionsWidth = math.floor((this:GetWidth() or 650) + 0.5)
BNP_DB.optionsHeight = math.floor((this:GetHeight() or 438) + 0.5)
end)
frame:Hide()
-- Header ---------------------------------------------------------------
local headerBG = frame:CreateTexture(nil, "BACKGROUND")
headerBG:SetTexture("Interface\\ChatFrame\\ChatFrameBackground")
headerBG:SetVertexColor(0.055, 0.075, 0.105, 1)
headerBG:SetPoint("TOPLEFT", frame, "TOPLEFT", 4, -4)
headerBG:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -4, -4)
headerBG:SetHeight(70)
local headerAccent = frame:CreateTexture(nil, "ARTWORK")
headerAccent:SetTexture("Interface\\ChatFrame\\ChatFrameBackground")
headerAccent:SetVertexColor(0.19, 0.52, 0.92, 1)
headerAccent:SetPoint("BOTTOMLEFT", headerBG, "BOTTOMLEFT", 0, 0)
headerAccent:SetPoint("BOTTOMRIGHT", headerBG, "BOTTOMRIGHT", 0, 0)
headerAccent:SetHeight(2)
local title = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
title:SetPoint("TOPLEFT", frame, "TOPLEFT", 22, -16)
title:SetText("Blizz Nameplates+")
title:SetTextColor(0.96, 0.97, 1.00)
local subtitle = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
subtitle:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -5)
subtitle:SetText("Classic Blizzard nameplates, refined.")
subtitle:SetTextColor(0.52, 0.60, 0.70)
local author = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
author:SetPoint("LEFT", subtitle, "RIGHT", 8, 0)
author:SetText("by Wurmschwanz")
author:SetTextColor(0.92, 0.72, 0.25)
local version = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
version:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -46, -22)
version:SetText("Version " .. tostring(BNP.version or "?"))
version:SetTextColor(0.42, 0.68, 1.00)
frame.versionText = version
local close = CreateFrame("Button", nil, frame, "UIPanelCloseButton")
close:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -5, -7)
-- Left navigation / content surfaces ----------------------------------
local sidebarBG = frame:CreateTexture(nil, "BACKGROUND")
sidebarBG:SetTexture("Interface\\ChatFrame\\ChatFrameBackground")
sidebarBG:SetVertexColor(0.025, 0.032, 0.045, 1)
sidebarBG:SetPoint("TOPLEFT", frame, "TOPLEFT", 10, -82)
sidebarBG:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 10, 12)
sidebarBG:SetWidth(146)
local sidebarLine = frame:CreateTexture(nil, "ARTWORK")
sidebarLine:SetTexture("Interface\\ChatFrame\\ChatFrameBackground")
sidebarLine:SetVertexColor(0.14, 0.18, 0.24, 1)
sidebarLine:SetPoint("TOPRIGHT", sidebarBG, "TOPRIGHT", 0, 0)
sidebarLine:SetPoint("BOTTOMRIGHT", sidebarBG, "BOTTOMRIGHT", 0, 0)
sidebarLine:SetWidth(1)
local contentBG = frame:CreateTexture(nil, "BACKGROUND")
contentBG:SetTexture("Interface\\ChatFrame\\ChatFrameBackground")
contentBG:SetVertexColor(0.045, 0.055, 0.072, 0.96)
contentBG:SetPoint("TOPLEFT", frame, "TOPLEFT", 166, -82)
contentBG:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -10, 12)
local pageIndex = 0
local function CreatePage(contentHeight)
-- Keep the outer options window compact. Longer pages live inside their
-- own clipped ScrollFrame. A slim modern scrollbar on the right makes it
-- obvious when more settings are available below the visible area.
pageIndex = pageIndex + 1
local scroll = CreateFrame("ScrollFrame", "BNPOptionsPageScroll" .. pageIndex, frame)
scroll:SetPoint("TOPLEFT", frame, "TOPLEFT", 176, -100)
scroll:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -28, 14)
scroll:EnableMouseWheel(true)
scroll:Hide()
local page = CreateFrame("Frame", nil, scroll)
page:SetWidth(438)
page:SetHeight(contentHeight or 580)
scroll:SetScrollChild(page)
page.BNPScrollFrame = scroll
local scrollBar = CreateFrame("Slider", "BNPOptionsPageScrollbar" .. pageIndex, frame)
scrollBar:SetOrientation("VERTICAL")
scrollBar:SetWidth(10)
scrollBar:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -14, -104)
scrollBar:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -14, 18)
scrollBar:SetMinMaxValues(0, 1)
scrollBar:SetValue(0)
scrollBar:SetValueStep(1)
scrollBar:Hide()
local track = scrollBar:CreateTexture(nil, "BACKGROUND")
track:SetTexture("Interface\\ChatFrame\\ChatFrameBackground")
track:SetVertexColor(0.10, 0.13, 0.17, 0.95)
track:SetPoint("TOP", scrollBar, "TOP", 0, 0)
track:SetPoint("BOTTOM", scrollBar, "BOTTOM", 0, 0)
track:SetWidth(4)
scrollBar.BNPTrack = track
-- Keep WoW's native slider thumb only as the invisible drag/position
-- anchor. Some Vanilla/custom clients can visually split a stretched
-- thumb texture while scrolling or resizing, so BNP draws its own solid
-- thumb frame on top instead. This keeps the scrollbar visually stable
-- while preserving the Slider's native drag behaviour.
scrollBar:SetThumbTexture("Interface\\ChatFrame\\ChatFrameBackground")
local thumb = scrollBar:GetThumbTexture()
if thumb then
thumb:SetWidth(8)
thumb:SetHeight(42)
thumb:SetVertexColor(1, 1, 1, 0)
end
local visualThumb = CreateFrame("Frame", nil, scrollBar)
visualThumb:SetWidth(8)
visualThumb:SetHeight(42)
visualThumb:SetFrameLevel(scrollBar:GetFrameLevel() + 2)
if thumb then
visualThumb:SetPoint("CENTER", thumb, "CENTER", 0, 0)
else
visualThumb:SetPoint("TOP", scrollBar, "TOP", 0, 0)
end
local visualThumbBG = visualThumb:CreateTexture(nil, "ARTWORK")
visualThumbBG:SetTexture("Interface\\ChatFrame\\ChatFrameBackground")
visualThumbBG:SetVertexColor(0.28, 0.58, 0.96, 1)
visualThumbBG:SetAllPoints(visualThumb)
visualThumb.BNPTexture = visualThumbBG
scrollBar.BNPVisualThumb = visualThumb
local topCap = scrollBar:CreateTexture(nil, "ARTWORK")
topCap:SetTexture("Interface\\ChatFrame\\ChatFrameBackground")
topCap:SetVertexColor(0.18, 0.30, 0.45, 0.85)
topCap:SetPoint("TOP", scrollBar, "TOP", 0, 0)
topCap:SetWidth(8)
topCap:SetHeight(1)
local bottomCap = scrollBar:CreateTexture(nil, "ARTWORK")
bottomCap:SetTexture("Interface\\ChatFrame\\ChatFrameBackground")
bottomCap:SetVertexColor(0.18, 0.30, 0.45, 0.85)
bottomCap:SetPoint("BOTTOM", scrollBar, "BOTTOM", 0, 0)
bottomCap:SetWidth(8)
bottomCap:SetHeight(1)
scroll.BNPScrollBar = scrollBar
scrollBar.BNPOwner = scroll
function scroll:BNPRefreshScrollBar()
-- Each settings page owns its own scrollbar, but all scrollbar frames
-- are parented to the main options window. During a window resize the
-- layout refresh runs for every page, including hidden tabs. Without
-- this guard those hidden pages could briefly show their own thumbs,
-- which looked like extra static sliders above/below the real one.
-- Only the currently visible ScrollFrame may ever show its scrollbar.
if not self:IsShown() then
if self.BNPScrollBar then self.BNPScrollBar:Hide() end
return
end
local child = self:GetScrollChild()
local childHeight = child and child:GetHeight() or 0
local maxScroll = childHeight - self:GetHeight()
if maxScroll < 0 then maxScroll = 0 end
if maxScroll > 0 then
local bar = self.BNPScrollBar
bar:SetMinMaxValues(0, maxScroll)
local value = self:GetVerticalScroll()
if value > maxScroll then
value = maxScroll
self:SetVerticalScroll(value)
end
bar.BNPSyncing = true
bar:SetValue(value)
bar.BNPSyncing = nil
local barThumb = bar:GetThumbTexture()
if barThumb and childHeight > 0 then
local thumbHeight = math.floor(self:GetHeight() * (self:GetHeight() / childHeight))
if thumbHeight < 34 then thumbHeight = 34 end
if thumbHeight > 82 then thumbHeight = 82 end
barThumb:SetHeight(thumbHeight)
if bar.BNPVisualThumb then bar.BNPVisualThumb:SetHeight(thumbHeight) end
end
bar:Show()
else
self:SetVerticalScroll(0)
self.BNPScrollBar:Hide()
end
end
function scroll:BNPScrollBy(delta)
local child = self:GetScrollChild()
local maxScroll = (child and child:GetHeight() or 0) - self:GetHeight()
if maxScroll < 0 then maxScroll = 0 end
local value = self:GetVerticalScroll() - ((delta or 0) * 34)
if value < 0 then value = 0 end
if value > maxScroll then value = maxScroll end
self:SetVerticalScroll(value)
if self.BNPScrollBar and self.BNPScrollBar:IsShown() then
self.BNPScrollBar.BNPSyncing = true
self.BNPScrollBar:SetValue(value)
self.BNPScrollBar.BNPSyncing = nil
end
end
scroll:SetScript("OnMouseWheel", function()
this:BNPScrollBy(arg1 or 0)
end)
scroll:SetScript("OnShow", function()
this:BNPRefreshScrollBar()
end)
scrollBar:SetScript("OnValueChanged", function()
if this.BNPSyncing then return end
local owner = this.BNPOwner
if owner then owner:SetVerticalScroll(arg1 or this:GetValue()) end
end)
scrollBar:SetScript("OnEnter", function()
local v = this.BNPVisualThumb
if v and v.BNPTexture then v.BNPTexture:SetVertexColor(0.38, 0.68, 1.00, 1) end
end)
scrollBar:SetScript("OnLeave", function()
local v = this.BNPVisualThumb
if v and v.BNPTexture then v.BNPTexture:SetVertexColor(0.28, 0.58, 0.96, 1) end
end)
return page
end
local function CreateSection(parent, label, y)
local text = parent:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
text:SetPoint("TOPLEFT", parent, "TOPLEFT", 8, y)
text:SetText(string.upper(label))
text:SetTextColor(0.42, 0.68, 1.00)
local line = parent:CreateTexture(nil, "ARTWORK")
line:SetTexture("Interface\\ChatFrame\\ChatFrameBackground")
line:SetVertexColor(0.18, 0.30, 0.45, 0.75)
line:SetPoint("LEFT", text, "RIGHT", 10, 0)
line:SetPoint("RIGHT", parent, "RIGHT", -8, 0)
line:SetHeight(1)
return text
end
local function SetCheckEnabled(check, enabled)
if not check then return end
if enabled then
if check.Enable then check:Enable() end
check:SetAlpha(1.0)
if check.BNPLabel then check.BNPLabel:SetTextColor(0.90, 0.92, 0.95) end
else
if check.Disable then check:Disable() end
check:SetAlpha(0.38)
if check.BNPLabel then check.BNPLabel:SetTextColor(0.42, 0.45, 0.50) end
end
end
frame.pages = {
nameplates = CreatePage(510),
personal = CreatePage(345),
auras = CreatePage(520),
totems = CreatePage(610),
castbar = CreatePage(300),
target = CreatePage(530),
other = CreatePage(550),
tools = CreatePage(160),
}
frame.tabs = {}
-- Keep page widths and scrollbars in sync with the user-resized window.
function frame:BNPRefreshResizableLayout()
local contentWidth = (self:GetWidth() or 650) - 204
if contentWidth < 396 then contentWidth = 396 end
local key, page
for key, page in pairs(self.pages or {}) do
if page and page.SetWidth then page:SetWidth(contentWidth) end
if page and page.BNPScrollFrame and page.BNPScrollFrame.BNPRefreshScrollBar then
page.BNPScrollFrame:BNPRefreshScrollBar()
end
end
end
local resizeGrip = CreateFrame("Button", "BNPOptionsResizeGrip", frame)
resizeGrip:SetWidth(20)
resizeGrip:SetHeight(20)
resizeGrip:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -3, 3)
resizeGrip:SetNormalTexture("Interface\\ChatFrame\\UI-ChatIM-SizeGrabber-Up")
resizeGrip:SetHighlightTexture("Interface\\ChatFrame\\UI-ChatIM-SizeGrabber-Highlight")
resizeGrip:SetPushedTexture("Interface\\ChatFrame\\UI-ChatIM-SizeGrabber-Down")
resizeGrip:SetFrameLevel(frame:GetFrameLevel() + 20)
-- Some Vanilla/custom clients do not ship or render the old Blizzard
-- size-grabber textures. Draw a simple fallback marker so the resize corner
-- is always obvious regardless of UI scale or client texture set.
local resizeGripMark = resizeGrip:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
resizeGripMark:SetPoint("CENTER", resizeGrip, "CENTER", 1, -1)
resizeGripMark:SetText("///")
resizeGripMark:SetTextColor(0.42, 0.58, 0.76, 0.95)
if resizeGripMark.SetShadowColor then resizeGripMark:SetShadowColor(0, 0, 0, 1) end
if resizeGripMark.SetShadowOffset then resizeGripMark:SetShadowOffset(1, -1) end
resizeGrip.BNPMark = resizeGripMark
resizeGrip:SetScript("OnMouseDown", function()
if arg1 ~= "LeftButton" then return end
if frame.StartSizing then
frame.BNPIsResizing = true
frame:StartSizing("BOTTOMRIGHT")
end
end)
resizeGrip:SetScript("OnMouseUp", function()
if frame.StopMovingOrSizing then frame:StopMovingOrSizing() end
frame.BNPIsResizing = nil
frame:BNPRefreshResizableLayout()
BNP_DB = BNP_DB or {}
BNP_DB.optionsWidth = math.floor((frame:GetWidth() or 650) + 0.5)
BNP_DB.optionsHeight = math.floor((frame:GetHeight() or 438) + 0.5)
end)
resizeGrip:SetScript("OnUpdate", function()
if not frame.BNPIsResizing then return end
this.BNPTick = (this.BNPTick or 0) + arg1
if this.BNPTick < 0.04 then return end
this.BNPTick = 0
frame:BNPRefreshResizableLayout()
end)
resizeGrip:SetScript("OnEnter", function()
if this.BNPMark then this.BNPMark:SetTextColor(0.35, 0.72, 1.00, 1) end
GameTooltip:SetOwner(this, "ANCHOR_TOPLEFT")
GameTooltip:SetText("Resize Window", 1, 0.82, 0)
GameTooltip:AddLine("Drag this corner to change the menu size.", 1, 1, 1, true)
GameTooltip:Show()
end)
resizeGrip:SetScript("OnLeave", function()
if this.BNPMark then this.BNPMark:SetTextColor(0.42, 0.58, 0.76, 0.95) end
GameTooltip:Hide()
end)
frame.resizeGrip = resizeGrip
frame:BNPRefreshResizableLayout()
local tabDefs = {
{ key = "nameplates", label = "Nameplates" },
{ key = "personal", label = "Personal" },
{ key = "auras", label = "Auras" },
{ key = "totems", label = "Icons & Marks" },
{ key = "castbar", label = "Castbar" },
{ key = "target", label = "Target" },
{ key = "other", label = "Other Debuffs" },
{ key = "tools", label = "Tools" },
}
local function SetTabVisual(button, active, hovered)
if not button then return end
button.BNPActive = active and true or false
if active then
button.BNPBG:SetVertexColor(0.085, 0.14, 0.21, 1)
button.BNPAccent:Show()
button.BNPText:SetTextColor(0.96, 0.97, 1.00)
elseif hovered then
button.BNPBG:SetVertexColor(0.055, 0.075, 0.10, 1)
button.BNPAccent:Hide()
button.BNPText:SetTextColor(0.72, 0.82, 0.94)
else
button.BNPBG:SetVertexColor(0.025, 0.032, 0.045, 0.01)
button.BNPAccent:Hide()
button.BNPText:SetTextColor(0.58, 0.63, 0.70)
end
end
function frame:ShowTab(key)
local pageKey, page
for pageKey, page in pairs(self.pages) do
if pageKey == key then
page:Show()
if page.BNPScrollFrame then
page.BNPScrollFrame:Show()
if page.BNPScrollFrame.BNPRefreshScrollBar then
page.BNPScrollFrame:BNPRefreshScrollBar()
end
end
else
page:Hide()
if page.BNPScrollFrame then
page.BNPScrollFrame:Hide()
if page.BNPScrollFrame.BNPScrollBar then page.BNPScrollFrame.BNPScrollBar:Hide() end
end
end
end
local i
for i = 1, table.getn(tabDefs) do
local def = tabDefs[i]
SetTabVisual(self.tabs[def.key], def.key == key, false)
end
self.selectedTab = key
if key == "other" and self.RefreshOtherDebuffPage then
self:RefreshOtherDebuffPage()
end
end
local navTitle = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
navTitle:SetPoint("TOPLEFT", frame, "TOPLEFT", 26, -98)
navTitle:SetText("SETTINGS")
navTitle:SetTextColor(0.36, 0.43, 0.52)
local i
for i = 1, table.getn(tabDefs) do
local def = tabDefs[i]
local button = CreateFrame("Button", nil, frame)
button:SetWidth(126)
button:SetHeight(34)
button:SetPoint("TOPLEFT", frame, "TOPLEFT", 20, -116 - ((i - 1) * 38))
button.BNPTabKey = def.key
local bg = button:CreateTexture(nil, "BACKGROUND")
bg:SetAllPoints(button)
bg:SetTexture("Interface\\ChatFrame\\ChatFrameBackground")
button.BNPBG = bg
local accent = button:CreateTexture(nil, "ARTWORK")
accent:SetTexture("Interface\\ChatFrame\\ChatFrameBackground")
accent:SetVertexColor(0.19, 0.52, 0.92, 1)
accent:SetPoint("TOPLEFT", button, "TOPLEFT", 0, -4)
accent:SetPoint("BOTTOMLEFT", button, "BOTTOMLEFT", 0, 4)
accent:SetWidth(3)
accent:Hide()
button.BNPAccent = accent
local label = button:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
label:SetPoint("LEFT", button, "LEFT", 14, 0)
label:SetText(def.label)
label:SetJustifyH("LEFT")
button.BNPText = label
button:SetScript("OnClick", function()
frame:ShowTab(this.BNPTabKey)
end)
button:SetScript("OnEnter", function()
if not this.BNPActive then SetTabVisual(this, false, true) end
end)
button:SetScript("OnLeave", function()
SetTabVisual(this, this.BNPActive, false)
end)
frame.tabs[def.key] = button
SetTabVisual(button, false, false)
end
-- NAMEPLATES TAB ---------------------------------------------------------
local nameplatesPage = frame.pages.nameplates
-- GENERAL ---------------------------------------------------------------
CreateSection(nameplatesPage, "General", -4)
local scale = CreateSlider(nameplatesPage, "Nameplate Scale", 0.70, 1.50, 0.05, -28, 28, 150)
scale:SetScript("OnValueChanged", function()
if not BNP_DB then return end
local value = Round(this:GetValue(), 0.05)
BNP_DB.nameplateScale = value
getglobal(this:GetName() .. "Text"):SetText("Nameplate Scale: " .. string.format("%.2f", value))
if BNP.ApplyNameplateScaleAll then BNP:ApplyNameplateScaleAll() end
end)
frame.scaleSlider = scale
local yOffset = CreateSlider(nameplatesPage, "Nameplate Y Offset", 0, 50, 1, -28, 220, 150)
yOffset:SetScript("OnValueChanged", function()
if not BNP_DB then return end
local value = math.floor(this:GetValue() + 0.5)
BNP_DB.nameplateYOffset = value
getglobal(this:GetName() .. "Text"):SetText("Nameplate Y Offset: +" .. value)
if BNP.ApplyNameplateYOffsetAll then BNP:ApplyNameplateYOffsetAll() end
end)
frame.yOffsetSlider = yOffset
local nonTargetAlpha = CreateSlider(nameplatesPage, "Non-Target Alpha", 30, 100, 5, -64, 28, 150)
nonTargetAlpha:SetScript("OnValueChanged", function()
if not BNP_DB then return end
local percent = math.floor((this:GetValue() / 5) + 0.5) * 5
if percent < 30 then percent = 30 end
if percent > 100 then percent = 100 end
BNP_DB.nonTargetAlpha = percent / 100
getglobal(this:GetName() .. "Text"):SetText("Non-Target Alpha: " .. percent .. "%")
if BNP.RefreshNonTargetAlpha then BNP:RefreshNonTargetAlpha() end
end)
frame.nonTargetAlphaSlider = nonTargetAlpha
local classColors = CreateCheck(nameplatesPage, "Class Colors", -60, function()
BNP_DB.classColors = this:GetChecked() and true or false
if BNP.RefreshClassColors then BNP:RefreshClassColors() end
end, 214)
frame.classColorsCheck = classColors
local darkNameplateBorder = CreateCheck(nameplatesPage, "Dark Border", -88, function()
BNP_DB.darkNameplateBorder = this:GetChecked() and true or false
if BNP.RefreshNameplateBorderStyle then BNP:RefreshNameplateBorderStyle() end
end, 22)
darkNameplateBorder:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetText("Dark Nameplate Border", 1, 0.82, 0)
GameTooltip:AddLine("Darkens only the classic Blizzard nameplate border using the ShaguTweaks Darkened UI color.", 1, 1, 1, true)
GameTooltip:AddLine("When disabled, the original gold border is restored, including with ShaguTweaks. Other elements remain unchanged.", 0.8, 0.8, 0.8, true)
GameTooltip:Show()
end)
darkNameplateBorder:SetScript("OnLeave", function() GameTooltip:Hide() end)
frame.darkNameplateBorderCheck = darkNameplateBorder
local hideNameplateBorder = CreateCheck(nameplatesPage, "Hide Border", -88, function()
BNP_DB.hideNameplateBorder = this:GetChecked() and true or false
if BNP.RefreshNameplateBorderStyle then BNP:RefreshNameplateBorderStyle() end
if frame.UpdateDependentControls then frame:UpdateDependentControls() end
end, 214)
hideNameplateBorder:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetText("Hide Nameplate Border", 1, 0.82, 0)
GameTooltip:AddLine("Hides only the native Blizzard nameplate border. BNP keeps the border reference internally so it can be restored safely.", 1, 1, 1, true)
GameTooltip:Show()
end)
hideNameplateBorder:SetScript("OnLeave", function() GameTooltip:Hide() end)
frame.hideNameplateBorderCheck = hideNameplateBorder
-- TANK MODE -------------------------------------------------------------
CreateSection(nameplatesPage, "Tank Mode", -118)
local tank = CreateCheck(nameplatesPage, "Tank Mode", -142, function()
BNP_DB.tankMode = this:GetChecked() and true or false
if BNP.UpdateTankMode then BNP:UpdateTankMode() end
if frame.UpdateDependentControls then frame:UpdateDependentControls() end
end, 22)
tank:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetText("Tank Mode", 1, 0.82, 0)
GameTooltip:AddLine("Normal mode is fixed: Green when the unit targets you, Red when it targets someone else.", 1, 1, 1, true)
GameTooltip:Show()
end)
tank:SetScript("OnLeave", function() GameTooltip:Hide() end)
frame.tankCheck = tank
local invertTankColors = CreateCheck(nameplatesPage, "Invert Tank Colors", -142, function()
BNP_DB.invertTankColors = this:GetChecked() and true or false
if BNP.UpdateTankMode then BNP:UpdateTankMode() end
if frame.UpdateDependentControls then frame:UpdateDependentControls() end
end, 214)
invertTankColors:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetText("Invert Tank Colors", 1, 0.82, 0)
GameTooltip:AddLine("Uses the two custom colors below instead of the normal fixed Green/Red Tank Mode colors.", 1, 1, 1, true)
GameTooltip:Show()
end)
invertTankColors:SetScript("OnLeave", function() GameTooltip:Hide() end)
frame.invertTankColorsCheck = invertTankColors
local invertAggroColor = CreateColorSwatch(nameplatesPage, "Aggro Color", -170, 22,
function() return BNP:GetInvertTankAggroColor() end,
function(r, g, b) BNP:SetInvertTankAggroColor(r, g, b) end,
1.00, 0.00, 0.00)
frame.invertAggroColorSwatch = invertAggroColor
local invertNoAggroColor = CreateColorSwatch(nameplatesPage, "No Aggro Color", -170, 214,
function() return BNP:GetInvertTankNoAggroColor() end,
function(r, g, b) BNP:SetInvertTankNoAggroColor(r, g, b) end,
0.00, 1.00, 0.00)
frame.invertNoAggroColorSwatch = invertNoAggroColor
-- NAMES & LEVEL ---------------------------------------------------------
CreateSection(nameplatesPage, "Names & Level", -202)
local hidePlayerNames = CreateCheck(nameplatesPage, "Hide Player Names", -226, function()
BNP_DB.hidePlayerNames = this:GetChecked() and true or false
if BNP.RefreshNameVisibility then BNP:RefreshNameVisibility() end
end, 22)
frame.hidePlayerNamesCheck = hidePlayerNames
local hideNPCNames = CreateCheck(nameplatesPage, "Hide NPC Names", -226, function()
BNP_DB.hideNPCNames = this:GetChecked() and true or false
if BNP.RefreshNameVisibility then BNP:RefreshNameVisibility() end
end, 214)
frame.hideNPCNamesCheck = hideNPCNames
local hideNameplateLevel = CreateCheck(nameplatesPage, "Hide Level", -250, function()
BNP_DB.hideNameplateLevel = this:GetChecked() and true or false
if BNP.RefreshNameplateLevelVisibility then BNP:RefreshNameplateLevelVisibility() end
if BNP.RefreshCastbarLayout then BNP:RefreshCastbarLayout() end
end, 22)
hideNameplateLevel:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetText("Hide Nameplate Level", 1, 0.82, 0)
GameTooltip:AddLine("Hides the numeric Blizzard level text without removing BNP's internal reference to it. Boss/level icons are left untouched.", 1, 1, 1, true)
GameTooltip:Show()
end)
hideNameplateLevel:SetScript("OnLeave", function() GameTooltip:Hide() end)
frame.hideNameplateLevelCheck = hideNameplateLevel
local customNameColor = CreateCheck(nameplatesPage, "Custom Name Color", -250, function()
BNP_DB.customNameColor = this:GetChecked() and true or false
if BNP.RefreshNameAppearance then BNP:RefreshNameAppearance() end
if frame.UpdateDependentControls then frame:UpdateDependentControls() end
end, 214)
customNameColor:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetText("Custom Name Color", 1, 0.82, 0)
GameTooltip:AddLine("Keeps nameplate names in one fixed color instead of allowing the client to recolor them, for example to red during combat.", 1, 1, 1, true)
GameTooltip:Show()
end)
customNameColor:SetScript("OnLeave", function() GameTooltip:Hide() end)
frame.customNameColorCheck = customNameColor
local nameColorSwatch, nameColorLabel = CreateColorSwatch(nameplatesPage, "Name Color", -276, 214,
function() return BNP:GetNameColor() end,
function(r, g, b) BNP:SetNameColor(r, g, b) end,
1.00, 1.00, 1.00,
"Choose the fixed color used for nameplate names while Custom Name Color is enabled.",
"Restores the custom name color to white.")
nameColorSwatch:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetText("Name Color", 1, 0.82, 0)
GameTooltip:AddLine("Choose the fixed color used for nameplate names while Custom Name Color is enabled.", 1, 1, 1, true)
GameTooltip:Show()
end)
nameColorSwatch:SetScript("OnLeave", function() GameTooltip:Hide() end)
frame.nameColorSwatch = nameColorSwatch
-- Name text controls are independent from Nameplate Scale. The size setting
-- only touches the Blizzard name FontString; the Y offset only moves it.
local nameFontSize = CreateSlider(nameplatesPage, "Name Font Size", 8, 24, 1, -306, 28, 150)
nameFontSize:SetScript("OnValueChanged", function()
if not BNP_DB or frame.BNPSyncingNameControls then return end
local value = math.floor(this:GetValue() + 0.5)
BNP_DB.nameFontSize = value
getglobal(this:GetName() .. "Text"):SetText("Name Font Size: " .. value)
if BNP.RefreshNameAppearance then BNP:RefreshNameAppearance() end
end)
frame.nameFontSizeSlider = nameFontSize
local nameFontYOffset = CreateSlider(nameplatesPage, "Name Y Offset", -50, 50, 1, -306, 220, 150)
nameFontYOffset:SetScript("OnValueChanged", function()
if not BNP_DB or frame.BNPSyncingNameControls then return end
local value = RoundSignedInteger(this:GetValue())
BNP_DB.nameFontYOffset = value
getglobal(this:GetName() .. "Text"):SetText("Name Y Offset: " .. (value > 0 and "+" or "") .. value)
if BNP.ApplyNameFontYOffsetAll then BNP:ApplyNameFontYOffsetAll() end
end)
frame.nameFontYOffsetSlider = nameFontYOffset
-- HEALTH BAR & TEXT -----------------------------------------------------
CreateSection(nameplatesPage, "Health Bar & Text", -336)
local healthTextLabel = nameplatesPage:CreateFontString(nil, "OVERLAY", "GameFontNormal")
healthTextLabel:SetPoint("TOPLEFT", nameplatesPage, "TOPLEFT", 28, -358)
healthTextLabel:SetText("Display")
healthTextLabel:SetTextColor(1.00, 0.82, 0.00)
local healthTextDropdown = CreateFrame("Frame", "BNPHealthTextDropdown", nameplatesPage, "UIDropDownMenuTemplate")
healthTextDropdown:SetPoint("TOPLEFT", nameplatesPage, "TOPLEFT", 10, -370)
UIDropDownMenu_SetWidth(150, healthTextDropdown)
local healthModeLabels = {
off = "Off",
percent = "Percent",
hp = "HP",
both = "HP + Percent",
}
local function SetHealthTextMode(mode)
if not healthModeLabels[mode] then mode = "off" end
BNP_DB.healthText = mode
BNP_DB.healthPercent = (mode == "percent" or mode == "both")
UIDropDownMenu_SetSelectedValue(healthTextDropdown, mode)
UIDropDownMenu_SetText(healthModeLabels[mode], healthTextDropdown)
if BNP.RefreshHealthPercent then BNP:RefreshHealthPercent() end
end
UIDropDownMenu_Initialize(healthTextDropdown, function()
local modes = { "off", "percent", "hp", "both" }
local n
for n = 1, table.getn(modes) do
local mode = modes[n]
local info = {}
info.text = healthModeLabels[mode]
info.value = mode
info.func = function() SetHealthTextMode(this.value) end
info.checked = (BNP:GetHealthTextMode() == mode)
UIDropDownMenu_AddButton(info)
end
end)
frame.healthTextDropdown = healthTextDropdown
frame.SetHealthTextMode = SetHealthTextMode
local healthOutlineLabel = nameplatesPage:CreateFontString(nil, "OVERLAY", "GameFontNormal")
healthOutlineLabel:SetPoint("TOPLEFT", nameplatesPage, "TOPLEFT", 220, -358)
healthOutlineLabel:SetText("Outline")
healthOutlineLabel:SetTextColor(1.00, 0.82, 0.00)
local healthOutlineDropdown = CreateFrame("Frame", "BNPHealthOutlineDropdown", nameplatesPage, "UIDropDownMenuTemplate")
healthOutlineDropdown:SetPoint("TOPLEFT", nameplatesPage, "TOPLEFT", 200, -370)
UIDropDownMenu_SetWidth(150, healthOutlineDropdown)
local healthOutlineLabels = {
none = "None",
outline = "Outline",
thick = "Thick Outline",
}
local function SetHealthTextOutline(value)
if not healthOutlineLabels[value] then value = "outline" end
BNP_DB.healthTextOutline = value
UIDropDownMenu_SetSelectedValue(healthOutlineDropdown, value)
UIDropDownMenu_SetText(healthOutlineLabels[value], healthOutlineDropdown)
if BNP.RefreshHealthPercent then BNP:RefreshHealthPercent() end
end
UIDropDownMenu_Initialize(healthOutlineDropdown, function()
local values = { "none", "outline", "thick" }
local n
for n = 1, table.getn(values) do
local value = values[n]
local info = {}
info.text = healthOutlineLabels[value]
info.value = value
info.func = function() SetHealthTextOutline(this.value) end
info.checked = (BNP:GetHealthTextOutline() == value)
UIDropDownMenu_AddButton(info)
end
end)
frame.healthTextOutlineDropdown = healthOutlineDropdown
frame.SetHealthTextOutline = SetHealthTextOutline
local healthFontSize = CreateSlider(nameplatesPage, "Health Font Size", 8, 20, 1, -416, 28, 150)
healthFontSize:SetScript("OnValueChanged", function()
if not BNP_DB or frame.BNPSyncingHealthTextControls then return end
local value = math.floor(this:GetValue() + 0.5)
BNP_DB.healthTextFontSize = value
getglobal(this:GetName() .. "Text"):SetText("Health Font Size: " .. value)
if BNP.RefreshHealthPercent then BNP:RefreshHealthPercent() end
end)
frame.healthTextFontSizeSlider = healthFontSize
local blackHealthbarBackground = CreateCheck(nameplatesPage, "Black Health Background", -412, function()
BNP_DB.blackHealthbarBackground = this:GetChecked() and true or false
if BNP.RefreshHealthbarBackground then BNP:RefreshHealthbarBackground() end
end, 214)
blackHealthbarBackground:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetText("Black Healthbar Background", 1, 0.82, 0)
GameTooltip:AddLine("Shows missing health on the nameplate against a solid black background instead of the transparent world view.", 1, 1, 1, true)
GameTooltip:Show()
end)
blackHealthbarBackground:SetScript("OnLeave", function() GameTooltip:Hide() end)
frame.blackHealthbarBackgroundCheck = blackHealthbarBackground
-- COMBO POINTS (Rogue / Druid only) ------------------------------------
if comboOptionsClass then
CreateSection(nameplatesPage, "Combo Points", -442)
local comboPoints = CreateCheck(nameplatesPage, "Combo Points", -466, function()
BNP_DB.comboPoints = this:GetChecked() and true or false
if BNP.RefreshComboPoints then BNP:RefreshComboPoints() end
if BNP.RefreshAllAuraLayouts then BNP:RefreshAllAuraLayouts() end
if BNP.RefreshAllImmunityLayouts then BNP:RefreshAllImmunityLayouts() end
if frame.UpdateDependentControls then frame:UpdateDependentControls() end
end, 22)
frame.comboPointsCheck = comboPoints
local darkComboPointBorder = CreateCheck(nameplatesPage, "Dark CP Border", -466, function()
BNP_DB.darkComboPointBorder = this:GetChecked() and true or false
if BNP.RefreshComboPointBorderStyle then BNP:RefreshComboPointBorderStyle() end
if BNP.RefreshComboPoints then BNP:RefreshComboPoints() end
end, 150)
darkComboPointBorder:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetText("Dark Combo Point Border", 1, 0.82, 0)
GameTooltip:AddLine("Replaces the gold Combo Point rim with a dark border.", 1, 1, 1, true)
GameTooltip:AddLine("This setting is independent from Dark Nameplate Border and ShaguTweaks.", 0.8, 0.8, 0.8, true)
GameTooltip:Show()
end)
darkComboPointBorder:SetScript("OnLeave", function() GameTooltip:Hide() end)
frame.darkComboPointBorderCheck = darkComboPointBorder
local comboYOffset = CreateSlider(nameplatesPage, "Combo Point Y Offset", -50, 50, 1, -466, 278, 105)
comboYOffset:SetScript("OnValueChanged", function()
if not BNP_DB then return end
local value = RoundSignedInteger(this:GetValue())
BNP_DB.comboPointsYOffset = value
getglobal(this:GetName() .. "Text"):SetText("Combo Point Y Offset: " .. (value > 0 and "+" or "") .. value)
if BNP.RefreshComboPoints then BNP:RefreshComboPoints() end
end)
frame.comboPointsYOffsetSlider = comboYOffset
end
-- PERSONAL NAMEPLATE -----------------------------------------------------
-- Dedicated page with clear functional groups. Buff and debuff controls
-- intentionally use fixed columns so their X/Y sliders cannot be confused.
local personalPage = frame.pages.personal
-- GENERAL ---------------------------------------------------------------
CreateSection(personalPage, "General", -4)
local personalNameplate = CreateCheck(personalPage, "Enable Personal Nameplate", -28, 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(personalPage, "Combat Only", -28, 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
local personalClassColor = CreateCheck(personalPage, "Class Color", -58, 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 personalHideLevel = CreateCheck(personalPage, "Hide Level", -58, function()
BNP_DB.personalNameplateHideLevel = this:GetChecked() and true or false
if BNP.RefreshPersonalNameplate then BNP:RefreshPersonalNameplate() end
end, 246)
personalHideLevel:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetText("Hide Level", 1, 0.82, 0)
GameTooltip:AddLine("Hides the level and removes the round level medallion so the Personal Nameplate becomes a closed centered bar.", 1, 1, 1, true)
GameTooltip:Show()
end)
personalHideLevel:SetScript("OnLeave", function() GameTooltip:Hide() end)
frame.personalNameplateHideLevelCheck = personalHideLevel
-- HEALTH & POSITION -----------------------------------------------------
CreateSection(personalPage, "Health & Position", -96)
local personalHealthTextLabel = personalPage:CreateFontString(nil, "OVERLAY", "GameFontNormal")
personalHealthTextLabel:SetPoint("TOPLEFT", personalPage, "TOPLEFT", 28, -120)
personalHealthTextLabel:SetText("Health Text")
personalHealthTextLabel:SetTextColor(1.00, 0.82, 0.00)
frame.personalNameplateHealthTextLabel = personalHealthTextLabel
local personalHealthTextDropdown = CreateFrame("Frame", "BNPPersonalHealthTextDropdown", personalPage, "UIDropDownMenuTemplate")
personalHealthTextDropdown:SetPoint("TOPLEFT", personalPage, "TOPLEFT", 10, -132)
UIDropDownMenu_SetWidth(142, personalHealthTextDropdown)
local personalScale = CreateSlider(personalPage, "Scale", 0.70, 1.50, 0.05, -132, 246, 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(personalPage, "Y Offset", -250, 150, 5, -174, 28, 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
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
-- AURAS -----------------------------------------------------------------
CreateSection(personalPage, "Auras", -214)
-- Fixed columns make it immediately obvious which offsets belong together.
local buffHeading = personalPage:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
buffHeading:SetPoint("TOPLEFT", personalPage, "TOPLEFT", 28, -238)
buffHeading:SetText("BUFFS")
buffHeading:SetTextColor(0.72, 0.82, 0.94)
local debuffHeading = personalPage:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
debuffHeading:SetPoint("TOPLEFT", personalPage, "TOPLEFT", 246, -238)
debuffHeading:SetText("DEBUFFS")
debuffHeading:SetTextColor(0.72, 0.82, 0.94)
local personalBuffs = CreateCheck(personalPage, "Show Buffs", -252, function()
BNP_DB.personalNameplateBuffs = this:GetChecked() and true or false
if BNP.RefreshPersonalNameplate then BNP:RefreshPersonalNameplate() end
end, 22)
personalBuffs:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetText("Show Buffs", 1, 0.82, 0)
GameTooltip:AddLine("Shows short buffs such as Renew, Rejuvenation and shields. Long buffs over 60 seconds are ignored.", 1, 1, 1, true)
GameTooltip:Show()
end)
personalBuffs:SetScript("OnLeave", function() GameTooltip:Hide() end)
frame.personalNameplateBuffsCheck = personalBuffs
local personalDebuffs = CreateCheck(personalPage, "Show Debuffs", -252, function()
BNP_DB.personalNameplateDebuffs = this:GetChecked() and true or false
if BNP.RefreshPersonalNameplate then BNP:RefreshPersonalNameplate() end
end, 240)
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
local personalBuffXOffset = CreateSlider(personalPage, "Buff X Offset", -100, 100, 1, -286, 28, 150)
personalBuffXOffset:SetScript("OnValueChanged", function()
if not BNP_DB then return end
local value = RoundSignedInteger(this:GetValue())
BNP_DB.personalNameplateBuffXOffset = value
getglobal(this:GetName() .. "Text"):SetText("Buff X Offset: " .. (value > 0 and "+" or "") .. value)
if BNP.RefreshPersonalNameplate then BNP:RefreshPersonalNameplate() end
end)
personalBuffXOffset:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetText("Personal Buff X Offset", 1, 0.82, 0)
GameTooltip:AddLine("Moves the Personal Nameplate buff row left or right independently from debuffs.", 1, 1, 1, true)
GameTooltip:Show()
end)
personalBuffXOffset:SetScript("OnLeave", function() GameTooltip:Hide() end)
frame.personalNameplateBuffXOffsetSlider = personalBuffXOffset
local personalDebuffXOffset = CreateSlider(personalPage, "Debuff X Offset", -100, 100, 1, -286, 246, 150)
personalDebuffXOffset:SetScript("OnValueChanged", function()
if not BNP_DB then return end
local value = RoundSignedInteger(this:GetValue())
BNP_DB.personalNameplateDebuffXOffset = value
getglobal(this:GetName() .. "Text"):SetText("Debuff X Offset: " .. (value > 0 and "+" or "") .. value)
if BNP.RefreshPersonalNameplate then BNP:RefreshPersonalNameplate() end
end)
personalDebuffXOffset:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetText("Personal Debuff X Offset", 1, 0.82, 0)
GameTooltip:AddLine("Moves the Personal Nameplate debuff row left or right so it does not run into the buff row or other UI.", 1, 1, 1, true)
GameTooltip:Show()
end)
personalDebuffXOffset:SetScript("OnLeave", function() GameTooltip:Hide() end)
frame.personalNameplateDebuffXOffsetSlider = personalDebuffXOffset
local personalBuffYOffset = CreateSlider(personalPage, "Buff Y Offset", -100, 100, 1, -324, 28, 150)
personalBuffYOffset:SetScript("OnValueChanged", function()
if not BNP_DB then return end
local value = RoundSignedInteger(this:GetValue())
BNP_DB.personalNameplateBuffYOffset = value
getglobal(this:GetName() .. "Text"):SetText("Buff Y Offset: " .. (value > 0 and "+" or "") .. value)
if BNP.RefreshPersonalNameplate then BNP:RefreshPersonalNameplate() end
end)
personalBuffYOffset:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetText("Personal Buff Y Offset", 1, 0.82, 0)
GameTooltip:AddLine("Moves the Personal Nameplate buff row up or down independently from debuffs.", 1, 1, 1, true)
GameTooltip:Show()
end)
personalBuffYOffset:SetScript("OnLeave", function() GameTooltip:Hide() end)
frame.personalNameplateBuffYOffsetSlider = personalBuffYOffset
local personalDebuffYOffset = CreateSlider(personalPage, "Debuff Y Offset", -100, 100, 1, -324, 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
-- AURAS TAB --------------------------------------------------------------
local aurasPage = frame.pages.auras
CreateSection(aurasPage, "Debuffs", -4)
local icon = CreateSlider(aurasPage, "Aura Icon Size", 12, 32, 1, -92, 28, 150)
icon:SetScript("OnValueChanged", function()
if not BNP_DB then return end
local value = math.floor(this:GetValue() + 0.5)
BNP_DB.iconSize = value
getglobal(this:GetName() .. "Text"):SetText("Aura Icon Size: " .. value)
if BNP.RefreshAllAuraLayouts then BNP:RefreshAllAuraLayouts() end
if BNP.RefreshAllImmunityLayouts then BNP:RefreshAllImmunityLayouts() end
if BNP.RefreshCastbarLayout then BNP:RefreshCastbarLayout() end
end)
frame.iconSlider = icon
local auraFontSize = CreateSlider(aurasPage, "Aura Font Size", 6, 18, 1, -138, 28, 150)
auraFontSize:SetScript("OnValueChanged", function()
if not BNP_DB then return end
local value = math.floor(this:GetValue() + 0.5)
BNP_DB.auraFontSize = value
getglobal(this:GetName() .. "Text"):SetText("Aura Font Size: " .. value)
if BNP.RefreshAllAuraLayouts then BNP:RefreshAllAuraLayouts() end
end)
frame.auraFontSizeSlider = auraFontSize
frame.cooldownSpiralCheck = CreateCheck(aurasPage, "Cooldown Spiral", -136, function()
BNP_DB.cooldownSpiral = this:GetChecked() and true or false
if BNP.RefreshAuraCooldownSpirals then BNP:RefreshAuraCooldownSpirals() end
end, 208)
local debuffYOffset = CreateSlider(aurasPage, "Debuff Y Offset", -50, 50, 1, -92, 220, 150)
debuffYOffset:SetScript("OnValueChanged", function()
if not BNP_DB then return end
local value = RoundSignedInteger(this:GetValue())
BNP_DB.debuffYOffset = value
getglobal(this:GetName() .. "Text"):SetText("Debuff Y Offset: " .. (value > 0 and "+" or "") .. value)
if BNP.RefreshAllAuraLayouts then BNP:RefreshAllAuraLayouts() end
if BNP.RefreshDebuffVisibility then BNP:RefreshDebuffVisibility() end
if BNP.RefreshAllImmunityLayouts then BNP:RefreshAllImmunityLayouts() end
if BNP.RefreshCastbarLayout then BNP:RefreshCastbarLayout() end
end)
frame.debuffYOffsetSlider = debuffYOffset
local ccIcon = CreateSlider(aurasPage, "CC Icon Size", 12, 32, 1, -270, 28, 150)
ccIcon:SetScript("OnValueChanged", function()
if not BNP_DB then return end
local value = math.floor(this:GetValue() + 0.5)
BNP_DB.ccIconSize = value
getglobal(this:GetName() .. "Text"):SetText("CC Icon Size: " .. value)
if BNP.RefreshAllAuraLayouts then BNP:RefreshAllAuraLayouts() end
if BNP.RefreshAllImmunityLayouts then BNP:RefreshAllImmunityLayouts() end
if BNP.RefreshCastbarLayout then BNP:RefreshCastbarLayout() end
end)
frame.ccIconSlider = ccIcon
local ccYOffset = CreateSlider(aurasPage, "CC Y Offset", -50, 50, 1, -270, 220, 150)
ccYOffset:SetScript("OnValueChanged", function()
if not BNP_DB then return end
local value = RoundSignedInteger(this:GetValue())
BNP_DB.ccYOffset = value
getglobal(this:GetName() .. "Text"):SetText("CC Y Offset: " .. (value > 0 and "+" or "") .. value)
if BNP.RefreshAllAuraLayouts then BNP:RefreshAllAuraLayouts() end
if BNP.RefreshAllImmunityLayouts then BNP:RefreshAllImmunityLayouts() end
if BNP.RefreshDebuffVisibility then BNP:RefreshDebuffVisibility() end
if BNP.RefreshCastbarLayout then BNP:RefreshCastbarLayout() end
end)
frame.ccYOffsetSlider = ccYOffset
local debuffs = CreateCheck(aurasPage, "Enable Debuffs", -40, function()
BNP_DB.debuffs = this:GetChecked() and true or false
if BNP.RefreshDebuffVisibility then BNP:RefreshDebuffVisibility() end
if frame.UpdateDependentControls then frame:UpdateDependentControls() end
if BNP.RefreshAllImmunityLayouts then BNP:RefreshAllImmunityLayouts() end
if BNP.RefreshCastbarLayout then BNP:RefreshCastbarLayout() end
end)
frame.debuffsCheck = debuffs
local debuffPositionDropdown = CreateFrame("Frame", "BNPDebuffPositionDropdown", aurasPage, "UIDropDownMenuTemplate")
debuffPositionDropdown:SetPoint("TOPLEFT", aurasPage, "TOPLEFT", 188, -44)
UIDropDownMenu_SetWidth(112, debuffPositionDropdown)
local debuffPositionLabels = {
top = "Top Mid",
top_left = "Top Left",
top_right = "Top Right",
left = "Left",
right = "Right",
bottom_mid = "Bottom Mid",
bottom_left = "Bottom Left",
bottom_right = "Bottom Right",
}
local function SetDebuffPosition(position)
if not debuffPositionLabels[position] then position = "top" end
BNP_DB.debuffPosition = position
UIDropDownMenu_SetSelectedValue(debuffPositionDropdown, position)
UIDropDownMenu_SetText(debuffPositionLabels[position], debuffPositionDropdown)
if frame.UpdateDependentControls then frame:UpdateDependentControls() end
if BNP.RefreshAllAuraLayouts then BNP:RefreshAllAuraLayouts() end
if BNP.RefreshAllImmunityLayouts then BNP:RefreshAllImmunityLayouts() end
if BNP.RefreshDebuffVisibility then BNP:RefreshDebuffVisibility() end
if BNP.RefreshTotemIndicators then BNP:RefreshTotemIndicators() end
if BNP.RefreshCastbarLayout then BNP:RefreshCastbarLayout() end
end
UIDropDownMenu_Initialize(debuffPositionDropdown, function()
local positions = { "top", "top_left", "top_right", "left", "right", "bottom_mid", "bottom_left", "bottom_right" }
local n
for n = 1, table.getn(positions) do
local position = positions[n]
local info = {}
info.text = debuffPositionLabels[position]
info.value = position
info.func = function() SetDebuffPosition(this.value) end
info.checked = (BNP:GetDebuffPosition() == position)
UIDropDownMenu_AddButton(info)
end
end)
frame.debuffPositionDropdown = debuffPositionDropdown
frame.SetDebuffPosition = SetDebuffPosition
local debuffPositionLabel = aurasPage:CreateFontString(nil, "OVERLAY", "GameFontNormal")
debuffPositionLabel:SetPoint("BOTTOMLEFT", debuffPositionDropdown, "TOPLEFT", 18, 5)
debuffPositionLabel:SetText("Debuff Position")
debuffPositionLabel:SetTextColor(1.00, 0.82, 0.00)
frame.debuffPositionLabel = debuffPositionLabel
CreateSection(aurasPage, "Crowd Control", -184)
local crowdControl = CreateCheck(aurasPage, "Enable Crowd Control", -218, function()
BNP_DB.crowdControl = this:GetChecked() and true or false
if BNP.RefreshDebuffVisibility then BNP:RefreshDebuffVisibility() end
if BNP.RefreshAllImmunityLayouts then BNP:RefreshAllImmunityLayouts() end
if frame.UpdateDependentControls then frame:UpdateDependentControls() end
if BNP.RefreshCastbarLayout then BNP:RefreshCastbarLayout() end
end)
frame.crowdControlCheck = crowdControl
local ccPositionDropdown = CreateFrame("Frame", "BNPCCPositionDropdown", aurasPage, "UIDropDownMenuTemplate")
ccPositionDropdown:SetPoint("TOPLEFT", aurasPage, "TOPLEFT", 188, -222)
UIDropDownMenu_SetWidth(92, ccPositionDropdown)
local ccPositionLabels = {
top = "Top",
left = "Left",
right = "Right",
}
local function SetCCPosition(position)
if not ccPositionLabels[position] then position = "top" end
BNP_DB.ccPosition = position
UIDropDownMenu_SetSelectedValue(ccPositionDropdown, position)
UIDropDownMenu_SetText(ccPositionLabels[position], ccPositionDropdown)
if frame.UpdateDependentControls then frame:UpdateDependentControls() end
if BNP.RefreshAllAuraLayouts then BNP:RefreshAllAuraLayouts() end
if BNP.RefreshDebuffVisibility then BNP:RefreshDebuffVisibility() end
if BNP.RefreshCastbarLayout then BNP:RefreshCastbarLayout() end
end
UIDropDownMenu_Initialize(ccPositionDropdown, function()
local positions = { "top", "left", "right" }
local n
for n = 1, table.getn(positions) do
local position = positions[n]
local info = {}
info.text = ccPositionLabels[position]
info.value = position
info.func = function() SetCCPosition(this.value) end
info.checked = (BNP:GetCCPosition() == position)
UIDropDownMenu_AddButton(info)
end
end)
frame.ccPositionDropdown = ccPositionDropdown
frame.SetCCPosition = SetCCPosition
local ccPositionLabel = aurasPage:CreateFontString(nil, "OVERLAY", "GameFontNormal")
ccPositionLabel:SetPoint("BOTTOMLEFT", ccPositionDropdown, "TOPLEFT", 18, 5)
ccPositionLabel:SetText("CC Position")
ccPositionLabel:SetTextColor(1.00, 0.82, 0.00)
frame.ccPositionLabel = ccPositionLabel
local separateCCRow = CreateCheck(aurasPage, "Display CCs in Separate Row", -312, function()
BNP_DB.separateCCRow = this:GetChecked() and true or false
if BNP.RefreshAllAuraLayouts then BNP:RefreshAllAuraLayouts() end
if BNP.RefreshAllImmunityLayouts then BNP:RefreshAllImmunityLayouts() end
if BNP.RefreshDebuffVisibility then BNP:RefreshDebuffVisibility() end
if BNP.RefreshCastbarLayout then BNP:RefreshCastbarLayout() end
end, 42)
frame.separateCCRowCheck = separateCCRow
local showOtherCCs = CreateCheck(aurasPage, "Show CCs from Other Players", -340, function()
BNP_DB.showOtherCCs = this:GetChecked() and true or false
if BNP.RefreshDebuffVisibility then BNP:RefreshDebuffVisibility() end
end, 42)
frame.showOtherCCsCheck = showOtherCCs
CreateSection(aurasPage, "Immunities / Important Buffs", -382)
local pvpImmunities = CreateCheck(aurasPage, "Enable PvP Immunities", -416, function()
BNP_DB.pvpImmunities = this:GetChecked() and true or false
if BNP.RefreshImmunityVisibility then BNP:RefreshImmunityVisibility() end
if frame.UpdateDependentControls then frame:UpdateDependentControls() end
if BNP.RefreshCastbarLayout then BNP:RefreshCastbarLayout() end
end, 22)
frame.pvpImmunitiesCheck = pvpImmunities
local immunityPositionDropdown = CreateFrame("Frame", "BNPImmunityPositionDropdown", aurasPage, "UIDropDownMenuTemplate")
immunityPositionDropdown:SetPoint("TOPLEFT", aurasPage, "TOPLEFT", 188, -420)
UIDropDownMenu_SetWidth(92, immunityPositionDropdown)
local immunityPositionLabels = {
top = "Top",
left = "Left",
right = "Right",
}
local function SetImmunityPosition(position)
if not immunityPositionLabels[position] then position = "top" end
BNP_DB.immunityPosition = position
UIDropDownMenu_SetSelectedValue(immunityPositionDropdown, position)
UIDropDownMenu_SetText(immunityPositionLabels[position], immunityPositionDropdown)
if BNP.RefreshAllImmunityLayouts then BNP:RefreshAllImmunityLayouts() end
if BNP.RefreshCastbarLayout then BNP:RefreshCastbarLayout() end
end
UIDropDownMenu_Initialize(immunityPositionDropdown, function()
local positions = { "top", "left", "right" }
local n
for n = 1, table.getn(positions) do
local position = positions[n]
local info = {}
info.text = immunityPositionLabels[position]
info.value = position
info.func = function() SetImmunityPosition(this.value) end
info.checked = (BNP:GetImmunityPosition() == position)
UIDropDownMenu_AddButton(info)
end
end)
frame.immunityPositionDropdown = immunityPositionDropdown
frame.SetImmunityPosition = SetImmunityPosition
local immunityPositionLabel = aurasPage:CreateFontString(nil, "OVERLAY", "GameFontNormal")
immunityPositionLabel:SetPoint("BOTTOMLEFT", immunityPositionDropdown, "TOPLEFT", 18, 5)
immunityPositionLabel:SetText("Immunity Position")
immunityPositionLabel:SetTextColor(1.00, 0.82, 0.00)
frame.immunityPositionLabel = immunityPositionLabel
local immunityIcon = CreateSlider(aurasPage, "Immunity Icon Size", 12, 32, 1, -468, 28, 150)
immunityIcon:SetScript("OnValueChanged", function()
if not BNP_DB then return end
local value = math.floor(this:GetValue() + 0.5)
BNP_DB.immunityIconSize = value
getglobal(this:GetName() .. "Text"):SetText("Immunity Icon Size: " .. value)
if BNP.RefreshAllImmunityLayouts then BNP:RefreshAllImmunityLayouts() end
if BNP.RefreshCastbarLayout then BNP:RefreshCastbarLayout() end
end)
frame.immunityIconSlider = immunityIcon
local immunityYOffset = CreateSlider(aurasPage, "Immunity Y Offset", -50, 50, 1, -468, 220, 150)
immunityYOffset:SetScript("OnValueChanged", function()
if not BNP_DB then return end
local value = RoundSignedInteger(this:GetValue())
BNP_DB.immunityYOffset = value
getglobal(this:GetName() .. "Text"):SetText("Immunity Y Offset: " .. (value > 0 and "+" or "") .. value)
if BNP.RefreshAllImmunityLayouts then BNP:RefreshAllImmunityLayouts() end
if BNP.RefreshCastbarLayout then BNP:RefreshCastbarLayout() end
end)
frame.immunityYOffsetSlider = immunityYOffset
-- ICONS TAB --------------------------------------------------------------
local totemsPage = frame.pages.totems
-- Totem indicators
CreateSection(totemsPage, "Totem Indicators", -4)
local totemIndicators = CreateCheck(totemsPage, "Enable Totem Icons", -36, function()
BNP_DB.totemIndicators = this:GetChecked() and true or false
if BNP.RefreshTotemIndicators then BNP:RefreshTotemIndicators() end
if frame.UpdateDependentControls then frame:UpdateDependentControls() end
end)
frame.totemIndicatorsCheck = totemIndicators
local totemIcon = CreateSlider(totemsPage, "Totem Icon Size", 16, 36, 1, -82)
totemIcon:SetScript("OnValueChanged", function()
if not BNP_DB then return end
local value = math.floor(this:GetValue() + 0.5)
BNP_DB.totemIconSize = value
getglobal(this:GetName() .. "Text"):SetText("Totem Icon Size: " .. value)
if BNP.RefreshTotemIndicators then BNP:RefreshTotemIndicators() end
end)
frame.totemIconSlider = totemIcon
local totemNote = totemsPage:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
totemNote:SetPoint("TOPLEFT", totemsPage, "TOPLEFT", 28, -132)
totemNote:SetWidth(350)
totemNote:SetJustifyH("LEFT")
totemNote:SetText("Replaces visible shaman totem nameplates with compact icons.")
-- Raid marks
CreateSection(totemsPage, "Raid Marks", -176)
local raidMarkPositionLabel = totemsPage:CreateFontString(nil, "OVERLAY", "GameFontNormal")
raidMarkPositionLabel:SetPoint("TOPLEFT", totemsPage, "TOPLEFT", 28, -202)
raidMarkPositionLabel:SetText("Raid Mark Position")
raidMarkPositionLabel:SetTextColor(1.00, 0.82, 0.00)
frame.raidMarkPositionLabel = raidMarkPositionLabel
local raidMarkPositionDropdown = CreateFrame("Frame", "BNPRaidMarkPositionDropdown", totemsPage, "UIDropDownMenuTemplate")
raidMarkPositionDropdown:SetPoint("TOPLEFT", totemsPage, "TOPLEFT", 10, -214)
UIDropDownMenu_SetWidth(150, raidMarkPositionDropdown)
local raidMarkPositionLabels = {
top = "Top",
left = "Left",
right = "Right",
}
local function SetRaidMarkPosition(position)
if not raidMarkPositionLabels[position] then position = "top" end
BNP_DB.raidMarkPosition = position
UIDropDownMenu_SetSelectedValue(raidMarkPositionDropdown, position)
UIDropDownMenu_SetText(raidMarkPositionLabels[position], raidMarkPositionDropdown)
if BNP.RefreshRaidMarkPositions then BNP:RefreshRaidMarkPositions() end
end
UIDropDownMenu_Initialize(raidMarkPositionDropdown, function()
local positions = { "top", "left", "right" }
local n
for n = 1, table.getn(positions) do
local position = positions[n]
local info = {}
info.text = raidMarkPositionLabels[position]
info.value = position
info.func = function() SetRaidMarkPosition(this.value) end
info.checked = (BNP:GetRaidMarkPosition() == position)
UIDropDownMenu_AddButton(info)
end
end)
frame.raidMarkPositionDropdown = raidMarkPositionDropdown
frame.SetRaidMarkPosition = SetRaidMarkPosition
local raidMarkXOffset = CreateSlider(totemsPage, "Raid Mark X Offset: 0", -50, 50, 1, -270, 28, 150)
raidMarkXOffset:SetScript("OnValueChanged", function()
if not BNP_DB or frame.BNPSyncingRaidMarkControls then return end
local value = RoundSignedInteger(this:GetValue())
BNP_DB.raidMarkXOffset = value
getglobal(this:GetName() .. "Text"):SetText("Raid Mark X Offset: " .. (value > 0 and "+" or "") .. value)
if BNP.RefreshRaidMarkPositions then BNP:RefreshRaidMarkPositions() end
end)
frame.raidMarkXOffsetSlider = raidMarkXOffset
local raidMarkYOffset = CreateSlider(totemsPage, "Raid Mark Y Offset: 0", -50, 50, 1, -270, 220, 150)
raidMarkYOffset:SetScript("OnValueChanged", function()
if not BNP_DB or frame.BNPSyncingRaidMarkControls then return end
local value = RoundSignedInteger(this:GetValue())
BNP_DB.raidMarkYOffset = value
getglobal(this:GetName() .. "Text"):SetText("Raid Mark Y Offset: " .. (value > 0 and "+" or "") .. value)
if BNP.RefreshRaidMarkPositions then BNP:RefreshRaidMarkPositions() end
end)
frame.raidMarkYOffsetSlider = raidMarkYOffset
-- Quest indicators
CreateSection(totemsPage, "Quest Indicators", -334)
local questPlateIndicators = CreateCheck(totemsPage, "Enable Quest Icons", -368, function()
BNP_DB.questPlateIndicators = this:GetChecked() and true or false
if BNP.SetQuestPlateRuntimeEnabled then
BNP:SetQuestPlateRuntimeEnabled(BNP_DB.questPlateIndicators, true)
else
if BNP_DB.questPlateIndicators and BNP.RequestQuestPlateObjectiveUpdate then
BNP:RequestQuestPlateObjectiveUpdate()
end
if BNP.RefreshQuestPlateIndicators then BNP:RefreshQuestPlateIndicators() end
end
if frame.UpdateDependentControls then frame:UpdateDependentControls() end
end)
frame.questPlateIndicatorsCheck = questPlateIndicators
local questIconSize = CreateSlider(totemsPage, "Quest Icon Size", 6, 64, 1, -416, 28, 150)
questIconSize:SetScript("OnValueChanged", function()
if not BNP_DB then return end
local value = math.floor(this:GetValue() + 0.5)
BNP_DB.questPlateIconSize = value
getglobal(this:GetName() .. "Text"):SetText("Quest Icon Size: " .. value)
if BNP.RefreshQuestPlateIndicators then BNP:RefreshQuestPlateIndicators() end
end)
frame.questPlateIconSizeSlider = questIconSize
local questXOffset = CreateSlider(totemsPage, "Quest Icon X Offset", -100, 100, 1, -482, 28, 150)
questXOffset:SetScript("OnValueChanged", function()
if not BNP_DB then return end
local value = RoundSignedInteger(this:GetValue())
BNP_DB.questPlateXOffset = value
getglobal(this:GetName() .. "Text"):SetText("Quest Icon X Offset: " .. (value > 0 and "+" or "") .. value)
if BNP.RefreshQuestPlateIndicators then BNP:RefreshQuestPlateIndicators() end
end)
frame.questPlateXOffsetSlider = questXOffset
local questYOffset = CreateSlider(totemsPage, "Quest Icon Y Offset", -100, 100, 1, -482, 220, 150)
questYOffset:SetScript("OnValueChanged", function()
if not BNP_DB then return end
local value = RoundSignedInteger(this:GetValue())
BNP_DB.questPlateYOffset = value
getglobal(this:GetName() .. "Text"):SetText("Quest Icon Y Offset: " .. (value > 0 and "+" or "") .. value)
if BNP.RefreshQuestPlateIndicators then BNP:RefreshQuestPlateIndicators() end
end)
frame.questPlateYOffsetSlider = questYOffset
local questNote = totemsPage:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
questNote:SetPoint("TOPLEFT", totemsPage, "TOPLEFT", 28, -526)
questNote:SetWidth(350)
questNote:SetJustifyH("LEFT")
questNote:SetText("Shows remaining kill/item quest objectives beside matching mob nameplates.")
frame.questPlateNote = questNote
local questStatus = totemsPage:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
questStatus:SetPoint("TOPLEFT", totemsPage, "TOPLEFT", 28, -560)
questStatus:SetWidth(350)
questStatus:SetJustifyH("LEFT")
frame.questPlateStatus = questStatus
-- CASTBAR TAB ------------------------------------------------------------
local castbarPage = frame.pages.castbar
CreateSection(castbarPage, "Castbar", -4)
local castbars = CreateCheck(castbarPage, "Castbars", -42, function()
local enabled = this:GetChecked() and true or false
if BNP.SetCastbarsEnabled then BNP:SetCastbarsEnabled(enabled) else BNP_DB.castbars = enabled end
if frame.UpdateDependentControls then frame:UpdateDependentControls() end
end)
frame.castbarsCheck = castbars
local castbarTest = CreateCheck(castbarPage, "Test Castbars", -42, function()
if BNP.SetCastbarTestMode then BNP:SetCastbarTestMode(this:GetChecked() and true or false) end
end, 128)
castbarTest:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetText("Test Castbars", 1, 0.82, 0)
GameTooltip:AddLine("Shows a looping simulated castbar on every visible nameplate.", 1, 1, 1, true)
GameTooltip:AddLine("Use it to adjust style, height, font size and offsets live. The test mode is not saved.", 0.8, 0.8, 0.8, true)
GameTooltip:Show()
end)
castbarTest:SetScript("OnLeave", function() GameTooltip:Hide() end)
frame.castbarTestCheck = castbarTest
local castbarIcon = CreateCheck(castbarPage, "Spell Icon", -42, function()
BNP_DB = BNP_DB or {}
BNP_DB.castbarIcon = this:GetChecked() and true or false
if BNP.RefreshCastbarLayout then BNP:RefreshCastbarLayout() end
end, 250)
frame.castbarIconCheck = castbarIcon
local castbarStyleLabel = castbarPage:CreateFontString(nil, "OVERLAY", "GameFontNormal")
castbarStyleLabel:SetPoint("TOPLEFT", castbarPage, "TOPLEFT", 28, -82)
castbarStyleLabel:SetText("Castbar Style")
castbarStyleLabel:SetTextColor(1.00, 0.82, 0.00)
local castbarStyleDropdown = CreateFrame("Frame", "BNPCastbarStyleDropdown", castbarPage, "UIDropDownMenuTemplate")
castbarStyleDropdown:SetPoint("TOPLEFT", castbarPage, "TOPLEFT", 10, -94)
UIDropDownMenu_SetWidth(150, castbarStyleDropdown)
local castbarStyleLabels = {
modern = "Modern",
classic = "Classic",
}
local castbarHeight
local function UpdateCastbarHeightRange(style)
if not castbarHeight then return end
BNP_DB = BNP_DB or {}
local minValue = style == "classic" and 8 or 4
castbarHeight:SetMinMaxValues(minValue, 20)
getglobal(castbarHeight:GetName() .. "Low"):SetText(tostring(minValue))
local value = tonumber(BNP_DB.castbarHeight) or BNP:GetCastbarHeight()
value = math.floor(value + 0.5)
if value < minValue then
value = minValue
BNP_DB.castbarHeight = value
elseif value > 20 then
value = 20
BNP_DB.castbarHeight = value
end
castbarHeight:SetValue(value)
getglobal(castbarHeight:GetName() .. "Text"):SetText("Castbar Height: " .. value)
end
local function SetCastbarStyle(style)
if not castbarStyleLabels[style] then style = "modern" end
BNP_DB.castbarStyle = style
UIDropDownMenu_SetSelectedValue(castbarStyleDropdown, style)
UIDropDownMenu_SetText(castbarStyleLabels[style], castbarStyleDropdown)
UpdateCastbarHeightRange(style)
if BNP.RefreshCastbarStyle then BNP:RefreshCastbarStyle() elseif BNP.RefreshCastbarLayout then BNP:RefreshCastbarLayout() end
end
UIDropDownMenu_Initialize(castbarStyleDropdown, function()
local styles = { "modern", "classic" }
local n
for n = 1, table.getn(styles) do
local style = styles[n]
local info = {}
info.text = castbarStyleLabels[style]
info.value = style
info.func = function() SetCastbarStyle(this.value) end
info.checked = (BNP:GetCastbarStyle() == style)
UIDropDownMenu_AddButton(info)
end
end)
frame.castbarStyleLabel = castbarStyleLabel
frame.castbarStyleDropdown = castbarStyleDropdown
frame.SetCastbarStyle = SetCastbarStyle
castbarHeight = CreateSlider(castbarPage, "Castbar Height", 4, 20, 1, -94, 220, 150)
castbarHeight:SetScript("OnValueChanged", function()
if not BNP_DB then return end
local value = math.floor(this:GetValue() + 0.5)
BNP_DB.castbarHeight = value
getglobal(this:GetName() .. "Text"):SetText("Castbar Height: " .. value)
if BNP.RefreshCastbarLayout then BNP:RefreshCastbarLayout() elseif BNP.RefreshCastbarHeights then BNP:RefreshCastbarHeights() end
end)
UpdateCastbarHeightRange(BNP:GetCastbarStyle())
frame.castbarHeightSlider = castbarHeight
frame.UpdateCastbarHeightRange = UpdateCastbarHeightRange
local castbarFontSize = CreateSlider(castbarPage, "Castbar Font Size", 6, 18, 1, -166, 28, 150)
castbarFontSize:SetScript("OnValueChanged", function()
if not BNP_DB then return end
local value = math.floor(this:GetValue() + 0.5)
BNP_DB.castbarFontSize = value
getglobal(this:GetName() .. "Text"):SetText("Castbar Font Size: " .. value)
if BNP.RefreshCastbarLayout then BNP:RefreshCastbarLayout() end
end)
frame.castbarFontSizeSlider = castbarFontSize
local castbarXOffset = CreateSlider(castbarPage, "Castbar X Offset", -50, 50, 1, -238, 28, 150)
castbarXOffset:SetScript("OnValueChanged", function()
if not BNP_DB then return end
local value = RoundSignedInteger(this:GetValue())
BNP_DB.castbarXOffset = value
getglobal(this:GetName() .. "Text"):SetText("Castbar X Offset: " .. (value > 0 and "+" or "") .. value)
if BNP.RefreshCastbarLayout then BNP:RefreshCastbarLayout() end
end)
frame.castbarXOffsetSlider = castbarXOffset
local castbarYOffset = CreateSlider(castbarPage, "Castbar Y Offset", -50, 50, 1, -238, 220, 150)
castbarYOffset:SetScript("OnValueChanged", function()
if not BNP_DB then return end
local value = RoundSignedInteger(this:GetValue())
BNP_DB.castbarYOffset = value
getglobal(this:GetName() .. "Text"):SetText("Castbar Y Offset: " .. (value > 0 and "+" or "") .. value)
if BNP.RefreshCastbarLayout then BNP:RefreshCastbarLayout() end
end)
frame.castbarYOffsetSlider = castbarYOffset
-- TARGET TAB -------------------------------------------------------------
local targetPage = frame.pages.target
-- Target highlight: glow and arrows share one color, but keep independent
-- enable switches and sizing controls.
CreateSection(targetPage, "Target Highlight", -4)
local targetFocus = CreateCheck(targetPage, "Target Glow", -38, function()
BNP_DB.targetFocus = this:GetChecked() and true or false
if BNP.RefreshTargetFocus then BNP:RefreshTargetFocus() end
if frame.UpdateDependentControls then frame:UpdateDependentControls() end
end)
frame.targetFocusCheck = targetFocus
local targetArrows = CreateCheck(targetPage, "Target Arrows", -38, function()
BNP_DB.targetArrows = this:GetChecked() and true or false
if BNP.RefreshTargetFocus then BNP:RefreshTargetFocus() end
if frame.UpdateDependentControls then frame:UpdateDependentControls() end
end)
targetArrows:ClearAllPoints()
targetArrows:SetPoint("TOPLEFT", targetPage, "TOPLEFT", 204, -38)
frame.targetArrowsCheck = targetArrows
-- One shared free RGB color for glow and arrows.
local targetColorSwatch, targetColorLabel = CreateColorSwatch(targetPage, "Glow / Arrow Color", -86, 28,
function() return BNP:GetTargetColor() end,
function(r, g, b) BNP:SetTargetColor(r, g, b) end,
1.00, 1.00, 1.00,
"Choose the shared color used by Target Glow and Target Arrows.",
"Restores the default BNP glow / arrow color.")
frame.targetColorSwatch = targetColorSwatch
frame.targetColorLabel = targetColorLabel
local glowSize = CreateSlider(targetPage, "Target Glow Size", 0, 20, 1, -150)
glowSize:SetWidth(145)
glowSize:SetScript("OnValueChanged", function()
if not BNP_DB then return end
local value = math.floor(this:GetValue() + 0.5)
BNP_DB.targetGlowSize = value
getglobal(this:GetName() .. "Text"):SetText("Target Glow Size: +" .. value .. " px")
if BNP.RefreshTargetFocus then BNP:RefreshTargetFocus() end
end)
frame.targetGlowSizeSlider = glowSize
local glowOpacity = CreateSlider(targetPage, "Target Glow Opacity", 20, 100, 5, -208)
glowOpacity:SetWidth(145)
glowOpacity:SetScript("OnValueChanged", function()
if not BNP_DB then return end
local value = math.floor(this:GetValue() + 0.5)
BNP_DB.targetGlowOpacity = value / 100
getglobal(this:GetName() .. "Text"):SetText("Target Glow Opacity: " .. value .. "%")
if BNP.RefreshTargetFocus then BNP:RefreshTargetFocus() end
end)
frame.targetGlowOpacitySlider = glowOpacity
local arrowStyleLabel = targetPage:CreateFontString(nil, "OVERLAY", "GameFontNormal")
arrowStyleLabel:SetPoint("TOPLEFT", targetPage, "TOPLEFT", 210, -88)
arrowStyleLabel:SetText("Arrow Style")
arrowStyleLabel:SetTextColor(1.00, 0.82, 0.00)
frame.arrowStyleLabel = arrowStyleLabel
local arrowStyleDropdown = CreateFrame("Frame", "BNPTargetArrowStyleDropdown", targetPage, "UIDropDownMenuTemplate")
arrowStyleDropdown:SetPoint("TOPLEFT", targetPage, "TOPLEFT", 192, -100)
UIDropDownMenu_SetWidth(140, arrowStyleDropdown)
local arrowStyleLabels = {
chevron = "Chevron",
triangle = "Triangle",
slim_triangle = "Slim Triangle",
double_chevron = "Double Chevron",
diamond_tip = "Diamond Tip",
}
local function SetTargetArrowStyle(style)
if not arrowStyleLabels[style] then style = "chevron" end
BNP_DB.targetArrowStyle = style
UIDropDownMenu_SetSelectedValue(arrowStyleDropdown, style)
UIDropDownMenu_SetText(arrowStyleLabels[style], arrowStyleDropdown)
if BNP.RefreshTargetFocus then BNP:RefreshTargetFocus() end
end
UIDropDownMenu_Initialize(arrowStyleDropdown, function()
local styles = { "chevron", "triangle", "slim_triangle", "double_chevron", "diamond_tip" }
local n
for n = 1, table.getn(styles) do
local style = styles[n]
local info = {}
info.text = arrowStyleLabels[style]
info.value = style
info.func = function() SetTargetArrowStyle(this.value) end
info.checked = ((BNP_DB and BNP_DB.targetArrowStyle) or "chevron") == style
UIDropDownMenu_AddButton(info)
end
end)
frame.arrowStyleDropdown = arrowStyleDropdown
frame.SetTargetArrowStyle = SetTargetArrowStyle
local arrowSize = CreateSlider(targetPage, "Target Arrow Size", 10, 24, 1, -166)
arrowSize:ClearAllPoints()
arrowSize:SetPoint("TOPLEFT", targetPage, "TOPLEFT", 210, -166)
arrowSize:SetWidth(145)
arrowSize:SetScript("OnValueChanged", function()
if not BNP_DB then return end
local value = math.floor(this:GetValue() + 0.5)
BNP_DB.targetArrowSize = value
getglobal(this:GetName() .. "Text"):SetText("Target Arrow Size: " .. value)
if BNP.RefreshTargetFocus then BNP:RefreshTargetFocus() end
end)
frame.targetArrowSizeSlider = arrowSize
local thickArrows = CreateCheck(targetPage, "Thick Arrows", -220, function()
BNP_DB.targetArrowThick = this:GetChecked() and true or false
if BNP.RefreshTargetFocus then BNP:RefreshTargetFocus() end
end)
thickArrows:ClearAllPoints()
thickArrows:SetPoint("TOPLEFT", targetPage, "TOPLEFT", 204, -220)
frame.targetArrowThickCheck = thickArrows
local targetOnlyNameplates = CreateCheck(targetPage, "Only Show Target", -250, function()
BNP_DB.targetOnlyNameplates = this:GetChecked() and true or false
if BNP.RefreshTargetOnlyNameplates then BNP:RefreshTargetOnlyNameplates() end
if frame.UpdateDependentControls then frame:UpdateDependentControls() end
end)
targetOnlyNameplates:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetText("Only Show Target", 1, 0.82, 0)
GameTooltip:AddLine("While a target is selected, hides every other nameplate by making it fully transparent.", 1, 1, 1, true)
GameTooltip:AddLine("With no target selected, all nameplates stay visible so you can still select one. Disabling this option immediately restores normal visibility.", 0.8, 0.8, 0.8, true)
GameTooltip:Show()
end)
targetOnlyNameplates:SetScript("OnLeave", function() GameTooltip:Hide() end)
frame.targetOnlyNameplatesCheck = targetOnlyNameplates
-- Target border
CreateSection(targetPage, "Target Border", -280)
local targetBorderColor = CreateCheck(targetPage, "Target Border Color", -314, function()
BNP_DB.targetBorderColorEnabled = this:GetChecked() and true or false
if BNP.RefreshTargetBorderColor then BNP:RefreshTargetBorderColor() end
if frame.UpdateDependentControls then frame:UpdateDependentControls() end
end)
frame.targetBorderColorCheck = targetBorderColor
-- Keep the compact color box on the same row, slightly lower than the label
-- so it visually aligns with the checkbox text.
local targetBorderColorSwatch = CreateColorSwatch(targetPage, "", -320, 166,
function() return BNP:GetTargetBorderColor() end,
function(r, g, b) BNP:SetTargetBorderColor(r, g, b) end,
1.00, 0.20, 0.20,
"Choose the color of the native nameplate border while this unit is your target.",
"Restores the default BNP target border color.")
targetBorderColorSwatch:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetText("Target Border Color", 1, 0.82, 0)
GameTooltip:AddLine("Click the color box to choose the border color used on your current target.", 1, 1, 1, true)
GameTooltip:AddLine("Target Glow and Target Arrows use the separate Glow / Arrow Color above.", 0.8, 0.8, 0.8, true)
GameTooltip:Show()
end)
targetBorderColorSwatch:SetScript("OnLeave", function() GameTooltip:Hide() end)
frame.targetBorderColorSwatch = targetBorderColorSwatch
local targetBorderBold = CreateCheck(targetPage, "Bold Target Border", -346, function()
BNP_DB.targetBorderBold = this:GetChecked() and true or false
if BNP.RefreshTargetBorderColor then BNP:RefreshTargetBorderColor() end
end)
targetBorderBold:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetText("Bold Target Border", 1, 0.82, 0)
GameTooltip:AddLine("Uses a thicker version of the same target border. No second frame is added.", 1, 1, 1, true)
GameTooltip:Show()
end)
targetBorderBold:SetScript("OnLeave", function() GameTooltip:Hide() end)
frame.targetBorderBoldCheck = targetBorderBold
-- Target scale
CreateSection(targetPage, "Target Scale", -398)
local targetScaleEnabled = CreateCheck(targetPage, "Enable Target Scale", -432, function()
BNP_DB.targetScaleEnabled = this:GetChecked() and true or false
if BNP.RefreshTargetScale then BNP:RefreshTargetScale() end
if frame.UpdateDependentControls then frame:UpdateDependentControls() end
end)
frame.targetScaleEnabledCheck = targetScaleEnabled
local targetScale = CreateSlider(targetPage, "Target Scale: 1.20x", 1.00, 1.50, 0.05, -478, 28, 220)
targetScale:SetScript("OnValueChanged", function()
if not BNP_DB then return end
local value = Round(this:GetValue(), 0.05)
if value < 1.00 then value = 1.00 end
if value > 1.50 then value = 1.50 end
BNP_DB.targetScale = value
getglobal(this:GetName() .. "Text"):SetText(string.format("Target Scale: %.2fx", value))
if BNP.RefreshTargetScale then BNP:RefreshTargetScale() end
end)
frame.targetScaleSlider = targetScale
-- OTHER DEBUFFS TAB ------------------------------------------------------
BuildOtherDebuffsOptions(frame, frame.pages.other, CreateSection)
-- TOOLS TAB --------------------------------------------------------------
local toolsPage = frame.pages.tools
CreateSection(toolsPage, "Tools", -4)
local auraRecorder = CreateFrame("Button", nil, toolsPage, "UIPanelButtonTemplate")
auraRecorder:SetPoint("TOPLEFT", toolsPage, "TOPLEFT", 28, -48)
auraRecorder:SetWidth(254)
auraRecorder:SetHeight(24)
auraRecorder:SetText("Missing Spell / Aura...")
auraRecorder:SetScript("OnClick", function() BNP:OpenAuraRecorder() end)
auraRecorder:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetText("Missing Spell / Aura Recorder", 1, 0.82, 0)
GameTooltip:AddLine("Records target aura IDs, SuperWoW events and refresh signals in one copyable report.", 1, 1, 1, true)
GameTooltip:Show()
end)
auraRecorder:SetScript("OnLeave", function() GameTooltip:Hide() end)
frame.auraRecorderButton = auraRecorder
local note = toolsPage:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
note:SetPoint("TOPLEFT", toolsPage, "TOPLEFT", 28, -88)
note:SetWidth(330)
note:SetJustifyH("LEFT")
note:SetText("Diagnostic tools for testing missing spells and aura refresh behavior.")
function frame:UpdateDependentControls()
local castbarsEnabled = BNP:AreCastbarsEnabled()
SetCheckEnabled(self.castbarTestCheck, castbarsEnabled)
SetCheckEnabled(self.castbarIconCheck, castbarsEnabled)
local ccEnabled = BNP:AreCrowdControlEnabled()
local debuffsEnabled = BNP:AreDebuffsEnabled()
local debuffPosition = BNP.GetDebuffPosition and BNP:GetDebuffPosition() or "top"
local ccPosition = BNP.GetCCPosition and BNP:GetCCPosition() or debuffPosition
local separateRowAvailable = ccEnabled and debuffsEnabled and (debuffPosition == "top" or debuffPosition == "top_left" or debuffPosition == "top_right") and ccPosition == "top"
SetCheckEnabled(self.separateCCRowCheck, separateRowAvailable)
if self.separateCCRowCheck then
self.separateCCRowCheck:SetChecked(separateRowAvailable and BNP:IsSeparateCCRowEnabled() or false)
end
SetCheckEnabled(self.showOtherCCsCheck, ccEnabled)
if self.debuffYOffsetSlider then
self.debuffYOffsetSlider:SetAlpha(debuffsEnabled and 1.0 or 0.45)
if self.debuffYOffsetSlider.EnableMouse then self.debuffYOffsetSlider:EnableMouse(debuffsEnabled) end
end
if self.ccYOffsetSlider then
self.ccYOffsetSlider:SetAlpha(ccEnabled and 1.0 or 0.45)
if self.ccYOffsetSlider.EnableMouse then self.ccYOffsetSlider:EnableMouse(ccEnabled) end
end
local comboEnabled = BNP.AreComboPointsEnabled and BNP:AreComboPointsEnabled() or false
if self.comboPointsYOffsetSlider then
self.comboPointsYOffsetSlider:SetAlpha(comboEnabled and 1.0 or 0.45)
if self.comboPointsYOffsetSlider.EnableMouse then self.comboPointsYOffsetSlider:EnableMouse(comboEnabled) end
end
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.personalNameplateHideLevelCheck, personalEnabled)
SetCheckEnabled(self.personalNameplateDebuffsCheck, personalEnabled)
SetCheckEnabled(self.personalNameplateBuffsCheck, 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, self.personalNameplateBuffYOffsetSlider, self.personalNameplateDebuffXOffsetSlider, self.personalNameplateBuffXOffsetSlider }
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
swatch:SetAlpha(enabled and 1.0 or 0.45)
if swatch.EnableMouse then swatch:EnableMouse(enabled) end
if swatch.BNPLabel then
swatch.BNPLabel:SetTextColor(enabled and 1 or 0.5, enabled and 0.82 or 0.5, enabled and 0 or 0.5)
end
end
SetTankSwatchEnabled(self.invertAggroColorSwatch, invertTankEnabled)
SetTankSwatchEnabled(self.invertNoAggroColorSwatch, invertTankEnabled)
local nameColorEnabled = BNP.IsCustomNameColorEnabled and BNP:IsCustomNameColorEnabled() or false
if self.nameColorSwatch then
self.nameColorSwatch:SetAlpha(nameColorEnabled and 1.0 or 0.45)
if self.nameColorSwatch.EnableMouse then self.nameColorSwatch:EnableMouse(nameColorEnabled) end
if self.nameColorSwatch.BNPLabel then
self.nameColorSwatch.BNPLabel:SetTextColor(nameColorEnabled and 1 or 0.5, nameColorEnabled and 0.82 or 0.5, nameColorEnabled and 0 or 0.5)
end
end
local immunitiesEnabled = BNP.ArePvPImmunitiesEnabled and BNP:ArePvPImmunitiesEnabled() or false
if self.immunityIconSlider then
self.immunityIconSlider:SetAlpha(immunitiesEnabled and 1.0 or 0.45)
if self.immunityIconSlider.EnableMouse then self.immunityIconSlider:EnableMouse(immunitiesEnabled) end
end
if self.immunityYOffsetSlider then
self.immunityYOffsetSlider:SetAlpha(immunitiesEnabled and 1.0 or 0.45)
if self.immunityYOffsetSlider.EnableMouse then self.immunityYOffsetSlider:EnableMouse(immunitiesEnabled) end
end
if self.immunityPositionLabel then
self.immunityPositionLabel:SetTextColor(immunitiesEnabled and 1 or 0.5, immunitiesEnabled and 0.82 or 0.5, immunitiesEnabled and 0 or 0.5)
end
if self.immunityPositionDropdown then
self.immunityPositionDropdown:SetAlpha(immunitiesEnabled and 1.0 or 0.45)
if immunitiesEnabled then
if UIDropDownMenu_EnableDropDown then UIDropDownMenu_EnableDropDown(self.immunityPositionDropdown) end
else
if UIDropDownMenu_DisableDropDown then UIDropDownMenu_DisableDropDown(self.immunityPositionDropdown) end
end
end
local totemsEnabled = BNP.AreTotemIndicatorsEnabled and BNP:AreTotemIndicatorsEnabled() or false
if self.totemIconSlider then
self.totemIconSlider:SetAlpha(totemsEnabled and 1.0 or 0.45)
if self.totemIconSlider.EnableMouse then self.totemIconSlider:EnableMouse(totemsEnabled) end
end
local questEnabled = BNP.AreQuestPlateIndicatorsEnabled and BNP:AreQuestPlateIndicatorsEnabled() or false
local questSliders = { self.questPlateIconSizeSlider, self.questPlateXOffsetSlider, self.questPlateYOffsetSlider }
local questIndex
for questIndex = 1, table.getn(questSliders) do
local slider = questSliders[questIndex]
if slider then
slider:SetAlpha(questEnabled and 1.0 or 0.45)
if slider.EnableMouse then slider:EnableMouse(questEnabled) end
end
end
if self.questPlateStatus then
local dependency = BNP.GetQuestPlateDependencyName and BNP:GetQuestPlateDependencyName() or nil
if dependency then
self.questPlateStatus:SetText("Database: " .. dependency .. " detected")
self.questPlateStatus:SetTextColor(0.35, 1.0, 0.45)
else
self.questPlateStatus:SetText("Database: Questie-Octo / pfQuest not detected")
self.questPlateStatus:SetTextColor(1.0, 0.35, 0.25)
end
end
local glowEnabled = BNP:IsTargetFocusEnabled()
local arrowsEnabled = BNP.AreTargetArrowsEnabled and BNP:AreTargetArrowsEnabled() or false
local targetColorEnabled = glowEnabled or arrowsEnabled
if self.targetColorLabel then
self.targetColorLabel:SetTextColor(targetColorEnabled and 1 or 0.5, targetColorEnabled and 0.82 or 0.5, targetColorEnabled and 0 or 0.5)
end
if self.targetColorSwatch then
self.targetColorSwatch:SetAlpha(targetColorEnabled and 1.0 or 0.45)
if self.targetColorSwatch.EnableMouse then self.targetColorSwatch:EnableMouse(targetColorEnabled) end
end
local targetBorderEnabled = BNP.IsTargetBorderColorEnabled and BNP:IsTargetBorderColorEnabled() or false
SetCheckEnabled(self.targetBorderBoldCheck, targetBorderEnabled)
if self.targetBorderColorSwatch then
self.targetBorderColorSwatch:SetAlpha(targetBorderEnabled and 1.0 or 0.45)
if self.targetBorderColorSwatch.EnableMouse then self.targetBorderColorSwatch:EnableMouse(targetBorderEnabled) end
end
if self.targetGlowSizeSlider then
self.targetGlowSizeSlider:SetAlpha(glowEnabled and 1.0 or 0.45)
if self.targetGlowSizeSlider.EnableMouse then self.targetGlowSizeSlider:EnableMouse(glowEnabled) end
end
if self.targetGlowOpacitySlider then
self.targetGlowOpacitySlider:SetAlpha(glowEnabled and 1.0 or 0.45)
if self.targetGlowOpacitySlider.EnableMouse then self.targetGlowOpacitySlider:EnableMouse(glowEnabled) end
end
if self.arrowStyleLabel then
self.arrowStyleLabel:SetTextColor(arrowsEnabled and 1 or 0.5, arrowsEnabled and 0.82 or 0.5, arrowsEnabled and 0 or 0.5)
end
if self.arrowStyleDropdown then
self.arrowStyleDropdown:SetAlpha(arrowsEnabled and 1.0 or 0.45)
if arrowsEnabled then
if UIDropDownMenu_EnableDropDown then UIDropDownMenu_EnableDropDown(self.arrowStyleDropdown) end
else
if UIDropDownMenu_DisableDropDown then UIDropDownMenu_DisableDropDown(self.arrowStyleDropdown) end
end
end
if self.targetArrowSizeSlider then
self.targetArrowSizeSlider:SetAlpha(arrowsEnabled and 1.0 or 0.45)
if self.targetArrowSizeSlider.EnableMouse then self.targetArrowSizeSlider:EnableMouse(arrowsEnabled) end
end
SetCheckEnabled(self.targetArrowThickCheck, arrowsEnabled)
local targetOnlyEnabled = BNP.IsTargetOnlyNameplatesEnabled and BNP:IsTargetOnlyNameplatesEnabled() or false
if self.nonTargetAlphaSlider then
self.nonTargetAlphaSlider:SetAlpha(targetOnlyEnabled and 0.45 or 1.0)
if self.nonTargetAlphaSlider.EnableMouse then self.nonTargetAlphaSlider:EnableMouse(not targetOnlyEnabled) end
end
local targetScaleEnabled = BNP.IsTargetScaleEnabled and BNP:IsTargetScaleEnabled() or false
if self.targetScaleSlider then
self.targetScaleSlider:SetAlpha(targetScaleEnabled and 1.0 or 0.45)
if self.targetScaleSlider.EnableMouse then self.targetScaleSlider:EnableMouse(targetScaleEnabled) end
end
end
frame:ShowTab("nameplates")
self.optionsFrame = frame
end
function BNP:SyncOptions()
self:CreateOptions()
local frame = self.optionsFrame
frame.scaleSlider:SetValue(self:GetNameplateScale())
if frame.yOffsetSlider then
local yOffset = self:GetNameplateYOffset()
frame.yOffsetSlider:SetValue(yOffset)
getglobal(frame.yOffsetSlider:GetName() .. "Text"):SetText("Nameplate Y Offset: +" .. yOffset)
end
if frame.nameFontSizeSlider or frame.nameFontYOffsetSlider then
frame.BNPSyncingNameControls = true
if frame.nameFontSizeSlider then
local value = self:GetNameFontSize()
frame.nameFontSizeSlider:SetValue(value)
getglobal(frame.nameFontSizeSlider:GetName() .. "Text"):SetText("Name Font Size: " .. value)
end
if frame.nameFontYOffsetSlider then
local value = self:GetNameFontYOffset()
frame.nameFontYOffsetSlider:SetValue(value)
getglobal(frame.nameFontYOffsetSlider:GetName() .. "Text"):SetText("Name Y Offset: " .. (value > 0 and "+" or "") .. value)
end
frame.BNPSyncingNameControls = nil
end
if frame.comboPointsYOffsetSlider then
local value = self:GetComboPointsYOffset()
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.personalNameplateHideLevelCheck then
frame.personalNameplateHideLevelCheck:SetChecked(self.IsPersonalNameplateLevelHidden and self:IsPersonalNameplateLevelHidden() 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.personalNameplateBuffsCheck then
frame.personalNameplateBuffsCheck:SetChecked(self.IsPersonalNameplateBuffsEnabled and self:IsPersonalNameplateBuffsEnabled() 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.personalNameplateBuffYOffsetSlider then
local value = self.GetPersonalNameplateBuffYOffset and self:GetPersonalNameplateBuffYOffset() or 0
frame.personalNameplateBuffYOffsetSlider:SetValue(value)
getglobal(frame.personalNameplateBuffYOffsetSlider:GetName() .. "Text"):SetText("Buff Y Offset: " .. (value > 0 and "+" or "") .. value)
end
if frame.personalNameplateDebuffXOffsetSlider then
local value = self.GetPersonalNameplateDebuffXOffset and self:GetPersonalNameplateDebuffXOffset() or 0
frame.personalNameplateDebuffXOffsetSlider:SetValue(value)
getglobal(frame.personalNameplateDebuffXOffsetSlider:GetName() .. "Text"):SetText("Debuff X Offset: " .. (value > 0 and "+" or "") .. value)
end
if frame.personalNameplateBuffXOffsetSlider then
local value = self.GetPersonalNameplateBuffXOffset and self:GetPersonalNameplateBuffXOffset() or 0
frame.personalNameplateBuffXOffsetSlider:SetValue(value)
getglobal(frame.personalNameplateBuffXOffsetSlider:GetName() .. "Text"):SetText("Buff X Offset: " .. (value > 0 and "+" or "") .. value)
end
if frame.debuffYOffsetSlider then
local value = self:GetDebuffYOffset()
frame.debuffYOffsetSlider:SetValue(value)
getglobal(frame.debuffYOffsetSlider:GetName() .. "Text"):SetText("Debuff Y Offset: " .. (value > 0 and "+" or "") .. value)
end
if frame.ccYOffsetSlider then
local value = self:GetCCYOffset()
frame.ccYOffsetSlider:SetValue(value)
getglobal(frame.ccYOffsetSlider:GetName() .. "Text"):SetText("CC Y Offset: " .. (value > 0 and "+" or "") .. value)
end
if frame.immunityYOffsetSlider then
local value = self:GetImmunityYOffset()
frame.immunityYOffsetSlider:SetValue(value)
getglobal(frame.immunityYOffsetSlider:GetName() .. "Text"):SetText("Immunity Y Offset: " .. (value > 0 and "+" or "") .. value)
end
if frame.nonTargetAlphaSlider then
local alphaPercent = math.floor((self:GetNonTargetAlpha() * 100) + 0.5)
frame.nonTargetAlphaSlider:SetValue(alphaPercent)
getglobal(frame.nonTargetAlphaSlider:GetName() .. "Text"):SetText("Non-Target Alpha: " .. alphaPercent .. "%")
end
frame.iconSlider:SetValue(self:GetIconSize())
if frame.auraFontSizeSlider then
local auraFontSize = self:GetAuraFontSize()
frame.auraFontSizeSlider:SetValue(auraFontSize)
getglobal(frame.auraFontSizeSlider:GetName() .. "Text"):SetText("Aura Font Size: " .. auraFontSize)
end
if frame.cooldownSpiralCheck then frame.cooldownSpiralCheck:SetChecked(self:IsAuraCooldownSpiralEnabled()) end
if frame.ccIconSlider then
local ccSize = self:GetCCIconSize()
frame.ccIconSlider:SetValue(ccSize)
getglobal(frame.ccIconSlider:GetName() .. "Text"):SetText("CC Icon Size: " .. ccSize)
end
if frame.immunityIconSlider then
local immunitySize = self:GetImmunityIconSize()
frame.immunityIconSlider:SetValue(immunitySize)
getglobal(frame.immunityIconSlider:GetName() .. "Text"):SetText("Immunity Icon Size: " .. immunitySize)
end
if frame.totemIconSlider then
local totemSize = self:GetTotemIconSize()
frame.totemIconSlider:SetValue(totemSize)
getglobal(frame.totemIconSlider:GetName() .. "Text"):SetText("Totem Icon Size: " .. totemSize)
end
local castbarStyle = self:GetCastbarStyle()
if frame.UpdateCastbarHeightRange then frame.UpdateCastbarHeightRange(castbarStyle) end
frame.castbarHeightSlider:SetValue(self:GetCastbarHeight())
if frame.castbarIconCheck then frame.castbarIconCheck:SetChecked(self:IsCastbarIconEnabled()) end
if frame.castbarStyleDropdown then
local labels = { modern = "Modern", classic = "Classic" }
UIDropDownMenu_SetSelectedValue(frame.castbarStyleDropdown, castbarStyle)
UIDropDownMenu_SetText(labels[castbarStyle] or "Modern", frame.castbarStyleDropdown)
end
if frame.castbarFontSizeSlider then
local size = self:GetCastbarFontSize()
frame.castbarFontSizeSlider:SetValue(size)
getglobal(frame.castbarFontSizeSlider:GetName() .. "Text"):SetText("Castbar Font Size: " .. size)
end
if frame.castbarXOffsetSlider then
local offset = self:GetCastbarXOffset()
frame.castbarXOffsetSlider:SetValue(offset)
getglobal(frame.castbarXOffsetSlider:GetName() .. "Text"):SetText("Castbar X Offset: " .. (offset > 0 and "+" or "") .. offset)
end
if frame.castbarYOffsetSlider then
local offset = self:GetCastbarYOffset()
frame.castbarYOffsetSlider:SetValue(offset)
getglobal(frame.castbarYOffsetSlider:GetName() .. "Text"):SetText("Castbar Y Offset: " .. (offset > 0 and "+" or "") .. offset)
end
frame.classColorsCheck:SetChecked(self:AreClassColorsEnabled())
if frame.darkNameplateBorderCheck then
frame.darkNameplateBorderCheck:SetChecked(self:IsDarkNameplateBorderEnabled())
end
if frame.hideNameplateBorderCheck then
frame.hideNameplateBorderCheck:SetChecked(self:IsNameplateBorderHidden())
end
if frame.hideNameplateLevelCheck then
frame.hideNameplateLevelCheck:SetChecked(self:IsNameplateLevelHidden())
end
if frame.blackHealthbarBackgroundCheck then
frame.blackHealthbarBackgroundCheck:SetChecked(self:IsBlackHealthbarBackgroundEnabled())
end
if frame.hidePlayerNamesCheck then frame.hidePlayerNamesCheck:SetChecked(self:HidePlayerNamesEnabled()) end
if frame.hideNPCNamesCheck then frame.hideNPCNamesCheck:SetChecked(self:HideNPCNamesEnabled()) end
if frame.customNameColorCheck then frame.customNameColorCheck:SetChecked(self:IsCustomNameColorEnabled()) end
if frame.nameColorSwatch and frame.nameColorSwatch.RefreshColor then frame.nameColorSwatch:RefreshColor() end
if frame.debuffsCheck then frame.debuffsCheck:SetChecked(self:AreDebuffsEnabled()) end
if frame.debuffPositionDropdown then
local position = self:GetDebuffPosition()
local labels = { top = "Top Mid", top_left = "Top Left", top_right = "Top Right", left = "Left", right = "Right", bottom_mid = "Bottom Mid", bottom_left = "Bottom Left", bottom_right = "Bottom Right" }
UIDropDownMenu_SetSelectedValue(frame.debuffPositionDropdown, position)
UIDropDownMenu_SetText(labels[position] or "Top Mid", frame.debuffPositionDropdown)
end
if frame.ccPositionDropdown then
local position = self:GetCCPosition()
local labels = { top = "Top", left = "Left", right = "Right" }
UIDropDownMenu_SetSelectedValue(frame.ccPositionDropdown, position)
UIDropDownMenu_SetText(labels[position] or "Top", frame.ccPositionDropdown)
end
if frame.crowdControlCheck then frame.crowdControlCheck:SetChecked(self:AreCrowdControlEnabled()) end
if frame.separateCCRowCheck then frame.separateCCRowCheck:SetChecked(self:IsSeparateCCRowEnabled()) end
if frame.showOtherCCsCheck then frame.showOtherCCsCheck:SetChecked(self:ShowOtherPlayersCCs()) end
if frame.otherDebuffsEnabledCheck then frame.otherDebuffsEnabledCheck:SetChecked(self:AreOtherDebuffsEnabled()) end
if frame.RefreshOtherDebuffPage then frame:RefreshOtherDebuffPage() end
if frame.pvpImmunitiesCheck then frame.pvpImmunitiesCheck:SetChecked(self:ArePvPImmunitiesEnabled()) end
if frame.immunityPositionDropdown then
local position = self:GetImmunityPosition()
local labels = { top = "Top", left = "Left", right = "Right" }
UIDropDownMenu_SetSelectedValue(frame.immunityPositionDropdown, position)
UIDropDownMenu_SetText(labels[position] or "Top", frame.immunityPositionDropdown)
end
if frame.totemIndicatorsCheck then frame.totemIndicatorsCheck:SetChecked(self:AreTotemIndicatorsEnabled()) end
if frame.questPlateIndicatorsCheck then
frame.questPlateIndicatorsCheck:SetChecked(self.AreQuestPlateIndicatorsEnabled and self:AreQuestPlateIndicatorsEnabled() or false)
end
if frame.questPlateIconSizeSlider then
local size = self.GetQuestPlateIconSize and self:GetQuestPlateIconSize() or 16
frame.questPlateIconSizeSlider:SetValue(size)
getglobal(frame.questPlateIconSizeSlider:GetName() .. "Text"):SetText("Quest Icon Size: " .. size)
end
if frame.questPlateXOffsetSlider then
local value = self.GetQuestPlateXOffset and self:GetQuestPlateXOffset() or 10
frame.questPlateXOffsetSlider:SetValue(value)
getglobal(frame.questPlateXOffsetSlider:GetName() .. "Text"):SetText("Quest Icon X Offset: " .. (value > 0 and "+" or "") .. value)
end
if frame.questPlateYOffsetSlider then
local value = self.GetQuestPlateYOffset and self:GetQuestPlateYOffset() or -7
frame.questPlateYOffsetSlider:SetValue(value)
getglobal(frame.questPlateYOffsetSlider:GetName() .. "Text"):SetText("Quest Icon Y Offset: " .. (value > 0 and "+" or "") .. value)
end
frame.castbarsCheck:SetChecked(self:AreCastbarsEnabled())
if frame.castbarTestCheck then
frame.castbarTestCheck:SetChecked(self.IsCastbarTestMode and self:IsCastbarTestMode() or false)
end
frame.tankCheck:SetChecked(self:IsTankModeEnabled())
if frame.invertTankColorsCheck then frame.invertTankColorsCheck:SetChecked(self:AreTankModeColorsInverted()) end
if frame.invertAggroColorSwatch and frame.invertAggroColorSwatch.RefreshColor then frame.invertAggroColorSwatch:RefreshColor() end
if frame.invertNoAggroColorSwatch and frame.invertNoAggroColorSwatch.RefreshColor then frame.invertNoAggroColorSwatch:RefreshColor() end
if frame.comboPointsCheck and comboOptionsClass then
frame.comboPointsCheck:SetChecked(self:AreComboPointsEnabled())
end
if frame.darkComboPointBorderCheck and comboOptionsClass then
frame.darkComboPointBorderCheck:SetChecked(self:IsDarkComboPointBorderEnabled())
end
if frame.healthTextDropdown then
local mode = self:GetHealthTextMode()
UIDropDownMenu_SetSelectedValue(frame.healthTextDropdown, mode)
local labels = { off = "Off", percent = "Percent", hp = "HP", both = "HP + Percent" }
UIDropDownMenu_SetText(labels[mode] or "Off", frame.healthTextDropdown)
end
if frame.healthTextFontSizeSlider then
frame.BNPSyncingHealthTextControls = true
local size = self:GetHealthTextFontSize()
frame.healthTextFontSizeSlider:SetValue(size)
getglobal(frame.healthTextFontSizeSlider:GetName() .. "Text"):SetText("Health Font Size: " .. size)
frame.BNPSyncingHealthTextControls = nil
end
if frame.healthTextOutlineDropdown then
local outline = self:GetHealthTextOutline()
local labels = { none = "None", outline = "Outline", thick = "Thick Outline" }
UIDropDownMenu_SetSelectedValue(frame.healthTextOutlineDropdown, outline)
UIDropDownMenu_SetText(labels[outline] or "Outline", frame.healthTextOutlineDropdown)
end
frame.targetFocusCheck:SetChecked(self:IsTargetFocusEnabled())
if frame.targetOnlyNameplatesCheck then
frame.targetOnlyNameplatesCheck:SetChecked(self.IsTargetOnlyNameplatesEnabled and self:IsTargetOnlyNameplatesEnabled() or false)
end
if frame.targetBorderColorCheck then
frame.targetBorderColorCheck:SetChecked(self.IsTargetBorderColorEnabled and self:IsTargetBorderColorEnabled() or false)
end
if frame.targetBorderBoldCheck then
frame.targetBorderBoldCheck:SetChecked(self.IsTargetBorderBoldEnabled and self:IsTargetBorderBoldEnabled() or false)
end
if frame.targetBorderColorSwatch and frame.targetBorderColorSwatch.RefreshColor then
frame.targetBorderColorSwatch:RefreshColor()
end
if frame.targetScaleEnabledCheck then
frame.targetScaleEnabledCheck:SetChecked(self.IsTargetScaleEnabled and self:IsTargetScaleEnabled() or false)
end
if frame.targetScaleSlider then
local targetScale = self.GetTargetScale and self:GetTargetScale() or 1.20
frame.targetScaleSlider:SetValue(targetScale)
getglobal(frame.targetScaleSlider:GetName() .. "Text"):SetText(string.format("Target Scale: %.2fx", targetScale))
end
if frame.raidMarkPositionDropdown then
local position = self.GetRaidMarkPosition and self:GetRaidMarkPosition() or "top"
local labels = { top = "Top", left = "Left", right = "Right" }
UIDropDownMenu_SetSelectedValue(frame.raidMarkPositionDropdown, position)
UIDropDownMenu_SetText(labels[position] or "Top", frame.raidMarkPositionDropdown)
end
if frame.raidMarkXOffsetSlider or frame.raidMarkYOffsetSlider then
frame.BNPSyncingRaidMarkControls = true
if frame.raidMarkXOffsetSlider then
local value = self.GetRaidMarkXOffset and self:GetRaidMarkXOffset() or 0
frame.raidMarkXOffsetSlider:SetValue(value)
getglobal(frame.raidMarkXOffsetSlider:GetName() .. "Text"):SetText("Raid Mark X Offset: " .. (value > 0 and "+" or "") .. value)
end
if frame.raidMarkYOffsetSlider then
local value = self.GetRaidMarkYOffset and self:GetRaidMarkYOffset() or 0
frame.raidMarkYOffsetSlider:SetValue(value)
getglobal(frame.raidMarkYOffsetSlider:GetName() .. "Text"):SetText("Raid Mark Y Offset: " .. (value > 0 and "+" or "") .. value)
end
frame.BNPSyncingRaidMarkControls = nil
end
if frame.targetArrowsCheck then
frame.targetArrowsCheck:SetChecked(self.AreTargetArrowsEnabled and self:AreTargetArrowsEnabled() or false)
end
if frame.targetColorSwatch and frame.targetColorSwatch.RefreshColor then
frame.targetColorSwatch:RefreshColor()
end
if frame.arrowStyleDropdown then
local style = self:GetTargetArrowStyle()
local labels = {
chevron = "Chevron",
triangle = "Triangle",
slim_triangle = "Slim Triangle",
double_chevron = "Double Chevron",
diamond_tip = "Diamond Tip",
}
UIDropDownMenu_SetSelectedValue(frame.arrowStyleDropdown, style)
UIDropDownMenu_SetText(labels[style] or "Chevron", frame.arrowStyleDropdown)
end
if frame.targetArrowSizeSlider then
local arrowSize = self:GetTargetArrowSize()
frame.targetArrowSizeSlider:SetValue(arrowSize)
getglobal(frame.targetArrowSizeSlider:GetName() .. "Text"):SetText("Target Arrow Size: " .. arrowSize)
end
if frame.targetArrowThickCheck then
frame.targetArrowThickCheck:SetChecked(self.AreTargetArrowsThick and self:AreTargetArrowsThick() or false)
end
if frame.targetGlowSizeSlider then
local glowSize = self:GetTargetGlowSize()
frame.targetGlowSizeSlider:SetValue(glowSize)
getglobal(frame.targetGlowSizeSlider:GetName() .. "Text"):SetText("Target Glow Size: +" .. glowSize .. " px")
end
if frame.targetGlowOpacitySlider then
local opacityPercent = math.floor((self:GetTargetGlowOpacity() * 100) + 0.5)
frame.targetGlowOpacitySlider:SetValue(opacityPercent)
getglobal(frame.targetGlowOpacitySlider:GetName() .. "Text"):SetText("Target Glow Opacity: " .. opacityPercent .. "%")
end
if frame.UpdateDependentControls then frame:UpdateDependentControls() end
frame.versionText:SetText("Version " .. tostring(self.version or "?"))
end
function BNP:ToggleOptions()
self:SyncOptions()
if self.optionsFrame:IsShown() then
self.optionsFrame:Hide()
else
self.optionsFrame:Show()
end
end
function BNP:CreateMinimapButton()
if self.minimapButton then return end
BNP_DB = BNP_DB or {}
if BNP_DB.minimapAngle == nil then BNP_DB.minimapAngle = 225 end
local button = CreateFrame("Button", "BNPMinimapButton", Minimap)
button:SetWidth(31)
button:SetHeight(31)
button:SetFrameStrata("MEDIUM")
button:SetFrameLevel((Minimap:GetFrameLevel() or 1) + 8)
button:RegisterForClicks("LeftButtonUp")
button:RegisterForDrag("LeftButton")
button:EnableMouse(true)
button:SetMovable(true)
-- Classic round minimap-button background.
local bg = button:CreateTexture(nil, "BACKGROUND")
bg:SetTexture("Interface\\Minimap\\UI-Minimap-Background")
bg:SetWidth(22)
bg:SetHeight(22)
bg:SetPoint("CENTER", button, "CENTER", 0, 0)
button.bg = bg
-- Addon identity: simple BN+ lettering instead of a spell/item icon.
-- Rendered directly by the client, so no external texture file is needed.
local iconBG = button:CreateTexture(nil, "ARTWORK")
iconBG:SetTexture(0.03, 0.03, 0.03, 1)
iconBG:SetWidth(20)
iconBG:SetHeight(20)
iconBG:SetPoint("CENTER", button, "CENTER", 0, 0)
button.iconBG = iconBG
local label = button:CreateFontString(nil, "OVERLAY")
label:SetFont("Fonts\\FRIZQT__.TTF", 10, "OUTLINE")
label:SetPoint("CENTER", button, "CENTER", 0, 0)
label:SetText("BN+")
label:SetTextColor(0.20, 0.60, 1.00)
button.label = label
-- Standard Blizzard circular minimap border.
local border = button:CreateTexture(nil, "OVERLAY")
border:SetTexture("Interface\\Minimap\\MiniMap-TrackingBorder")
border:SetWidth(54)
border:SetHeight(54)
border:SetPoint("TOPLEFT", button, "TOPLEFT", 0, 0)
button.border = border
local highlight = button:CreateTexture(nil, "HIGHLIGHT")
highlight:SetTexture("Interface\\Minimap\\UI-Minimap-ZoomButton-Highlight")
highlight:SetBlendMode("ADD")
highlight:SetAllPoints(button)
button.highlight = highlight
local function UpdatePosition()
local angle = tonumber(BNP_DB.minimapAngle) or 225
local radius = 78
local rad = math.rad(angle)
button:ClearAllPoints()
button:SetPoint(
"CENTER",
Minimap,
"CENTER",
math.cos(rad) * radius,
math.sin(rad) * radius
)
end
local function UpdateAngleFromCursor()
local mx, my = Minimap:GetCenter()
local scale = 1
if UIParent.GetEffectiveScale then
scale = UIParent:GetEffectiveScale() or 1
elseif UIParent.GetScale then
scale = UIParent:GetScale() or 1
end
local cx, cy = GetCursorPosition()
cx = cx / scale
cy = cy / scale
local dx = cx - mx
local dy = cy - my
local angle
if math.atan2 then
angle = math.deg(math.atan2(dy, dx))
else
if dx == 0 then
angle = dy >= 0 and 90 or -90
else
angle = math.deg(math.atan(dy / dx))
if dx < 0 then angle = angle + 180 end
end
end
BNP_DB.minimapAngle = angle
UpdatePosition()
end
button:SetScript("OnDragStart", function()
this:SetScript("OnUpdate", UpdateAngleFromCursor)
end)
button:SetScript("OnDragStop", function()
this:SetScript("OnUpdate", nil)
UpdateAngleFromCursor()
end)
button:SetScript("OnClick", function()
BNP:ToggleOptions()
end)
button:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_LEFT")
GameTooltip:SetText("Blizz Nameplates+")
GameTooltip:AddLine("Left-click: Open settings", 1, 1, 1)
GameTooltip:AddLine("Drag: Move minimap button", 0.8, 0.8, 0.8)
GameTooltip:Show()
end)
button:SetScript("OnLeave", function()
GameTooltip:Hide()
end)
UpdatePosition()
self.minimapButton = button
end