forked from wow/OmicronFrames
The aura based status colors can now be configured in frames.lua. This is important in TBC where some healers don't care about e.g. magic debuffs.
693 lines
20 KiB
Lua
693 lines
20 KiB
Lua
-- Copyright 2023 <omicron.me@protonmail.com>
|
|
--
|
|
-- This file is part of Omicron Frames
|
|
--
|
|
-- Omicron Frames is free software: you can redistribute it and/or modify it
|
|
-- under the terms of the GNU General Public License as published by the Free
|
|
-- Software Foundation, either version 3 of the License, or (at your option)
|
|
-- any later version.
|
|
--
|
|
-- Omicron Frames is distributed in the hope that it will be useful, but
|
|
-- WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
-- or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
-- more details.
|
|
--
|
|
-- You should have received a copy of the GNU General Public License along with
|
|
-- Omicron Frames. If not, see <https://www.gnu.org/licenses/>.
|
|
local omif = select(2, ...)
|
|
local types = omif.GetModule("types")
|
|
|
|
local NameFont = CreateFont("OicronFrames_NameFont")
|
|
NameFont:SetFont("Interface\\AddOns\\OmicronFrames\\media\\fonts\\roboto\\Roboto-Bold.ttf", 13, "")
|
|
NameFont:SetShadowColor(0, 0, 0)
|
|
NameFont:SetShadowOffset(1, -1)
|
|
|
|
---@class StatusBar
|
|
local StatusBar = types.StatusBar
|
|
|
|
---@class AuraList
|
|
local AuraList = types.AuraList
|
|
|
|
---@class SquareIndicator
|
|
local SquareIndicator = types.SquareIndicator
|
|
|
|
---@class IconIndicator
|
|
local IconIndicator = types.IconIndicator
|
|
|
|
---@class SquareIndicator
|
|
local BorderIndicator = types.BorderIndicator
|
|
|
|
---@class UnitFrame
|
|
local UnitFrame = types.CreateClass("UnitFrame")
|
|
types.UnitFrame = UnitFrame
|
|
|
|
local colors = {
|
|
hostile = { 0.5, 0.0, 0.0 },
|
|
neutral = { 0.7, 0.7, 0.0 },
|
|
healthy = { 0, 0.1, 0 },
|
|
high = { 0, 1.0, 0 },
|
|
mid = { 1.0, 1.0, 0 },
|
|
low = { 1.0, 0.0, 0 },
|
|
offline = { 0.7, 0.7, 0.7 },
|
|
dead = { 0.7, 0.7, 0.7 },
|
|
cyan = { 0.0, 0.8, 0.8 },
|
|
white = { 1.0, 1.0, 1.0 },
|
|
shield = { 0.85, 0.95, 1.0 },
|
|
absorb = { 0.5, 0.4, 0.75 },
|
|
}
|
|
|
|
-- This trucates _codepoints_ not graphemes. If combination codepoints are
|
|
-- contained in the string, it will not properly truncate and may return
|
|
-- incorrect graphemes
|
|
local function utf8_truncate(s, n)
|
|
local init = 0
|
|
for _ = 1, n do
|
|
local _, last = string.find(s, ".[\128-\191]*", init + 1)
|
|
if not last then
|
|
return s
|
|
end
|
|
init = last
|
|
end
|
|
return string.sub(s, 1, init)
|
|
end
|
|
|
|
---@param unit string
|
|
---@param config table
|
|
function UnitFrame:Init(unit, config)
|
|
local width = config.size.width
|
|
local height = config.size.height
|
|
|
|
self.unit = unit
|
|
self.hideInRaid = config.hideInRaid
|
|
|
|
self.rangeFriendly = config.range.friendly
|
|
self.rangeEnemy = config.range.enemy
|
|
self.rangeFade = config.range.fade
|
|
|
|
self.statusColors = config.statusColors
|
|
|
|
local secure = self:CreateSecureFrame(width, height)
|
|
secure:Hide()
|
|
|
|
self:SetMouseBinds(config.mouse)
|
|
|
|
local texture = "Interface\\Addons\\OmicronFrames\\media\\textures\\bar_subtle_diagonal"
|
|
local texture_top = "Interface\\Addons\\OmicronFrames\\media\\textures\\bar_subtle_diagonal_top"
|
|
local texture_bot = "Interface\\Addons\\OmicronFrames\\media\\textures\\bar_subtle_diagonal_bottom"
|
|
|
|
self.hp = StatusBar:new(self, width, height, 0, true, texture, texture)
|
|
|
|
self.power = StatusBar:new(self, width, 6, 2, false, texture, texture)
|
|
self.power:Hide()
|
|
|
|
self.absorb = StatusBar:new(self, width, height, 1, true, texture_top)
|
|
self.absorb:SetColor(unpack(colors.absorb))
|
|
self.absorb:SetFillStyle(Enum.StatusBarFillStyle.Reverse)
|
|
|
|
self.shield = StatusBar:new(self, width, height, 1, true, texture_bot)
|
|
self.shield:SetColor(unpack(colors.shield))
|
|
self.shield:SetRange(0, 1)
|
|
self.shield:SetValue(0.6)
|
|
self.shield:SetFillStyle(Enum.StatusBarFillStyle.Reverse)
|
|
|
|
self.auras = AuraList:new(self)
|
|
|
|
local overlays = CreateFrame("Frame", nil, secure)
|
|
overlays:SetFrameStrata("MEDIUM")
|
|
overlays:SetFrameLevel(100)
|
|
overlays:SetAllPoints(true)
|
|
overlays:Show()
|
|
self.overlays = overlays
|
|
self:CreateName()
|
|
|
|
self:CreateTriggers(config.triggers)
|
|
|
|
self:RegisterEvents()
|
|
self:Enable()
|
|
end
|
|
|
|
function UnitFrame:CreateTriggers(triggers)
|
|
for _, config in ipairs(triggers) do
|
|
local indicator = self:CreateIndicator(config.indicator)
|
|
---@type Trigger
|
|
local triggerType = types[config.kind]
|
|
local trigger = triggerType.CreateFromConfig(self, config)
|
|
trigger:SetTarget(indicator)
|
|
end
|
|
end
|
|
|
|
function UnitFrame:CreateIndicator(indicator)
|
|
local kind = indicator.kind
|
|
if kind == "SquareIndicator" then
|
|
return SquareIndicator:new(
|
|
self,
|
|
indicator.size,
|
|
indicator.point,
|
|
indicator.x,
|
|
indicator.y,
|
|
indicator.color,
|
|
indicator.showStacks,
|
|
indicator.fadeTime,
|
|
indicator.flashTime
|
|
)
|
|
elseif kind == "IconIndicator" then
|
|
return IconIndicator:new(
|
|
self,
|
|
indicator.size,
|
|
indicator.point,
|
|
indicator.x,
|
|
indicator.y,
|
|
indicator.showStacks,
|
|
indicator.fadeTime,
|
|
indicator.flashTime
|
|
)
|
|
elseif kind == "BorderIndicator" then
|
|
return BorderIndicator:new(self, indicator.thickness, indicator.color)
|
|
else
|
|
error(string.format("Invalid Indicator kind `%s` requested", indicator.kind))
|
|
end
|
|
end
|
|
|
|
function UnitFrame:StartRangeTicker()
|
|
if self.rangeTicker then
|
|
return
|
|
end
|
|
local delta = 0.45 + (fastrandom(0, 100) / 1000)
|
|
self.rangeTicker = C_Timer.NewTicker(delta, function()
|
|
self:UpdateRange()
|
|
end)
|
|
end
|
|
|
|
-- TODO: maybe string indicators need to be a thing
|
|
function UnitFrame:CreateName()
|
|
local name = self.overlays:CreateFontString(nil, "BACKGROUND")
|
|
self.name = name
|
|
|
|
name:SetFontObject(NameFont)
|
|
name:SetTextColor(0.8, 0.6, 0.1)
|
|
name:SetPoint("CENTER")
|
|
name:SetText("")
|
|
name:Show()
|
|
end
|
|
|
|
function UnitFrame:StopRangeTicker()
|
|
if self.rangeTicker then
|
|
self.rangeTicker:Cancel()
|
|
self.rangeTicker = nil
|
|
end
|
|
end
|
|
|
|
-- Returns
|
|
-- true if the unit is in range
|
|
-- false if the unit is not in range
|
|
-- nil if it could not be determined
|
|
function UnitFrame:IsInRange()
|
|
local unit = self.unit
|
|
local friendlySpell = self.rangeFriendly
|
|
local enemySpell = self.rangeEnemy
|
|
|
|
-- Prefer to use configured spells
|
|
if friendlySpell and UnitCanAssist("player", unit) then
|
|
return C_Spell.IsSpellInRange(friendlySpell, unit) == true
|
|
elseif enemySpell and UnitCanAttack("player", unit) then
|
|
return C_Spell.IsSpellInRange(enemySpell, unit) == true
|
|
end
|
|
|
|
-- Fall back to raid/party only range check
|
|
local inRange, checkedRange = UnitInRange(unit)
|
|
if checkedRange then
|
|
return inRange
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
function UnitFrame:UpdateRange()
|
|
local unit = self.unit
|
|
|
|
if UnitIsDeadOrGhost(unit) then
|
|
self.secureFrame:SetAlpha(1.0)
|
|
return
|
|
end
|
|
|
|
local inRange = self:IsInRange()
|
|
if inRange or inRange == nil then
|
|
self.secureFrame:SetAlpha(1.0)
|
|
else
|
|
self.secureFrame:SetAlpha(self.rangeFade)
|
|
end
|
|
end
|
|
|
|
function UnitFrame:SetSpellAction(button, spell)
|
|
local secure = self.secureFrame
|
|
local attributeName = button:gsub("type", "spell")
|
|
|
|
secure:SetAttribute(button, "spell")
|
|
secure:SetAttribute(attributeName, spell)
|
|
end
|
|
|
|
function UnitFrame:SetMacroAction(button, macro)
|
|
local secure = self.secureFrame
|
|
local attributeName = button:gsub("type", "macrotext")
|
|
|
|
secure:SetAttribute(button, "macro")
|
|
secure:SetAttribute(attributeName, macro:gsub("@UNIT", "@" .. self.unit))
|
|
end
|
|
|
|
local function ModifiersToPrefix(mods)
|
|
if not mods then
|
|
return ""
|
|
end
|
|
|
|
local result = {}
|
|
if mods.ctrl then
|
|
table.insert(result, "CTRL-")
|
|
end
|
|
if mods.shift then
|
|
table.insert(result, "SHIFT-")
|
|
end
|
|
if mods.alt then
|
|
table.insert(result, "ALT-")
|
|
end
|
|
return table.concat(result, "")
|
|
end
|
|
|
|
function UnitFrame:PrepareWheelBinds(bindings)
|
|
-- By default you can't use the SecureUnitButtonTemplate to handle scroll
|
|
-- wheel "clicks". We solve this by using SecureHandler*Template to bind
|
|
-- scroll wheel to the current unit frame button when the mouse enters and
|
|
-- to unbind it when the mouse leaves. When the keybind is triggered the
|
|
-- button will receive a click with a custom button name
|
|
|
|
-- Build a table with all lines of the scroll wheel + modifier combinations
|
|
-- we want to bind, then concat it with newlines into a full script
|
|
local unit = self.unit
|
|
local bindScript = {
|
|
[[self:ClearBindings()]],
|
|
}
|
|
for _, bind in ipairs(bindings) do
|
|
if bind.button == "wheel-up" or bind.button == "wheel-down" then
|
|
local prefix = ModifiersToPrefix(bind.mods)
|
|
local button = bind.button == "wheel-up" and "MOUSEWHEELUP" or "MOUSEWHEELDOWN"
|
|
table.insert(
|
|
bindScript,
|
|
string.format(
|
|
[[self:SetBindingClick(false, "%s%s", "OmicronSecureFrame%s", "%s%s")]],
|
|
prefix,
|
|
button,
|
|
unit,
|
|
prefix,
|
|
bind.button
|
|
)
|
|
)
|
|
end
|
|
end
|
|
|
|
local secure = self.secureFrame
|
|
local bindScript = table.concat(bindScript, "\n")
|
|
|
|
local removeBindScript = [[
|
|
self:ClearBindings()
|
|
]]
|
|
secure:SetAttribute("_onenter", bindScript)
|
|
secure:SetAttribute("_onleave", removeBindScript)
|
|
secure:SetAttribute("_onshow", removeBindScript)
|
|
secure:SetAttribute("_onhide", removeBindScript)
|
|
end
|
|
|
|
function UnitFrame:SetMouseBinds(binds)
|
|
local secure = self.secureFrame
|
|
local unit = self.unit
|
|
|
|
secure:RegisterForClicks("AnyDown")
|
|
self:PrepareWheelBinds(binds)
|
|
|
|
for _, bind in ipairs(binds) do
|
|
local prefix = ModifiersToPrefix(bind.mods)
|
|
local button
|
|
-- This is kind of a mess but there are two options for attribute names
|
|
-- in the normal click case (mouse1, mouse2, etc) we have to put the
|
|
-- modifiers before type so it would become SHIFT-type1 for mouse1 with
|
|
-- shift
|
|
--
|
|
-- Since scroll wheel is not supported here and we manually create
|
|
-- click events with global scroll wheel binds while we are moused over
|
|
-- the unit frame they receive the modifiers after the type so it would become
|
|
-- type-SHIFT-wheel-up for wheel-up with shift
|
|
if bind.button:find("mouse", 1, true) == 1 then
|
|
button = prefix .. bind.button:gsub("^mouse", "type")
|
|
elseif bind.button:find("wheel", 1, true) == 1 then
|
|
button = "type-" .. prefix .. bind.button
|
|
else
|
|
error("Keybinds were invalid")
|
|
end
|
|
if bind.kind == "macro" then
|
|
self:SetMacroAction(button, bind.data)
|
|
elseif bind.kind == "spell" then
|
|
self:SetSpellAction(button, bind.data)
|
|
else
|
|
secure:SetAttribute(button, bind.kind)
|
|
end
|
|
end
|
|
end
|
|
|
|
function UnitFrame:CreateSecureFrame(width, height)
|
|
local name = "OmicronSecureFrame" .. self.unit
|
|
local templates = table.concat({
|
|
"SecureUnitButtonTemplate",
|
|
"SecureHandlerEnterLeaveTemplate",
|
|
"SecureHandlerShowHideTemplate",
|
|
}, ",")
|
|
local secure = CreateFrame("Button", name, UIParent, templates)
|
|
self.secureFrame = secure
|
|
secure:SetFrameStrata("MEDIUM")
|
|
secure:SetFrameLevel(0)
|
|
secure:SetAttribute("unit", self.unit)
|
|
secure:SetSize(width, height)
|
|
|
|
return secure
|
|
end
|
|
|
|
function UnitFrame:Enable()
|
|
local secure = self.secureFrame
|
|
if self.hideInRaid then
|
|
local condition = "[@UNIT,exists,nogroup:raid] show; hide"
|
|
condition = condition:gsub("@UNIT", "@" .. self.unit)
|
|
RegisterAttributeDriver(secure, "state-visibility", condition)
|
|
elseif self.unit ~= "player" then
|
|
RegisterUnitWatch(self.secureFrame, false)
|
|
else
|
|
self.secureFrame:Show()
|
|
end
|
|
if UnitExists(self.unit) then
|
|
self:UpdateAll(self:HasUnitChanged())
|
|
end
|
|
end
|
|
|
|
function UnitFrame:Disable()
|
|
if self.hideInRaid then
|
|
UnregisterAttributeDriver(self.secureFrame, "state-visibility")
|
|
elseif self.unit ~= "player" then
|
|
UnregisterUnitWatch(self.secureFrame, false)
|
|
end
|
|
self.secureFrame:Hide()
|
|
end
|
|
|
|
-- returns true if this unit is a target unit
|
|
function UnitFrame:IsTargetFrame()
|
|
return self.unit:match(".*target") == self.unit
|
|
end
|
|
|
|
-- returns the target owner
|
|
function UnitFrame:GetTargetOwner()
|
|
if self.unit == "target" then
|
|
return "player"
|
|
else
|
|
return self.unit:sub(1, -7)
|
|
end
|
|
end
|
|
|
|
-- Set position from the center of the UIParent
|
|
function UnitFrame:SetPosition(x, y)
|
|
self.secureFrame:SetPoint("CENTER", UIParent, x, y)
|
|
end
|
|
|
|
function UnitFrame:RegisterEvents()
|
|
local unit = self.unit
|
|
local secure = self.secureFrame
|
|
|
|
secure:SetScript("OnShow", function(frame, event, ...)
|
|
self:OnShow()
|
|
end)
|
|
|
|
secure:SetScript("OnHide", function(frame, event, ...)
|
|
self:OnHide()
|
|
end)
|
|
|
|
secure:SetScript("OnEvent", function(frame, event, ...)
|
|
self[event](self, ...)
|
|
end)
|
|
|
|
if self:IsTargetFrame() then
|
|
local owner = self:GetTargetOwner()
|
|
if owner == "player" then
|
|
secure:RegisterEvent("PLAYER_TARGET_CHANGED")
|
|
else
|
|
secure:RegisterUnitEvent("UNIT_TARGET", self.parent:GetTargetOwner())
|
|
end
|
|
end
|
|
|
|
secure:RegisterEvent("GROUP_ROSTER_UPDATE")
|
|
secure:RegisterUnitEvent("UNIT_AURA", unit)
|
|
secure:RegisterUnitEvent("UNIT_HEALTH", unit)
|
|
secure:RegisterUnitEvent("UNIT_MAXHEALTH", unit)
|
|
secure:RegisterUnitEvent("UNIT_NAME_UPDATE", unit)
|
|
secure:RegisterUnitEvent("UNIT_ABSORB_AMOUNT_CHANGED", unit)
|
|
secure:RegisterUnitEvent("UNIT_HEAL_ABSORB_AMOUNT_CHANGED", unit)
|
|
end
|
|
|
|
-- returns whether or not the unit guid has changed since the last call to this
|
|
-- function. Always returns false if the current unit does not exist.
|
|
function UnitFrame:HasUnitChanged()
|
|
local unit = self.unit
|
|
if not UnitExists(unit) then
|
|
self.guid = nil
|
|
return false
|
|
end
|
|
|
|
local guid = UnitGUID(unit)
|
|
if self.guid == guid then
|
|
return false
|
|
else
|
|
self.guid = guid
|
|
return true
|
|
end
|
|
end
|
|
|
|
function UnitFrame:UNIT_HEAL_ABSORB_AMOUNT_CHANGED()
|
|
if self:HasUnitChanged() then
|
|
self:UpdateAll(true)
|
|
return
|
|
end
|
|
self:UpdateAbsorb()
|
|
end
|
|
|
|
function UnitFrame:UNIT_ABSORB_AMOUNT_CHANGED()
|
|
if self:HasUnitChanged() then
|
|
self:UpdateAll(true)
|
|
return
|
|
end
|
|
self:UpdateShield()
|
|
end
|
|
|
|
function UnitFrame:GROUP_ROSTER_UPDATE()
|
|
if self:HasUnitChanged() then
|
|
self:UpdateAll(true)
|
|
return
|
|
end
|
|
end
|
|
|
|
function UnitFrame:UNIT_AURA(unit, info)
|
|
if self:HasUnitChanged() then
|
|
self:UpdateAll(true)
|
|
return
|
|
end
|
|
if self.auras:Update(info) then
|
|
self:UpdateHealthColor()
|
|
end
|
|
end
|
|
|
|
function UnitFrame:UNIT_HEALTH()
|
|
if self:HasUnitChanged() then
|
|
self:UpdateAll(true)
|
|
return
|
|
end
|
|
self:UpdateHealth()
|
|
self:UpdateHealthColor()
|
|
end
|
|
|
|
function UnitFrame:UNIT_MAXHEALTH()
|
|
if self:HasUnitChanged() then
|
|
self:UpdateAll(true)
|
|
return
|
|
end
|
|
self:UpdateHealth()
|
|
self:UpdateHealthColor()
|
|
end
|
|
|
|
function UnitFrame:UNIT_TARGET()
|
|
self:UpdateAll(self:HasUnitChanged())
|
|
end
|
|
|
|
function UnitFrame:PLAYER_TARGET_CHANGED()
|
|
self:UpdateAll(self:HasUnitChanged())
|
|
end
|
|
|
|
function UnitFrame:UNIT_NAME_UPDATE()
|
|
self:UpdateName()
|
|
end
|
|
|
|
function UnitFrame:ROLE_CHANGED_INFORM(name, changer, old, new)
|
|
if UnitName(self.unit) == name then
|
|
self:UpdateRole(new)
|
|
end
|
|
end
|
|
|
|
function UnitFrame:UNIT_POWER_UPDATE()
|
|
self:UpdatePower()
|
|
end
|
|
|
|
function UnitFrame:UpdateRole(role)
|
|
if role == nil then
|
|
role = UnitGroupRolesAssigned(self.unit)
|
|
end
|
|
if role == "HEALER" then
|
|
self:EnablePower(Enum.PowerType.Mana)
|
|
elseif role == "TANK" and select(3, UnitClass(self.unit)) == 6 then
|
|
self:EnablePower(Enum.PowerType.RunicPower)
|
|
else
|
|
self:DisablePower()
|
|
end
|
|
end
|
|
|
|
function UnitFrame:UpdatePower()
|
|
local power = UnitPower(self.unit, self.powerType)
|
|
local max = UnitPowerMax(self.unit, self.powerType)
|
|
self.power:SetRange(0, max)
|
|
self.power:SetValue(power)
|
|
end
|
|
|
|
function UnitFrame:EnablePower(type)
|
|
self.powerType = type
|
|
local color = PowerBarColor[type]
|
|
self.power:SetColor(color.r, color.g, color.b)
|
|
self.power:Show()
|
|
self.secureFrame:RegisterUnitEvent("UNIT_POWER_UPDATE", self.unit)
|
|
self:UpdatePower()
|
|
end
|
|
|
|
function UnitFrame:DisablePower()
|
|
self.powerType = nil
|
|
self.secureFrame:UnregisterEvent("UNIT_POWER_UPDATE")
|
|
self.power:Hide()
|
|
end
|
|
|
|
function UnitFrame:OnShow()
|
|
self.secureFrame:RegisterEvent("ROLE_CHANGED_INFORM")
|
|
self:StartRangeTicker()
|
|
self:UpdateAll(self:HasUnitChanged())
|
|
end
|
|
|
|
function UnitFrame:OnHide()
|
|
self.secureFrame:UnregisterEvent("ROLE_CHANGED_INFORM")
|
|
self.guid = nil
|
|
self:StopRangeTicker()
|
|
end
|
|
|
|
function UnitFrame:UpdateAll(unitChanged)
|
|
if not UnitExists(self.unit) then
|
|
return
|
|
end
|
|
if unitChanged then
|
|
self.auras:Reset()
|
|
end
|
|
self:UpdateHealth()
|
|
self:UpdateHealthColor()
|
|
self:UpdateAbsorb()
|
|
self:UpdateShield()
|
|
self:UpdateRange()
|
|
self:UpdateName()
|
|
self:UpdateRole() -- Also calls UpdatePower if power is visible
|
|
end
|
|
|
|
function UnitFrame:UpdateHealth()
|
|
local unit = self.unit
|
|
local val = UnitHealth(unit)
|
|
local max = UnitHealthMax(unit)
|
|
|
|
self.hp:SetRange(0, max)
|
|
self.hp:SetValue(val)
|
|
end
|
|
|
|
function UnitFrame:UpdateShield()
|
|
local unit = self.unit
|
|
local val = UnitGetTotalAbsorbs(unit)
|
|
local max = UnitHealthMax(unit)
|
|
if val <= 0 then
|
|
self.shield:Hide()
|
|
else
|
|
self.shield:SetRange(0, max)
|
|
self.shield:SetValue(val)
|
|
self.shield:Show()
|
|
end
|
|
end
|
|
|
|
function UnitFrame:UpdateAbsorb()
|
|
local unit = self.unit
|
|
local val = UnitGetTotalHealAbsorbs(unit)
|
|
local max = UnitHealthMax(unit)
|
|
if val <= 0 then
|
|
self.absorb:Hide()
|
|
else
|
|
self.absorb:SetRange(0, max)
|
|
self.absorb:SetValue(val)
|
|
self.absorb:Show()
|
|
end
|
|
end
|
|
|
|
function UnitFrame:UpdateHealthColor()
|
|
local unit = self.unit
|
|
local val = UnitHealth(unit)
|
|
local max = UnitHealthMax(unit)
|
|
|
|
if max == 0 then
|
|
return
|
|
end
|
|
local pct = val / max
|
|
local isFriend = UnitIsFriend("player", unit)
|
|
|
|
if not isFriend then
|
|
self.hp:SetColor(unpack(colors.hostile))
|
|
return
|
|
end
|
|
|
|
local statusColor = nil
|
|
for _, entry in ipairs(self.statusColors) do
|
|
if self.auras.statusCount[entry.status] then
|
|
statusColor = entry.color
|
|
break
|
|
end
|
|
end
|
|
|
|
if statusColor then
|
|
self.hp:SetColor(unpack(statusColor))
|
|
elseif UnitIsDead(unit) then
|
|
self.hp:SetColor(unpack(colors.dead))
|
|
elseif pct >= 0.90 then
|
|
self.hp:SetColor(unpack(colors.healthy))
|
|
elseif pct >= 0.75 then
|
|
local progress = (pct - 0.75) / (0.90 - 0.75)
|
|
self.hp:SetInterpolatedColor(colors.mid, colors.high, progress)
|
|
else
|
|
local progress = pct / 0.75
|
|
-- Quadratic progress so we get to the dangerous colors faster as hp
|
|
-- goes lower
|
|
progress = progress * progress
|
|
self.hp:SetInterpolatedColor(colors.low, colors.mid, progress)
|
|
end
|
|
end
|
|
|
|
function UnitFrame:UpdateName()
|
|
if UnitIsPlayer(self.unit) then
|
|
local _, class = UnitClass(self.unit)
|
|
-- TODO: UnitClass can return Unknown and the color can be nil. Having
|
|
-- the unit exist but not fully loaded is possibly a state we want to
|
|
-- handle more generally
|
|
local color = RAID_CLASS_COLORS[class] or { r = 1, g = 1, b = 1 }
|
|
self.name:SetTextColor(color.r, color.g, color.b)
|
|
else
|
|
self.name:SetTextColor(1, 1, 1)
|
|
end
|
|
self.name:SetText(utf8_truncate((UnitName(self.unit)), 5))
|
|
end
|