--[[--------------------------------------------------------------------
oUF_AuraBars
Provides BuffBars and DebuffBars elements.
Rewritten by Phanx.
Example:
local BuffBars = CreateFrame("Frame", nil, frame)
BuffBars:SetPoint("BOTTOMLEFT", frame, "TOPLEFT", 0, 10)
BuffBars:SetPoint("BOTTOMRIGHT", frame, "TOPRIGHT", 0, 10)
frame.BuffBars = BuffBars
Options:
BuffBars.barHeight
- height of each bar
- defaults to 16
- width is controlled by the element's width
BuffBars.fontFile
- font file for bar text
- defaults to health bar value's font or GameFontNormal
BuffBars.fontOutline
- outline style for bar text
BuffBars.nameSize
- font size for spell names
- defaults to 10
BuffBars.timeSize
- font size for spell durations
- defaults to 10
BuffBars.customColor
- table with r/g/b key values for the element's bars
- defaults to green buffs and red debuffs
BuffBars.customFilter
- function that returns whether or not to show an aura
- defaults to showing boss debuffs, anything cast by the player or pet, or anything self-applied on an enemy
BuffBars.customSort
- function that sorts the auras
- defaults to showing timeless first, then the rest in descending order by duration
BuffBars.dispelTypeColors
- use debuff type colors for auras with types (magic, disease, etc)
- for auras with no type, see the .customColor option above
----------------------------------------------------------------------]]
local _, ns = ...
local oUF = ns.oUF or oUF
assert(oUF, "oUF_AuraBars was unable to locate oUF install.")
-- Upvalues for faster access:
local format, min, next, pairs, sort, type = format, min, next, pairs, sort, type
local UnitAura, UnitCanAttack, UnitIsUnit = UnitAura, UnitCanAttack, UnitIsUnit
------------------------------------------------------------------------
-- DEFAULTS
-- Override with element.customColor
local defaultColors = {
HELPFUL = { r = 0, g = 0.6, b = 0 },
HARMFUL = { r = 0.8, g = 0, b = 0 },
}
-- Override with element.customFilter
local function defaultAuraFilter(frame, unit, name, icon, count, dispelType, duration, expirationTime, casterUnit, isStealable, shouldConsolidate, spellID, canApplyAura, isBossDebuff, value1, value2, value3)
if isBossDebuff then
return true
elseif not casterUnit then
return UnitCanAttack("player", unit)
else
return UnitIsUnit(casterUnit, "player") or UnitIsUnit(casterUnit, "pet") or UnitIsUnit(casterUnit, "vehicle")
end
end
-- Override with element.customSort
local function defaultAuraSort(a, b)
if a.duration == 0 then
if b.duration == 0 then
return a.name < b.name
else
return true
end
else
if b.duration == 0 then
return false
else
return a.expirationTime > b.expirationTime
end
end
end
------------------------------------------------------------------------
-- CREATE BARS
local function UpdateLayout(element)
local bars = element.bars
local a, b, c, d = "BOTTOMLEFT", "TOPLEFT", "BOTTOMRIGHT", "TOPRIGHT"
if element.growDown then
a, b, c, d = b, a, d, c
end
for i = 1, #bars do
local bar = bars[i]
bar:ClearAllPoints()
if i > 1 then
bar:SetPoint(a, bars[i-1], b, 0, 0)
bar:SetPoint(c, bars[i-1], d, 0, 0)
else
bar:SetPoint(a, element, a, bar:GetHeight(), 0) -- leave room for the icon
bar:SetPoint(c, element, c, 0, 0)
end
end
end
local function bar_OnEnter(bar)
local element = bar:GetParent()
local aura = element.auras[bar.i]
GameTooltip:SetOwner(bar, "ANCHOR_TOPLEFT")
GameTooltip:SetUnitAura(element.__owner.unit, aura.index, element.__filter)
GameTooltip:Show()
end
local function bar_OnLeave(bar)
GameTooltip:Hide()
end
local function CreateBar(element, i)
local bars = element.bars
assert(#element.bars + 1 ~= bars, "Bad index passed to CreateBar")
local parent = element.__owner
local bar = CreateFrame("StatusBar", nil, element)
bar:SetHeight(element.barHeight or 16)
bar:SetStatusBarTexture([[Interface\AddOns\oUF_Terenna\Texture]] or [[Interface\ChatFrame\ChatFrameBackground]])
bar:EnableMouse(true)
bar:SetScript("OnEnter", bar_OnEnter)
bar:SetScript("OnLeave", bar_OnLeave)
bar:SetStatusBarColor(.25, .25, .25, 1)
bars[i] = bar
bar.i = i
-- Add a background:
local bg = bar:CreateTexture(nil, "BACKGROUND")
bg:SetAllPoints(true)
bg:SetTexture([[Interface\AddOns\oUF_Terenna\Texture]] or [[Interface\ChatFrame\ChatFrameBackground]])
bg:SetVertexColor(1, 1, 1)
bg:SetAlpha(.35)
bar.bg = bg
-- Add the icon:
local icon = bar:CreateTexture(nil, "ARTWORK")
icon:SetPoint("RIGHT", bar, "LEFT")
icon:SetHeight(element.barHeight or 16)
icon:SetWidth(element.barHeight or 16)
icon:SetTexCoord(0.06, 0.94, 0.06, 0.94)
bar.icon = icon
-- Add the time text:
local time = bar:CreateFontString(nil, "OVERLAY", "GameFontNormal")
time:SetPoint("RIGHT", -2, 1)
time:SetJustifyH("RIGHT")
time:SetTextColor(1, 1, 1)
bar.time = time
-- Add the name text:
local name = bar:CreateFontString(nil, "OVERLAY", "GameFontNormal")
name:SetPoint("LEFT", 2, 1)
name:SetPoint("RIGHT", time, "LEFT", -2, 0)
name:SetJustifyH("LEFT")
name:SetTextColor(1, 1, 1)
bar.name = name
-- Override the default font:
local font = element.fontFile or parent.Health.value and parent.Health.value:GetFont()
if font then
local size = select(2, GameFontNormal:GetFont())
local outline = element.fontOutline or parent.Health.value and select(3,parent.Health.value:GetFont())
time:SetFont(font, element.timeSize or 10)
name:SetFont(font, element.nameSize or 10)
end
-- Update the layout:
UpdateLayout(element)
-- Return the new bar:
return bar
end
------------------------------------------------------------------------
-- UPDATE TIMES FOR ACTIVE (DE)BUFFS
local function UpdateBars(element, elapsed)
local bars = element.bars
local now = GetTime()
for i = 1, #bars do
local bar = bars[i]
if bar.duration then
local remaining = bar.expirationTime - now
bar:SetValue(remaining)
bar.time:SetFormattedText(SecondsToTimeAbbrev(remaining))
end
end
end
------------------------------------------------------------------------
-- UTILITY FUNCTIONS
local table_create, table_delete
do
local pool = {}
function table_create()
local t = next(pool)
if t then
pool[t] = nil
end
return t or {}
end
function table_delete(t)
if type(t) == "table" then
for k, v in pairs(t) do
t[k] = table_delete(v)
end
t[true] = true
t[true] = nil
pool[t] = true
end
return nil
end
end
------------------------------------------------------------------------
-- UPDATE BARS BASED ON CURRENT (DE)BUFFS
local function Update(frame, event, unit, elementName)
if unit ~= frame.unit then return end
local element = frame[elementName]
local filterFunc = element.customFilter or defaultAuraFilter
local filter = element.__filter
local auras = element.auras
local numAuras = 0
-- Scan all (de)buffs on the unit:
for i = 1, 40 do
local name, icon, count, dispelType, duration, expirationTime, casterUnit, isStealable, shouldConsolidate, spellID, canApplyAura, isBossDebuff, value1, value2, value3 = UnitAura(unit, i, filter)
if not name then break end
if filterFunc(frame, unit, name, icon, count, dispelType, duration, expirationTime, casterUnit, isStealable, shouldConsolidate, spellID, canApplyAura, isBossDebuff, value1, value2, value3) then
numAuras = numAuras + 1
-- Store information about this (de)buff:
local t = auras[numAuras]
if not t then
t = table_create()
auras[numAuras] = t
end
t.index, t.name, t.icon, t.count, t.dispelType, t.duration, t.expirationTime, t.casterUnit, t.isStealable, t.shouldConsolidate, t.spellID, t.canApplyAura, t.isBossDebuff, t.value1, t.value2, t.value3 = i, name, icon, count or 1, dispelType, duration or 0, expirationTime, casterUnit, isStealable, shouldConsolidate, spellID, canApplyAura, isBossDebuff, value1, value2, value3
end
end
-- Clear old stored information:
for i = numAuras + 1, #auras do
auras[i] = table_delete(auras[i])
end
-- Sort the active (de)buffs:
sort(auras, element.sort or defaultAuraSort)
local bars = element.bars
local numAurasWithDuration = 0
-- Update the bars:
for i = 1, numAuras do
local aura = auras[i]
local bar = bars[i] or CreateBar(element, i)
bar.icon:SetTexture(aura.icon)
bar.name:SetText(aura.count > 1 and format("%s [%d]", aura.name, aura.count) or aura.name)
if aura.duration == 0 then
bar.duration, bar.expirationTime = nil, nil
bar:SetMinMaxValues(0, 1)
bar:SetValue(1)
bar.time:SetText(nil)
else
bar.duration, bar.expirationTime, bar.spellID = aura.duration, aura.expirationTime, aura.spellID
bar:SetMinMaxValues(0, aura.duration)
-- No need to set the value or time text here; the OnUpdate will do it.
numAurasWithDuration = numAurasWithDuration + 1
end
if aura.duration == 0 then
bar:SetStatusBarColor(1, 1, 1, 0.0001)
elseif element.dispelTypeColors then
if aura.dispelType then
if aura.dispelType == 'Magic' then
bar:SetStatusBarColor(0.2, 0.6, 1)
elseif aura.dispelType == 'Curse' then
bar:SetStatusBarColor(0.6, 0, 1)
elseif aura.dispelType == 'Disease' then
bar:SetStatusBarColor(0.6, 0.4, 0)
elseif aura.dispelType == 'Poison' then
bar:SetStatusBarColor(0, 0.6, 0)
end
else
bar:SetStatusBarColor(0.65, 0.1, 0.1)
end
bar:SetStatusBarColor(.25, .25, .25)
end
bar:Show()
end
-- Hide unused bars:
for i = numAuras + 1, #bars do
local bar = bars[i]
bar.duration, bar.expirationTime = nil, nil
bar:SetMinMaxValues(0, 1)
bar:SetValue(0)
bar.icon:SetTexture(nil)
bar.name:SetText(nil)
bar.time:SetText(nil)
bar:Hide()
end
-- Start or stop the OnUpdate as needed:
if numAurasWithDuration > 0 then
element:SetScript("OnUpdate", UpdateBars)
else
element:SetScript("OnUpdate", nil)
end
-- Adjust the total height in case there's a backdrop:
element:SetHeight(numAuras * (element.barHeight or 16))
end
------------------------------------------------------------------------
-- CREATE ELEMENTS
local function Enable(self, elementName, updateFunc, auraFilter)
local element = self[elementName]
element.__filter = auraFilter
element.__owner = self
self:RegisterEvent("UNIT_AURA", updateFunc)
element.auras = element.auras or {}
element.bars = element.bars or {}
return true
end
local function Disable(self, elementName, updateFunc)
local element = self[elementName]
self:UnregisterEvent("UNIT_AURA", updateFunc)
element:SetScript("OnUpdate", nil)
element:Hide()
for _, bar in pairs(element.bars) do
bar.duration, bar.expirationTime = nil, nil
bar:SetMinMaxValues(0, 1)
bar:SetValue(0)
bar.icon:SetTexture(nil)
bar.name:SetText(nil)
bar.time:SetText(nil)
bar:Hide()
end
return true
end
------------------------------------------------------------------------
local function UpdateBuffBars(self, event, unit)
return self.BuffBars and Update(self, event, unit, "BuffBars")
end
local function EnableBuffBars(self)
return self.BuffBars and Enable(self, "BuffBars", UpdateBuffBars, "HELPFUL")
end
local function DisableBuffBars(self)
return self.BuffBars and Disable(self, "BuffBars", UpdateBuffBars)
end
oUF:AddElement("BuffBars", UpdateBuffBars, EnableBuffBars, DisableBuffBars)
------------------------------------------------------------------------
local function UpdateDebuffBars(self, event, unit)
return self.DebuffBars and Update(self, event, unit, "DebuffBars")
end
local function EnableDebuffBars(self)
return self.DebuffBars and Enable(self, "DebuffBars", UpdateDebuffBars, "HARMFUL")
end
local function DisableDebuffBars(self)
return self.DebuffBars and Disable(self, "DebuffBars", UpdateDebuffBars)
end
oUF:AddElement("DebuffBars", UpdateDebuffBars, EnableDebuffBars, DisableDebuffBars)