266 lines
7.6 KiB
Lua
266 lines
7.6 KiB
Lua
BNP = BNP or {}
|
|
|
|
local function Enabled()
|
|
return BNP.IsHealthTextEnabled and BNP:IsHealthTextEnabled()
|
|
end
|
|
|
|
local function ApplyHealthTextFont(text)
|
|
if not text then return end
|
|
|
|
local size = BNP.GetHealthTextFontSize and BNP:GetHealthTextFontSize() or 9
|
|
local outline = BNP.GetHealthTextOutline and BNP:GetHealthTextOutline() or "outline"
|
|
local flags = ""
|
|
if outline == "outline" then
|
|
flags = "OUTLINE"
|
|
elseif outline == "thick" then
|
|
flags = "THICKOUTLINE"
|
|
end
|
|
|
|
-- Avoid reapplying the font on every health update. Only touch the FontString
|
|
-- when the user actually changes size/outline or when the text is first made.
|
|
if text.BNPFontSize ~= size or text.BNPFontOutline ~= outline then
|
|
text:SetFont("Fonts\\FRIZQT__.TTF", size, flags)
|
|
text.BNPFontSize = size
|
|
text.BNPFontOutline = outline
|
|
end
|
|
end
|
|
|
|
local function CreateHealthText(plate)
|
|
if plate.BNPHealthPercent then return plate.BNPHealthPercent end
|
|
if not plate or not plate.healthbar then return nil end
|
|
|
|
-- Parent the text directly to the Blizzard health StatusBar. This keeps it
|
|
-- above the bar texture and automatically follows every frame-level change
|
|
-- made for Target Glow / permanent target priority. No separate holder can
|
|
-- become stale during target switches or nameplate recycling.
|
|
local anchor = plate.healthbar
|
|
local text = anchor:CreateFontString(nil, "OVERLAY")
|
|
ApplyHealthTextFont(text)
|
|
text:SetPoint("CENTER", anchor, "CENTER", 3, 0)
|
|
text:SetTextColor(1, 1, 1)
|
|
text:Hide()
|
|
|
|
plate.BNPHealthPercent = text
|
|
return text
|
|
end
|
|
|
|
local function GetPercentFromBar(plate)
|
|
if not plate or not plate.healthbar then return nil end
|
|
|
|
local minValue, maxValue = plate.healthbar:GetMinMaxValues()
|
|
local value = plate.healthbar:GetValue()
|
|
|
|
if value and minValue and maxValue and maxValue > minValue then
|
|
local pct = ((value - minValue) / (maxValue - minValue)) * 100
|
|
return math.floor(pct + 0.5)
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
local function GetPercentFromUnit(plate)
|
|
if not plate or not plate.GetName then return nil end
|
|
|
|
local token = plate:GetName(1)
|
|
if not token or not UnitExists(token) then return nil end
|
|
if not UnitHealth or not UnitHealthMax then return nil end
|
|
|
|
local health = UnitHealth(token)
|
|
local maxHealth = UnitHealthMax(token)
|
|
|
|
if health and maxHealth and maxHealth > 0 then
|
|
return math.floor((health / maxHealth) * 100 + 0.5)
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
local function GetHealthFromUnit(plate)
|
|
if not plate or not plate.GetName then return nil, nil end
|
|
|
|
local token = plate:GetName(1)
|
|
if not token or not UnitExists(token) then return nil, nil end
|
|
if not UnitHealth or not UnitHealthMax then return nil, nil end
|
|
|
|
local health = tonumber(UnitHealth(token))
|
|
local maxHealth = tonumber(UnitHealthMax(token))
|
|
|
|
if health and maxHealth and maxHealth > 0 then
|
|
return health, maxHealth
|
|
end
|
|
|
|
return nil, nil
|
|
end
|
|
|
|
local function GetHealthFromBar(plate)
|
|
if not plate or not plate.healthbar then return nil, nil end
|
|
|
|
local minValue, maxValue = plate.healthbar:GetMinMaxValues()
|
|
local value = plate.healthbar:GetValue()
|
|
|
|
value = tonumber(value)
|
|
minValue = tonumber(minValue)
|
|
maxValue = tonumber(maxValue)
|
|
|
|
if value and minValue and maxValue and maxValue > minValue then
|
|
return value - minValue, maxValue - minValue
|
|
end
|
|
|
|
return nil, nil
|
|
end
|
|
|
|
local function FormatHealth(value)
|
|
value = tonumber(value)
|
|
if not value then return nil end
|
|
if value < 0 then value = 0 end
|
|
|
|
-- Keep boss/player values readable inside the compact Blizzard healthbar.
|
|
if value >= 1000000 then
|
|
local shown = math.floor((value / 1000000) * 10 + 0.5) / 10
|
|
return tostring(shown) .. "m"
|
|
elseif value >= 10000 then
|
|
return tostring(math.floor(value / 1000 + 0.5)) .. "k"
|
|
elseif value >= 1000 then
|
|
local shown = math.floor((value / 1000) * 10 + 0.5) / 10
|
|
return tostring(shown) .. "k"
|
|
end
|
|
|
|
return tostring(math.floor(value + 0.5))
|
|
end
|
|
|
|
local function UpdatePlate(plate)
|
|
if not plate or not plate.healthbar then return end
|
|
|
|
local text = plate.BNPHealthPercent
|
|
|
|
if not Enabled() or not plate:IsShown() then
|
|
if text then text:Hide() end
|
|
return
|
|
end
|
|
|
|
text = text or CreateHealthText(plate)
|
|
if not text then return end
|
|
ApplyHealthTextFont(text)
|
|
|
|
local mode = BNP:GetHealthTextMode()
|
|
local health, maxHealth = GetHealthFromUnit(plate)
|
|
|
|
-- Fallback for clients/units where the SuperWoW unit token is temporarily
|
|
-- unavailable. This may be normalized health on stock Vanilla.
|
|
if health == nil or maxHealth == nil then
|
|
health, maxHealth = GetHealthFromBar(plate)
|
|
end
|
|
|
|
local pct = nil
|
|
if health and maxHealth and maxHealth > 0 then
|
|
pct = math.floor((health / maxHealth) * 100 + 0.5)
|
|
else
|
|
pct = GetPercentFromBar(plate)
|
|
if pct == nil then pct = GetPercentFromUnit(plate) end
|
|
end
|
|
|
|
if pct ~= nil then
|
|
if pct < 0 then pct = 0 end
|
|
if pct > 100 then pct = 100 end
|
|
end
|
|
|
|
local hpText = FormatHealth(health)
|
|
local display = nil
|
|
|
|
if mode == "percent" then
|
|
if pct ~= nil then display = tostring(pct) .. "%" end
|
|
elseif mode == "hp" then
|
|
display = hpText
|
|
elseif mode == "both" then
|
|
if hpText and pct ~= nil then
|
|
display = hpText .. " | " .. tostring(pct) .. "%"
|
|
elseif hpText then
|
|
display = hpText
|
|
elseif pct ~= nil then
|
|
display = tostring(pct) .. "%"
|
|
end
|
|
end
|
|
|
|
if not display or display == "" then
|
|
text:Hide()
|
|
return
|
|
end
|
|
|
|
if text.BNPLastHealthText ~= display then
|
|
text:SetText(display)
|
|
text.BNPLastHealthText = display
|
|
end
|
|
text:Show()
|
|
end
|
|
|
|
-- Intentionally simple fallback: only reads values and updates text.
|
|
-- No SetScript calls on healthbar, no SetAlpha, no color changes.
|
|
local updater = CreateFrame("Frame")
|
|
local elapsed = 0
|
|
local healthScanBucket = 0
|
|
local healthNextBucket = 0
|
|
|
|
local function GetHealthScanBucket(plate)
|
|
if plate.BNPHealthScanBucket == nil then
|
|
plate.BNPHealthScanBucket = healthNextBucket
|
|
healthNextBucket = 1 - healthNextBucket
|
|
end
|
|
return plate.BNPHealthScanBucket
|
|
end
|
|
|
|
updater:SetScript("OnUpdate", function()
|
|
elapsed = elapsed + arg1
|
|
if elapsed < 0.05 then return end
|
|
elapsed = 0
|
|
|
|
-- Health text is off by default. Do not walk every known nameplate when there
|
|
-- is literally nothing to update. Option changes refresh immediately.
|
|
if not Enabled() then return end
|
|
|
|
-- Keep the old 0.10s per-plate update rate, but distribute it over two frames.
|
|
healthScanBucket = 1 - healthScanBucket
|
|
|
|
local plate
|
|
for plate in pairs(BNP.plates or {}) do
|
|
if GetHealthScanBucket(plate) == healthScanBucket then
|
|
if plate:IsShown() then
|
|
UpdatePlate(plate)
|
|
elseif plate.BNPHealthPercent then
|
|
plate.BNPHealthPercent:Hide()
|
|
end
|
|
end
|
|
end
|
|
end)
|
|
|
|
function BNP:RefreshHealthPercent()
|
|
local plate
|
|
for plate in pairs(self.plates or {}) do
|
|
if plate and plate:IsShown() then
|
|
UpdatePlate(plate)
|
|
elseif plate and plate.BNPHealthPercent then
|
|
plate.BNPHealthPercent:Hide()
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Refresh immediately when Blizzard reuses a nameplate instead of waiting for
|
|
-- the next 0.10 s fallback pass.
|
|
if BNP.libnameplate then
|
|
table.insert(BNP.libnameplate.OnShow, function(plate)
|
|
local current = plate or this
|
|
if current then UpdatePlate(current) end
|
|
end)
|
|
end
|
|
|
|
-- Health text is parented directly to the healthbar and therefore follows
|
|
-- target frame-level changes automatically. PLAYER_TARGET_CHANGED used to
|
|
-- force a synchronous refresh of every plate even though no health value had
|
|
-- changed; the regular 0.10s bucketed updater already keeps values current.
|
|
local healthEvents = CreateFrame("Frame")
|
|
healthEvents:RegisterEvent("PLAYER_ENTERING_WORLD")
|
|
healthEvents:SetScript("OnEvent", function()
|
|
if BNP.RefreshHealthPercent then
|
|
BNP:RefreshHealthPercent()
|
|
end
|
|
end)
|