From ccfeb0810f1fd12c4f6886e4cbe82f2917619316 Mon Sep 17 00:00:00 2001 From: Dusk-92 Date: Mon, 31 Aug 2026 11:10:03 +0200 Subject: [PATCH] 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(). --- README.md | 5 +- ShaguTweaks.toc | 2 + libs/libhealth.lua | 206 +++++++++++++++++++++++++ mods/health-numbers.lua | 279 ++++++++++++++++++++++++++++++++++ mods/move-unitframes.lua | 94 +++++++++++- mods/unitframes-bighealth.lua | 161 +------------------- 6 files changed, 583 insertions(+), 164 deletions(-) create mode 100644 libs/libhealth.lua create mode 100644 mods/health-numbers.lua diff --git a/README.md b/README.md index e2ef526..441c686 100644 --- a/README.md +++ b/README.md @@ -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** diff --git a/ShaguTweaks.toc b/ShaguTweaks.toc index 2c5febc..f466470 100644 --- a/ShaguTweaks.toc +++ b/ShaguTweaks.toc @@ -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 diff --git a/libs/libhealth.lua b/libs/libhealth.lua new file mode 100644 index 0000000..16b40ad --- /dev/null +++ b/libs/libhealth.lua @@ -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 diff --git a/mods/health-numbers.lua b/mods/health-numbers.lua new file mode 100644 index 0000000..e84f3fd --- /dev/null +++ b/mods/health-numbers.lua @@ -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 diff --git a/mods/move-unitframes.lua b/mods/move-unitframes.lua index b5d2032..4025b2e 100644 --- a/mods/move-unitframes.lua +++ b/mods/move-unitframes.lua @@ -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 diff --git a/mods/unitframes-bighealth.lua b/mods/unitframes-bighealth.lua index 95a8d6a..147ed4b 100644 --- a/mods/unitframes-bighealth.lua +++ b/mods/unitframes-bighealth.lua @@ -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)