Files
Dusk-92 439156479a Raise pet health text above unit frame artwork
Render Real Health Numbers pet health/power text through a dedicated PetFrame overlay at a higher frame level while preserving the existing 9 px font size and anchors.
2026-09-08 13:08:04 +02:00

299 lines
8.6 KiB
Lua

local _G = ShaguTweaks.GetGlobalEnv()
local T = ShaguTweaks.T
local Abbreviate = ShaguTweaks.Abbreviate
local hooksecurefunc = hooksecurefunc or ShaguTweaks.hooksecurefunc
local module = ShaguTweaks:register({
title = T["Real Health Numbers"],
description = T["Shows health and power numbers on player, pet and target unit frames. On Vanilla servers, target health may be estimated when exact values are unavailable."],
expansions = { ["vanilla"] = true },
category = T["Unit Frames"],
enabled = nil,
})
module.enable = function(self)
if ShaguTweaks.RealHealthNumbersInstalled then return end
ShaguTweaks.RealHealthNumbersInstalled = true
if ShaguTweaks.libhealth and ShaguTweaks.libhealth.Enable then
ShaguTweaks.libhealth:Enable()
end
local function HideNativeText(text)
if text and text.SetAlpha then
text:SetAlpha(0)
end
end
local nativeTexts = {
PlayerFrameHealthBar and PlayerFrameHealthBar.TextString,
PlayerFrameManaBar and PlayerFrameManaBar.TextString,
TargetFrameHealthBar and TargetFrameHealthBar.TextString,
TargetFrameManaBar and TargetFrameManaBar.TextString,
PetFrameHealthBar and PetFrameHealthBar.TextString,
PetFrameManaBar and PetFrameManaBar.TextString,
_G.PlayerFrameAlternatePowerBarText,
_G.TargetHPText,
_G.TargetHPPercText,
}
for _, text in pairs(nativeTexts) do
HideNativeText(text)
end
self.valueTexts = self.valueTexts or {}
local function CreateValueText(parent, fontSize)
if not parent then return end
local text = parent:CreateFontString(nil, "OVERLAY", "GameFontWhite")
text:SetFont(STANDARD_TEXT_FONT, fontSize or 10, "OUTLINE")
text:SetHeight(32)
text:SetJustifyH("CENTER")
text:SetTextColor(1, 1, 1, 1)
return text
end
local function AnchorText(text, bar, y)
if not text or not bar then return end
text:ClearAllPoints()
text:SetPoint("CENTER", bar, "CENTER", 0, y or 0)
end
if not self.valueTexts.playerHealth then
self.valueTexts.playerHealth = CreateValueText(PlayerFrameHealthBar, 10)
self.valueTexts.playerMana = CreateValueText(PlayerFrameManaBar, 10)
self.valueTexts.targetHealth = CreateValueText(TargetFrameHealthBar, 10)
self.valueTexts.targetMana = CreateValueText(TargetFrameManaBar, 10)
local petTextParent
if PetFrame and (PetFrameHealthBar or PetFrameManaBar) then
self.petTextOverlay = CreateFrame("Frame", nil, PetFrame)
self.petTextOverlay:SetAllPoints(PetFrame)
local petTextLevel = PetFrame:GetFrameLevel()
if PetFrameHealthBar then
petTextLevel = math.max(petTextLevel, PetFrameHealthBar:GetFrameLevel())
end
if PetFrameManaBar then
petTextLevel = math.max(petTextLevel, PetFrameManaBar:GetFrameLevel())
end
-- Keep the text on PetFrame's strata while rendering it above the
-- nested pet artwork and status bars.
self.petTextOverlay:SetFrameLevel(petTextLevel + 5)
petTextParent = self.petTextOverlay
end
if PetFrameHealthBar then
self.valueTexts.petHealth = CreateValueText(petTextParent or PetFrameHealthBar, 9)
end
if PetFrameManaBar then
self.valueTexts.petMana = CreateValueText(petTextParent or PetFrameManaBar, 9)
end
if _G.PlayerFrameAlternatePowerBar then
self.valueTexts.playerAlternatePower =
CreateValueText(_G.PlayerFrameAlternatePowerBar, 10)
end
end
local function PositionTexts()
local playerHealthY = PlayerFrameHealthBar:GetHeight() >= 20 and -7 or 0.5
local targetHealthY = TargetFrameHealthBar:GetHeight() >= 20 and -7 or 0.5
AnchorText(self.valueTexts.playerHealth, PlayerFrameHealthBar, playerHealthY)
AnchorText(self.valueTexts.playerMana, PlayerFrameManaBar, 0.5)
AnchorText(self.valueTexts.targetHealth, TargetFrameHealthBar, targetHealthY)
AnchorText(self.valueTexts.targetMana, TargetFrameManaBar, 0.5)
AnchorText(self.valueTexts.petHealth, PetFrameHealthBar, 0)
AnchorText(self.valueTexts.petMana, PetFrameManaBar, -2)
if _G.PlayerFrameAlternatePowerBar then
AnchorText(
self.valueTexts.playerAlternatePower,
_G.PlayerFrameAlternatePowerBar,
0
)
end
end
local function SetValue(text, value)
if not text then return end
if value == nil then
text:SetText("")
text:Hide()
return
end
text:SetText(tostring(Abbreviate(value)))
text:Show()
end
local function UpdateHealth(text, unit, useEstimator)
if not text then return end
if not UnitExists(unit) then
text:SetText("")
text:Hide()
return
end
if unit == "target" and (UnitIsDead("target") or UnitIsGhost("target")) then
text:SetText("")
text:Hide()
return
end
local cur = UnitHealth(unit)
local max = UnitHealthMax(unit)
local known = true
if useEstimator and ShaguTweaks.libhealth
and ShaguTweaks.libhealth.GetUnitHealth then
cur, max, known = ShaguTweaks.libhealth:GetUnitHealth(unit)
end
if not max or max <= 0 then
text:SetText("")
text:Hide()
return
end
if useEstimator and not known and max == 100 then
text:SetText(tostring(cur or 0) .. "%")
text:Show()
return
end
SetValue(text, cur or 0)
end
local function UpdatePower(text, unit)
if not text then return end
if not UnitExists(unit) then
text:SetText("")
text:Hide()
return
end
local max = UnitManaMax(unit)
if not max or max <= 0 then
text:SetText("")
text:Hide()
return
end
SetValue(text, UnitMana(unit) or 0)
end
local function UpdatePlayer()
UpdateHealth(self.valueTexts.playerHealth, "player", false)
UpdatePower(self.valueTexts.playerMana, "player")
end
local function UpdateTarget()
UpdateHealth(self.valueTexts.targetHealth, "target", true)
UpdatePower(self.valueTexts.targetMana, "target")
end
local function UpdatePet()
UpdateHealth(self.valueTexts.petHealth, "pet", false)
UpdatePower(self.valueTexts.petMana, "pet")
end
local function UpdateAlternatePower()
local bar = _G.PlayerFrameAlternatePowerBar
local text = self.valueTexts.playerAlternatePower
if not bar or not text then return end
if not bar:IsShown() then
text:SetText("")
text:Hide()
return
end
local _, max = bar:GetMinMaxValues()
if not max or max <= 0 then
text:SetText("")
text:Hide()
return
end
local value = bar:GetValue() or 0
SetValue(text, math.floor(value + 0.5))
end
self.valueEvents = self.valueEvents or CreateFrame("Frame")
local events = self.valueEvents
events:RegisterEvent("PLAYER_ENTERING_WORLD")
events:RegisterEvent("PLAYER_TARGET_CHANGED")
events:RegisterEvent("PET_UI_UPDATE")
events:RegisterEvent("UNIT_HEALTH")
events:RegisterEvent("UNIT_MAXHEALTH")
events:RegisterEvent("UNIT_MANA")
events:RegisterEvent("UNIT_MAXMANA")
events:RegisterEvent("UNIT_RAGE")
events:RegisterEvent("UNIT_MAXRAGE")
events:RegisterEvent("UNIT_ENERGY")
events:RegisterEvent("UNIT_MAXENERGY")
events:RegisterEvent("UNIT_FOCUS")
events:RegisterEvent("UNIT_MAXFOCUS")
events:RegisterEvent("UNIT_HAPPINESS")
events:RegisterEvent("UNIT_MAXHAPPINESS")
events:RegisterEvent("UNIT_DISPLAYPOWER")
events:SetScript("OnEvent", function()
if event == "PLAYER_ENTERING_WORLD" then
PositionTexts()
UpdatePlayer()
UpdateTarget()
UpdatePet()
UpdateAlternatePower()
elseif event == "PLAYER_TARGET_CHANGED" then
UpdateTarget()
elseif event == "PET_UI_UPDATE" then
UpdatePet()
elseif arg1 == "player" then
UpdatePlayer()
UpdateAlternatePower()
elseif arg1 == "target" then
UpdateTarget()
elseif arg1 == "pet" then
UpdatePet()
end
end)
local alternatePowerBar = _G.PlayerFrameAlternatePowerBar
if alternatePowerBar and self.valueTexts.playerAlternatePower then
hooksecurefunc(alternatePowerBar, "SetValue", UpdateAlternatePower)
hooksecurefunc(alternatePowerBar, "Show", UpdateAlternatePower)
hooksecurefunc(alternatePowerBar, "Hide", UpdateAlternatePower)
end
-- All enabled modules are initialized during VARIABLES_LOADED in an
-- unspecified table order. Reposition once on the next frame so this module
-- automatically fits either the stock bars or Unit Frame Big Health.
local deferred = CreateFrame("Frame")
deferred:SetScript("OnUpdate", function()
this:SetScript("OnUpdate", nil)
this:Hide()
PositionTexts()
for _, text in pairs(nativeTexts) do
HideNativeText(text)
end
UpdatePlayer()
UpdateTarget()
UpdatePet()
UpdateAlternatePower()
end)
end