Restore Real Health Numbers and fix aura dragging

Restore Real Health Numbers as a standalone module with a lazy Vanilla health estimator fallback, keep Big Health presentation-only, clarify the module tooltip, and fix aura dragging for BuffButton0/BuffButton32/TempEnchant1 without StartMoving().
This commit is contained in:
Dusk-92
2026-08-31 11:10:03 +02:00
committed by GitHub
parent 6d59f60c27
commit ccfeb0810f
6 changed files with 583 additions and 164 deletions
+4 -1
View File
@@ -34,6 +34,8 @@ Settings: **Esc → Advanced Options**.
- Expanded Item Rarity Borders support for merchant, quest, mail, trade, crafting and loot frames.
- Added independent Item Rarity Glows using the shared item rarity engine.
- Expanded Movable Unit Frames to handle Player, Target, Party, Minimap, Buffs, Debuffs and Weapon Buffs through one shared Ctrl+Shift mover and alignment grid.
- Restored Real Health Numbers as an independent module, with direct values when available and a lazy Vanilla health estimator fallback for percentage-only targets.
- Unit Frame Big Health is presentation-only again and no longer forces numeric health/power text.
- Various stability fixes across legacy ShaguTweaks modules.
## 🔷 Mods using ClassicAPI directly
@@ -47,6 +49,7 @@ Settings: **Esc → Advanced Options**.
- Item Rarity Borders
- Item Rarity Glows
- Movable Unit Frames
- Real Health Numbers
- Nameplate Class Colors
- Nameplate Castbar
- No Toggle
@@ -79,6 +82,7 @@ Other modules can also benefit indirectly from ClassicAPI through shared ShaguTw
- Energy & Mana Tick
- Health Colors
- Movable Unit Frames
- Real Health Numbers
- Enemy Target Castbar
- Target Debuff Timer
@@ -146,7 +150,6 @@ Other modules can also benefit indirectly from ClassicAPI through shared ShaguTw
The following original ShaguTweaks modules are not included as standalone modules because their functionality is either integrated elsewhere or no longer required:
- **Chat Links** → integrated into **Chat Tweaks**
- **Real Health Numbers** → value display integrated into **Unit Frame Big Health**
- **Blue Shaman Class Colors** → no longer required; supported natively by Turtle WoW-like clients
- **Movable Unit Frames Extended** → integrated into **Movable Unit Frames**
+2
View File
@@ -40,6 +40,7 @@ libs\libdebuff.lua
libs\libunitscan.lua
libs\libnameplate.lua
libs\libpredict.lua
libs\libhealth.lua
# custom
mods\custom.lua
@@ -80,6 +81,7 @@ mods\chat-spam-filter.lua
mods\hide-gryphons.lua
mods\worldmap-colors.lua
mods\unitframes-bighealth.lua
mods\health-numbers.lua
mods\unitframes-classportrait.lua
mods\unitframes-energy-tick.lua
mods\cooldown-numbers.lua
+206
View File
@@ -0,0 +1,206 @@
local _G = ShaguTweaks.GetGlobalEnv()
-- Real-health fallback for stock Vanilla servers.
--
-- Turtle WoW and some custom clients expose real UnitHealth/UnitHealthMax
-- values directly. Stock 1.12 often exposes target health as 0..100 instead.
-- Keep the original ShaguTweaks estimation idea, but leave it completely
-- dormant until Real Health Numbers asks for it and only listen to combat/
-- health events while the current target actually needs estimation.
local mobdb = {}
local targetKey
local damage = 0
local percent = 0
local libhealth = CreateFrame("Frame")
libhealth.enabled = false
libhealth.reqhit = 2
libhealth.reqdmg = 10
local function InitCache()
ShaguTweaks_cache = ShaguTweaks_cache or {}
ShaguTweaks_cache["libhealth"] = ShaguTweaks_cache["libhealth"] or {}
mobdb = ShaguTweaks_cache["libhealth"]
end
local function IsPercentHealth(unit)
if not unit or not UnitExists(unit) then return false end
local cur = _G.UnitHealth(unit)
local max = _G.UnitHealthMax(unit)
return cur ~= nil and max == 100 and cur <= 100
end
local function StopSampling()
libhealth:UnregisterEvent("UNIT_COMBAT")
libhealth:UnregisterEvent("UNIT_HEALTH")
end
local function StartSampling()
libhealth:RegisterEvent("UNIT_COMBAT")
libhealth:RegisterEvent("UNIT_HEALTH")
end
local function SelectTarget()
StopSampling()
damage = 0
percent = _G.UnitHealth("target") or 0
targetKey = nil
if not IsPercentHealth("target") then return end
local name = UnitName("target")
local level = UnitLevel("target")
if not name or not level then return end
targetKey = string.format("%s:%s", name, level)
StartSampling()
end
local function GetCachedHealth(unitstr, cur, max)
local name = UnitName(unitstr)
local level = UnitLevel(unitstr)
if not name or not level then return cur, max end
local key = string.format("%s:%s", name, level)
local data = mobdb[key]
if data and data[1] and data[2]
and data[2] > libhealth.reqdmg
and (not data[3] or data[3] > libhealth.reqhit) then
return ceil(data[1] / 100 * cur), data[1], true
end
return cur, max
end
-- ClassicAPI can expose the real health deficit even when stock UnitHealth()
-- still reports a percentage. Use that as an immediate estimate when possible.
-- At 100% there is no deficit to derive a maximum from, so the historical
-- combat estimator/cache remains useful.
local function GetMissingHealthEstimate(unitstr, cur, max)
if max ~= 100 or cur >= 100 then return end
if type(_G.UnitHealthMissing) ~= "function" then return end
local missing = _G.UnitHealthMissing(unitstr)
local lostPercent = 100 - cur
if not missing or missing <= 0 or lostPercent <= 0 then return end
local estimatedMax = ceil(missing / lostPercent * 100)
if estimatedMax < 100 then return end
local estimatedCur = estimatedMax - missing
if estimatedCur < 0 then estimatedCur = 0 end
return estimatedCur, estimatedMax, true
end
function libhealth:GetUnitHealth(unitstr)
-- Preserve the old public library behavior for external consumers while
-- keeping the estimator lazy: the first actual query activates it.
if not self.enabled then self:Enable() end
local cur = _G.UnitHealth(unitstr)
local max = _G.UnitHealthMax(unitstr)
if not cur or not max then return cur or 0, max or 0 end
-- Real values are already available: do not estimate.
if cur > 100 or max > 100 or max < 100 then
return cur, max, true
end
local missingCur, missingMax, missingKnown =
GetMissingHealthEstimate(unitstr, cur, max)
if missingKnown then
return missingCur, missingMax, true
end
return GetCachedHealth(unitstr, cur, max)
end
function libhealth:GetUnitHealthByName(name, level, cur, max)
if not self.enabled then self:Enable() end
if not cur or not max then return cur or 0, max or 0 end
if cur > 100 or max > 100 or max < 100 then
return cur, max, true
end
if not name or not level then return cur, max end
local key = string.format("%s:%s", name, level)
local data = mobdb[key]
if data and data[1] and data[2]
and data[2] > libhealth.reqdmg
and (not data[3] or data[3] > libhealth.reqhit) then
return ceil(data[1] / 100 * cur), data[1], true
end
return cur, max
end
function libhealth:Enable()
if self.enabled then return end
self.enabled = true
InitCache()
self:RegisterEvent("PLAYER_ENTERING_WORLD")
self:RegisterEvent("PLAYER_TARGET_CHANGED")
SelectTarget()
end
function libhealth:Disable()
if not self.enabled then return end
self.enabled = false
self:UnregisterAllEvents()
targetKey = nil
damage = 0
percent = 0
end
libhealth:SetScript("OnEvent", function()
if not libhealth.enabled then return end
if event == "PLAYER_ENTERING_WORLD" then
InitCache()
SelectTarget()
elseif event == "PLAYER_TARGET_CHANGED" then
SelectTarget()
elseif targetKey and event == "UNIT_COMBAT" and arg1 == "target" then
if arg2 == "HEAL" then return end
local amount = tonumber(arg4) or 0
if amount > 0 then
damage = damage + amount
end
elseif targetKey and event == "UNIT_HEALTH" and arg1 == "target" then
local currentPercent = _G.UnitHealth("target") or 0
local diff = percent - currentPercent
if damage > 0 and diff > 0 then
local estimate = ceil(damage / diff * 100)
local data = mobdb[targetKey]
if not data or not data[2] or diff > data[2] then
mobdb[targetKey] = {
estimate,
diff,
data and data[3] and data[3] + 1 or 1,
}
elseif data then
data[3] = data[3] and data[3] + 1 or 1
end
end
damage = 0
percent = currentPercent
end
end)
ShaguTweaks.libhealth = libhealth
+279
View File
@@ -0,0 +1,279 @@
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)
if PetFrameHealthBar then
self.valueTexts.petHealth = CreateValueText(PetFrameHealthBar, 9)
end
if PetFrameManaBar then
self.valueTexts.petMana = CreateValueText(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
local targetHealthY = TargetFrameHealthBar:GetHeight() >= 20 and -7 or 0
AnchorText(self.valueTexts.playerHealth, PlayerFrameHealthBar, playerHealthY)
AnchorText(self.valueTexts.playerMana, PlayerFrameManaBar, 0)
AnchorText(self.valueTexts.targetHealth, TargetFrameHealthBar, targetHealthY)
AnchorText(self.valueTexts.targetMana, TargetFrameManaBar, 0)
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
+86 -8
View File
@@ -63,9 +63,9 @@ module.enable = function(self)
{ name = "PartyMemberFrame3", persist = true },
{ name = "PartyMemberFrame4", persist = true },
{ name = "Minimap", moveParent = true, persist = true },
{ name = "BuffButton0", persist = true, manualGroup = "buffs" },
{ name = "BuffButton32", persist = true, manualGroup = "debuffs" },
{ name = "TempEnchant1", persist = true, manualGroup = "weapon" },
{ name = "BuffButton0", persist = true, manualGroup = "buffs", cursorDrag = true },
{ name = "BuffButton32", persist = true, manualGroup = "debuffs", cursorDrag = true },
{ name = "TempEnchant1", persist = true, manualGroup = "weapon", cursorDrag = true },
}
local function Resolve(target)
@@ -131,6 +131,65 @@ module.enable = function(self)
moveFrame:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", pos[1], pos[2])
end
-- Aura buttons are layout-owned frames on some Vanilla/Turtle UI builds.
-- Calling StartMoving() on them can fail with "Frame ... is not movable or
-- resizable" even after SetMovable(true). For those frames, follow the cursor
-- ourselves and only change their anchor; regular unit frames still use the
-- native StartMoving path.
local cursorDrag = CreateFrame("Frame")
cursorDrag:Hide()
local function CursorPosition()
local x, y = GetCursorPosition()
local scale = UIParent:GetEffectiveScale()
if not scale or scale <= 0 then scale = 1 end
return x / scale, y / scale
end
local function StartCursorDrag(state, target, moveFrame)
local left = moveFrame:GetLeft()
local top = moveFrame:GetTop()
if not left or not top then return false end
local x, y = CursorPosition()
state.cursorStartX = x
state.cursorStartY = y
state.frameStartLeft = left
state.frameStartTop = top
cursorDrag.state = state
cursorDrag.target = target
cursorDrag.moveFrame = moveFrame
cursorDrag:Show()
return true
end
local function StopCursorDrag(state)
if cursorDrag.state ~= state then return end
cursorDrag:Hide()
cursorDrag.state = nil
cursorDrag.target = nil
cursorDrag.moveFrame = nil
end
cursorDrag:SetScript("OnUpdate", function()
local state = this.state
local target = this.target
local moveFrame = this.moveFrame
if not state or not target or not moveFrame then
this:Hide()
return
end
local x, y = CursorPosition()
local left = state.frameStartLeft + (x - state.cursorStartX)
local top = state.frameStartTop + (y - state.cursorStartY)
moveFrame:ClearAllPoints()
moveFrame:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", left, top)
end)
local function CreateGrid()
if grid then return grid end
@@ -217,7 +276,11 @@ module.enable = function(self)
moveFrame:SetClampedToScreen(true)
end
moveFrame:SetMovable(true)
-- Aura buttons can reject StartMoving() on some clients. They only need
-- mouse/drag scripts because cursorDrag handles their position directly.
if not target.cursorDrag then
moveFrame:SetMovable(true)
end
handle:EnableMouse(true)
handle:RegisterForDrag("LeftButton")
@@ -230,11 +293,22 @@ module.enable = function(self)
moveFrame:SetUserPlaced(true)
end
moveFrame:StartMoving()
if target.cursorDrag then
if not StartCursorDrag(state, target, moveFrame) then
state.dragged = false
SetDragging(target, false)
end
else
moveFrame:StartMoving()
end
end)
handle:SetScript("OnDragStop", function()
moveFrame:StopMovingOrSizing()
if target.cursorDrag then
StopCursorDrag(state)
else
moveFrame:StopMovingOrSizing()
end
if state.dragged then
SavePosition(target, moveFrame)
@@ -252,14 +326,18 @@ module.enable = function(self)
local moveFrame = state.moveFrame
if moveFrame then
moveFrame:StopMovingOrSizing()
if target.cursorDrag then
StopCursorDrag(state)
else
moveFrame:StopMovingOrSizing()
end
SetDragging(target, false)
if state.dragged then
SavePosition(target, moveFrame)
end
if state.movable ~= nil then
if not target.cursorDrag and state.movable ~= nil then
moveFrame:SetMovable(state.movable)
end
+6 -155
View File
@@ -26,6 +26,8 @@ module.enable = function(self)
local eliteTexture = addonpath .. "\\img\\UI-TargetingFrame-Elite"
local rareTexture = addonpath .. "\\img\\UI-TargetingFrame-Rare"
-- Big Health is presentation-only. Numeric health/power values are owned by
-- the independent Real Health Numbers module.
PlayerFrameTexture:SetTexture(normalTexture)
PlayerFrameHealthBar:SetPoint("TOPLEFT", 106, -22)
PlayerFrameHealthBar:SetHeight(30)
@@ -36,8 +38,6 @@ module.enable = function(self)
TargetFrameHealthBar:SetPoint("TOPRIGHT", -106, -22)
TargetFrameHealthBar:SetHeight(30)
-- Dark mode is applied after the world has loaded. Keep this event separate
-- from the one-frame deferred setup so the event cannot be unregistered early.
local world = CreateFrame("Frame")
world:RegisterEvent("PLAYER_ENTERING_WORLD")
world:SetScript("OnEvent", function()
@@ -47,7 +47,7 @@ module.enable = function(self)
end)
-- Delay hook installation by one frame so all enabled unit-frame modules have
-- finished their setup first. This keeps hooks attached to the final handlers.
-- finished their setup first. This keeps hooks attached to final handlers.
local deferred = CreateFrame("Frame")
deferred:SetScript("OnUpdate", function()
this:SetScript("OnUpdate", nil)
@@ -55,7 +55,9 @@ module.enable = function(self)
local function UpdateTargetClassificationTexture()
local classification = UnitClassification("target")
if classification == "worldboss" or classification == "rareelite" or classification == "elite" then
if classification == "worldboss"
or classification == "rareelite"
or classification == "elite" then
TargetFrameTexture:SetTexture(eliteTexture)
elseif classification == "rare" then
TargetFrameTexture:SetTexture(rareTexture)
@@ -64,156 +66,8 @@ module.enable = function(self)
end
end
-- Let Blizzard and other addons finish classification handling, then apply
-- the matching Big Health texture instead of replacing the global function.
hooksecurefunc("TargetFrame_CheckClassification", UpdateTargetClassificationTexture)
-- Big Health owns the compact value display while enabled. Keep Blizzard /
-- Turtle status texts alive for compatibility, but make them invisible so
-- their current/max or percent formats cannot overlap our centered values.
local nativeTexts = {
PlayerFrameHealthBar.TextString,
PlayerFrameManaBar.TextString,
TargetFrameHealthBar.TextString,
TargetFrameManaBar.TextString,
_G.PlayerFrameAlternatePowerBarText,
_G.TargetHPText,
_G.TargetHPPercText,
}
for _, text in pairs(nativeTexts) do
if text then text:SetAlpha(0) end
end
if not self.valueTexts then
self.valueTexts = {}
local function CreateValueText(parent, anchor, y)
local text = parent:CreateFontString(nil, "OVERLAY", "GameFontWhite")
-- Match the compact unit-frame number style used by the former
-- health-numbers module: Friz Quadrata, 10px, outlined.
text:SetFont(STANDARD_TEXT_FONT, 10, "OUTLINE")
text:SetHeight(32)
text:ClearAllPoints()
text:SetPoint("CENTER", anchor, "CENTER", 0, y or 0)
text:SetJustifyH("CENTER")
text:SetTextColor(1, 1, 1, 1)
return text
end
self.valueTexts.playerHealth = CreateValueText(PlayerFrameHealthBar, PlayerFrameHealthBar, -7)
self.valueTexts.playerMana = CreateValueText(PlayerFrameManaBar, PlayerFrameManaBar, 0)
self.valueTexts.targetHealth = CreateValueText(TargetFrameHealthBar, TargetFrameHealthBar, -7)
self.valueTexts.targetMana = CreateValueText(TargetFrameManaBar, TargetFrameManaBar, 0)
if _G.PlayerFrameAlternatePowerBar then
self.valueTexts.playerAlternatePower = CreateValueText(
_G.PlayerFrameAlternatePowerBar,
_G.PlayerFrameAlternatePowerBar,
0
)
end
end
local function UpdateValueText(text, unit, power)
if unit == "target" and not UnitExists("target") then
text:Hide()
return
end
local value
local max
if power == "health" then
value = UnitHealth(unit)
max = UnitHealthMax(unit)
else
value = UnitMana(unit)
max = UnitManaMax(unit)
end
if not max or max <= 0 then
text:Hide()
return
end
text:SetText(tostring(value or 0))
text:Show()
end
local function UpdateUnitValues(unit)
if unit == "player" then
UpdateValueText(self.valueTexts.playerHealth, "player", "health")
UpdateValueText(self.valueTexts.playerMana, "player", "power")
elseif unit == "target" then
UpdateValueText(self.valueTexts.targetHealth, "target", "health")
UpdateValueText(self.valueTexts.targetMana, "target", "power")
end
end
if not self.valueEvents then
self.valueEvents = CreateFrame("Frame")
self.valueEvents:RegisterEvent("PLAYER_ENTERING_WORLD")
self.valueEvents:RegisterEvent("PLAYER_TARGET_CHANGED")
self.valueEvents:RegisterEvent("UNIT_HEALTH")
self.valueEvents:RegisterEvent("UNIT_MAXHEALTH")
self.valueEvents:RegisterEvent("UNIT_MANA")
self.valueEvents:RegisterEvent("UNIT_MAXMANA")
self.valueEvents:RegisterEvent("UNIT_RAGE")
self.valueEvents:RegisterEvent("UNIT_MAXRAGE")
self.valueEvents:RegisterEvent("UNIT_ENERGY")
self.valueEvents:RegisterEvent("UNIT_MAXENERGY")
self.valueEvents:RegisterEvent("UNIT_FOCUS")
self.valueEvents:RegisterEvent("UNIT_MAXFOCUS")
self.valueEvents:RegisterEvent("UNIT_HAPPINESS")
self.valueEvents:RegisterEvent("UNIT_MAXHAPPINESS")
self.valueEvents:RegisterEvent("UNIT_DISPLAYPOWER")
self.valueEvents:SetScript("OnEvent", function()
if event == "PLAYER_ENTERING_WORLD" or event == "PLAYER_TARGET_CHANGED" then
UpdateUnitValues("player")
UpdateUnitValues("target")
elseif arg1 == "player" or arg1 == "target" then
UpdateUnitValues(arg1)
end
end)
end
UpdateUnitValues("player")
UpdateUnitValues("target")
-- Turtle WoW exposes a native alternate power bar for druids while
-- shapeshifted. It stores caster mana separately from the active rage/energy
-- value. Keep the native bar and only replace its text presentation.
local alternatePowerBar = _G.PlayerFrameAlternatePowerBar
if alternatePowerBar and self.valueTexts.playerAlternatePower then
local function UpdateAlternatePowerValue()
local text = self.valueTexts.playerAlternatePower
if not alternatePowerBar:IsShown() then
text:Hide()
return
end
local value = alternatePowerBar:GetValue() or 0
local _, max = alternatePowerBar:GetMinMaxValues()
if not max or max <= 0 then
text:Hide()
return
end
text:SetText(tostring(math.floor(value + 0.5)))
text:Show()
end
-- Turtle updates this bar from UnitFrameManaBar_Update using the second
-- UnitMana / UnitManaMax return values. Refresh after that native write.
hooksecurefunc(alternatePowerBar, "SetValue", UpdateAlternatePowerValue)
hooksecurefunc(alternatePowerBar, "Show", UpdateAlternatePowerValue)
UpdateAlternatePowerValue()
end
local playerSetStatusBarColor = PlayerFrameHealthBar.SetStatusBarColor
local targetSetStatusBarColor = TargetFrameHealthBar.SetStatusBarColor
@@ -249,11 +103,8 @@ module.enable = function(self)
ApplyTargetHealthColor()
end
-- Reapply the target health color after faction/class-color updates.
hooksecurefunc("TargetFrame_CheckFaction", ApplyTargetHealthColor)
-- Refresh once because the target frame may already have been initialized
-- before this deferred setup ran.
UpdateTargetClassificationTexture()
TargetFrame_CheckFaction()
end)