local PowerBarColor = PowerBarColor
local RAID_CLASS_COLORS = RAID_CLASS_COLORS
local UnitClass = UnitClass
local UnitExists = UnitExists
local UnitHealth = UnitHealth
local UnitHealthMax = UnitHealthMax
local UnitIsConnected = UnitIsConnected
local UnitIsCorpse = UnitIsCorpse
local UnitIsDead = UnitIsDead
local UnitIsGhost = UnitIsGhost
local UnitPower = UnitPower
local UnitPowerMax = UnitPowerMax
local UnitPowerType = UnitPowerType
local Chorus = Chorus
local function getUnit(f)
local p = f:GetParent()
local u = f.unit or f:GetAttribute('unit')
if not u and p then
u = p.unit or p:GetAttribute('unit')
end
return u
end
local function validateProgressFrame(self)
assert(self ~= nil)
local artwork = self.artwork
assert(artwork ~= nil)
local label1 = self.label1
assert(label1 ~= nil)
local label2 = self.label2
assert(label2 ~= nil)
local label3 = self.label3
assert(label3 ~= nil)
local ratio = self:GetValue()
assert(ratio ~= nil)
assert('number' == type(ratio))
assert(ratio >= 0.0)
assert(ratio <= 1.0)
local strategy = self.strategy
if strategy then
assert('string' == type(strategy))
strategy = strtrim(strategy)
assert(string.len(strategy) >= 1)
assert(string.len(strategy) <= 8192)
local unitDesignation = getUnit(self) or 'none'
assert('string' == type(unitDesignation))
unitDesignation = string.lower(strtrim(unitDesignation))
assert(string.len(unitDesignation) >= 1)
assert(string.len(unitDesignation) <= 256)
end
end
local function getRatio(a, b)
assert(a ~= nil)
assert('number' == type(a))
assert(a >= 0)
assert(b ~= nil)
assert('number' == type(b))
assert(b > 0)
assert(a <= b)
local x = math.min(math.abs(a), math.abs(b))
local y = math.max(math.abs(a), math.abs(b))
y = math.max(y, 1)
assert(y > 0)
local ratio = x / y
ratio = math.min(math.max(0, math.abs(ratio)), 1)
return ratio
end
local function applySize(self)
assert(self ~= nil)
local label1 = self.label1
assert(label1 ~= nil)
local label2 = self.label2
assert(label2 ~= nil)
local label3 = self.label3
assert(label3 ~= nil)
if self:GetHeight() >= 36 then
label1:Show()
label2:Show()
label3:Show()
elseif self:GetHeight() >= 12 then
label1:Show()
label2:Show()
label3:Hide()
else
label1:Hide()
label2:Hide()
label3:Hide()
end
end
local function applyRatio(self, a, b)
assert(self ~= nil)
local ratio = 0
--[[ Strict sanitization to work around WoW API quirks. ]]--
if a and b then
assert('number' == type(a))
assert('number' == type(b))
local x = math.min(a, b)
local y = math.max(a, b)
if x <= y then
ratio = getRatio(x, y)
end
end
self:SetValue(ratio)
end
local function formatQuantity(quantity)
assert(quantity ~= nil)
assert('number' == type(quantity))
local t
if math.abs(quantity) < 1000 then
t = string.format('%d',quantity)
elseif math.abs(quantity) < 1000000 then
t = string.format('%.2f K', quantity / 1000)
else
t = string.format('%.2f M', quantity / 1000000)
end
return t
end
local function applyOverlay(self, a, b)
assert(self ~= nil)
assert(a ~= nil)
assert('number' == type(a))
assert(a >= 0)
assert(b ~= nil)
assert('number' == type(b))
--[[ Work around quirks of the native API. ]]--
b = math.max(b, 1)
assert(b > 0)
assert(a <= b)
local ratio = getRatio(a, b)
local label1 = self.label1 or _G[self:GetName() .. 'Text1']
assert(label1 ~= nil)
local t = formatQuantity(a)
label1:SetText(t)
local label2 = self.label2 or _G[self:GetName() .. 'Text2']
assert(label2 ~= nil)
local e = string.format('%.0f%%', ratio * 100)
label2:SetText(e)
--[[ ShadowedUnitFrames recommend this approach for adjusting font size. ]]--
if t ~= nil and string.len(t) >= 1 then
local fontSize = 12
self:SetScale(label1:GetStringHeight() / fontSize)
end
end
local function applyRatioHealth(self, unitDesignation)
assert(self ~= nil)
assert(unitDesignation ~= nil)
local a
local b
if not UnitExists(unitDesignation) or
not UnitIsConnected(unitDesignation) or
UnitIsDead(unitDesignation) or
UnitIsCorpse(unitDesignation) or
UnitIsGhost(unitDesignation) then
a = 0
b = 1
else
a = UnitHealth(unitDesignation) or 0
b = UnitHealthMax(unitDesignation) or 1
end
applyRatio(self, a, b)
end
local function applyOverlayHealthDeficit(self, a, b)
assert(self ~= nil)
local label3 = self.label3 or _G[self:GetName() .. 'Text3']
assert(label3 ~= nil)
if a and b and a >= 1 and b >= 1 and a < b then
local c = a - b
local t = formatQuantity(c)
label3:SetText(t)
else
label3:SetText(nil)
end
end
local function applyOverlayHealth(self, unitDesignation)
assert(self ~= nil)
assert(unitDesignation ~= nil)
local label1 = self.label1
assert(label1 ~= nil)
local label2 = self.label2
assert(label2 ~= nil)
local label3 = self.label3
assert(label3 ~= nil)
local u = unitDesignation
local a = UnitHealth(u)
local b = UnitHealthMax(u)
if a and b and a <= b and b >= 1 and not UnitIsDead(u) and not UnitIsCorpse(u) and not UnitIsGhost(u) then
applyOverlay(self, a, b)
applyOverlayHealthDeficit(self, a, b)
else
label1:SetText(nil)
label2:SetText(nil)
label3:SetText(nil)
--[[ TODO Separate health status bar and unit statues. ]]--
if UnitIsCorpse(unitDesignation) then
label1:SetText('(Corpse)')
elseif UnitIsGhost(unitDesignation) then
label1:SetText('(Ghost)')
elseif UnitIsDead(unitDesignation) then
label1:SetText('(Dead)')
end
end
end
local function applyRatioPower(self, unitDesignation, powerTypeEnum)
assert(self ~= nil)
assert(unitDesignation ~= nil)
assert(powerTypeEnum ~= nil)
local a = UnitPower(unitDesignation, powerTypeEnum) or 0
local b = UnitPowerMax(unitDesignation, powerTypeEnum) or 1
applyRatio(self, a, b)
end
local function applyOverlayPower(self, unitDesignation, powerTypeEnum)
assert(self ~= nil)
assert(unitDesignation ~= nil)
assert(powerTypeEnum ~= nil)
local a = UnitPower(unitDesignation, powerTypeEnum) or 0
local b = UnitPowerMax(unitDesignation, powerTypeEnum) or 1
applyOverlay(self, a, b)
end
local function applyHealthFrameColorUnitClass(self, unitDesignation)
assert(self ~= nil)
assert(unitDesignation ~= nil)
local _, classDesignation = UnitClass(unitDesignation)
if not classDesignation then
return
end
local map = RAID_CLASS_COLORS
assert(map ~= nil)
assert('table' == type(map))
local colorTuple = map[classDesignation]
local r = 1
local g = 1
local b = 1
if colorTuple then
assert('table' == type(colorTuple))
r = math.min(math.max(0, math.abs(colorTuple.r)), 1)
g = math.min(math.max(0, math.abs(colorTuple.g)), 1)
b = math.min(math.max(0, math.abs(colorTuple.b)), 1)
end
local artwork = self.artwork
assert(artwork ~= nil)
local image = artwork:GetTexture()
if image then
artwork:SetVertexColor(r, g, b)
else
artwork:SetTexture(r, g, b)
end
end
local function applyHealthFrameColorUnitIsFriend(self, unitDesignation)
assert(self ~= nil)
assert(unitDesignation ~= nil)
local r = 255 / 255
local g = 215 / 255
local b = 0 / 255
if UnitIsFriend('player', unitDesignation) then
r = 124 / 255
g = 252 / 255
b = 0 / 255
elseif UnitIsEnemy('player', unitDesignation) then
r = 255 / 255
g = 69 / 255
b = 0 / 255
end
local artwork = self.artwork
assert(artwork ~= nil)
local image = artwork:GetTexture()
if image then
artwork:SetVertexColor(r, g, b)
else
artwork:SetTexture(r, g, b)
end
end
local function healthFrameEventProcessor(self)
validateProgressFrame(self)
local unitDesignation = getUnit(self) or 'none'
assert(unitDesignation ~= nil)
assert('string' == type(unitDesignation))
unitDesignation = string.lower(strtrim(unitDesignation))
assert(string.len(unitDesignation) >= 1)
assert(string.len(unitDesignation) <= 256)
if UnitExists(unitDesignation) and UnitIsConnected(unitDesignation) then
self:Show()
else
self:Hide()
return
end
applySize(self)
applyRatioHealth(self, unitDesignation)
applyOverlayHealth(self, unitDesignation)
local strategy = self.strategy
assert(strategy ~= nil)
assert('string' == type(strategy))
strategy = strtrim(strategy)
assert(string.len(strategy) >= 1)
assert(string.len(strategy) <= 256)
assert(strategy == 'UnitClass' or strategy == 'UnitIsFriend')
if 'UnitClass' == strategy then
applyHealthFrameColorUnitClass(self, unitDesignation)
elseif 'UnitIsFriend' == strategy then
applyHealthFrameColorUnitIsFriend(self, unitDesignation)
else
error('invalid enum: strategy must be either UnitClass or UnitIsFriend')
end
end
local function applyPowerFrameColor(self, powerTypeEnum)
assert(self ~= nil)
local r = 1
local g = 0
local b = 1
if powerTypeEnum then
--[[ See FrameXML/UnitFrame.lua:PowerBarColor ]]--
local map = PowerBarColor
assert(map ~= nil)
assert('table' == type(map))
local colorTuple = map[powerTypeEnum]
if colorTuple then
assert('table' == type(colorTuple))
r = math.min(math.max(0, math.abs(colorTuple.r)), 1)
g = math.min(math.max(0, math.abs(colorTuple.g)), 1)
b = math.min(math.max(0, math.abs(colorTuple.b)), 1)
end
end
local artwork = self.artwork
assert(artwork ~= nil)
local image = artwork:GetTexture()
if image then
artwork:SetVertexColor(r, g, b)
else
artwork:SetTexture(r, g, b)
end
end
local function powerFrameEventProcessor(self)
validateProgressFrame(self)
local unitDesignation = getUnit(self) or 'none'
assert(unitDesignation ~= nil)
assert('string' == type(unitDesignation))
unitDesignation = string.lower(strtrim(unitDesignation))
assert(string.len(unitDesignation) >= 1)
assert(string.len(unitDesignation) <= 256)
if UnitExists(unitDesignation) and UnitIsConnected(unitDesignation) then
self:Show()
else
self:Hide()
return
end
local powerTypeEnum
local strategy = self.strategy
assert(strategy ~= nil)
assert('string' == type(strategy))
strategy = strtrim(strategy)
assert(string.len(strategy) >= 1)
assert(string.len(strategy) <= 256)
if 'UnitPowerType' == strategy then
local enum, category = UnitPowerType(unitDesignation)
if category then
powerTypeEnum = enum
else
self:Hide()
return
end
else
--[[ See FrameXML/Constants.lua:SPELL_POWER_MANA ]]--
powerTypeEnum = _G[strategy]
end
assert(powerTypeEnum ~= nil)
assert('number' == type(powerTypeEnum))
assert(powerTypeEnum >= 0)
powerTypeEnum = math.min(math.max(0, math.floor(math.abs(powerTypeEnum))), 8192)
if UnitPowerMax(unitDesignation, powerTypeEnum) > 0 then
self:Show()
applySize(self)
applyRatioPower(self, unitDesignation, powerTypeEnum)
applyOverlayPower(self, unitDesignation, powerTypeEnum)
applyPowerFrameColor(self, powerTypeEnum)
else
self:Hide()
end
end
--[[ TODO Maybe make progress frame completely generic, by removing the unit field. ]]--
function Chorus.progressFrameMain(self)
assert(self ~= nil)
local frameDesignation = self:GetName()
assert(frameDesignation ~= nil)
assert('string' == type(frameDesignation))
frameDesignation = strtrim(frameDesignation)
assert(string.len(frameDesignation) >= 1)
assert(string.len(frameDesignation) <= 256)
local artwork = _G[frameDesignation .. 'Artwork']
assert(artwork ~= nil)
self.artwork = artwork
local label1 = _G[frameDesignation .. 'Text1']
assert(label1 ~= nil)
self.label1 = label1
local label2 = _G[frameDesignation .. 'Text2']
assert(label2 ~= nil)
self.label2 = label2
local label3 = _G[frameDesignation .. 'Text3']
assert(label3 ~= nil)
self.label3 = label3
self:RegisterEvent('PARTY_CONVERTED_TO_RAID')
self:RegisterEvent('PARTY_MEMBERS_CHANGED')
self:RegisterEvent('PARTY_MEMBER_DISABLE')
self:RegisterEvent('PARTY_MEMBER_ENABLE')
self:RegisterEvent('PLAYER_ALIVE')
self:RegisterEvent('PLAYER_FOCUS_CHANGED')
self:RegisterEvent('PLAYER_LOGIN')
self:RegisterEvent('PLAYER_TARGET_CHANGED')
self:RegisterEvent('RAID_ROSTER_UPDATE')
self:SetMinMaxValues(0, 1)
self:SetValue(1)
end
function Chorus.healthFrameMain(self, ...)
Chorus.progressFrameMain(self, ...)
self.strategy = 'UnitIsFriend'
self:SetScript('OnEvent', healthFrameEventProcessor)
self:RegisterEvent('UNIT_HEALTH')
--[[ TODO Add health deficit label ]]--
end
function Chorus.powerFrameMain(self, ...)
Chorus.progressFrameMain(self, ...)
self.strategy = 'UnitPowerType'
self:SetScript('OnEvent', powerFrameEventProcessor)
self:RegisterEvent('UNIT_ENERGY')
self:RegisterEvent('UNIT_MANA')
self:RegisterEvent('UNIT_MAXPOWER')
self:RegisterEvent('UNIT_POWER')
self:RegisterEvent('UNIT_RAGE')
self:RegisterEvent('UNIT_RUNIC_POWER')
end