Compare with Previous | Blame | View Log
--[[
Class bars
Runes, shards, burning embers, etc.
By Kesava for oUF Kui
]]
local addon, ns = ...
local kui = ns.kui
local cb
-------------------------------------------------------- Utility funtions --
-- Arranges bars into columns depending on which bars are visible
local function ArrangeBars()
local shown, pv, pc = {}
-- pv = previous visible bar
-- pc = previous visible bar which starts a column
for groupId,group in pairs(cb.container.groups) do
if not shown[groupId] then
shown[groupId] = 0
end
if not group.rows then
group.rows = #group.bars
end
for _,bar in ipairs(group.bars) do
if bar.bg:IsShown() then
shown[groupId] = shown[groupId] + 1
end
end
if shown[groupId] > 0 then
if group.rows == #group.bars then
-- automatically adjust bar width to fit container (i.e. when one
-- bar is hidden, expand the others to fit the total width)
group.autorows = shown[groupId]
else
-- use given number of rows to group bars together
group.autorows = group.rows or #group.bars
end
local rows = group.autorows
group.width = floor((cb.width / rows) - (cb.container.space * (rows - 1)) / rows)
-- add 1 to the width of each bar until remainingWidth is 0
-- i.e ensure bar width matches up correctly with the width of the container
group.totalWidth = (group.width * rows) + (cb.container.space * (rows - 1))
group.remainingWidth = cb.width - group.totalWidth
local vid = 0 -- visible id, [k] - [number of non-shown bars]
for _,bar in ipairs(group.bars) do
local group = cb.container.groups[bar.group]
if bar.bg:IsShown() then
vid = vid + 1
bar.bg:ClearAllPoints()
bar.bg:SetWidth(
group.width +
(group.remainingWidth > 0 and 1 or 0) +
(bar.hasBorder * 2)
)
if group.remainingWidth > 0 then
group.remainingWidth = group.remainingWidth - 1
end
if not pv then
-- first bar
-- anchor to the unit frame
bar.bg:SetPoint('BOTTOMLEFT', -bar.hasBorder, -bar.hasBorder)
pc = bar
else
-- subsequent bars, find correct position
if (vid-1) % group.autorows == 0 then
-- first bar in this row
-- above the previous row
bar.bg:SetPoint('BOTTOMLEFT', pc.bg, 'TOPLEFT',
(0 + pc.hasBorder) - bar.hasBorder,
(cb.container.space - pc.hasBorder) - bar.hasBorder)
pc = bar
else
-- right of the previous bar
bar.bg:SetPoint('LEFT', pv.bg, 'RIGHT',
(cb.container.space + pv.hasBorder) - bar.hasBorder,
0)
end
end
pv = bar
end
end
end
end
end
-- Hides the given bar and calls ArrangeBars
local function HideBar(self, bar)
bar.bg:Hide()
ArrangeBars()
end
-- Shows the given bar and calls ArrangeBars
local function ShowBar(self, bar)
bar.bg:Show()
ArrangeBars()
end
----------------------------------------------------------- Tooltip functions --
--------------------------------------------------------------- Totem tooltip --
local function ShamanTooltip(bar)
if bar.name then
local tl, delim = bar.tl, 's'
if tl > 60 then
tl = tl / 60
delim = 'm'
end
tl = floor(tl)
return {
bar.name,
{
t = tl .. delim,
c = { r = .6, g = .6, b = .6 }
}
}
end
end
------------------------------------------------------------ Update functions --
------------------------------------------------------- Update generic powers --
-- must be used as update.f
local function UpdateGeneric(self, event, ...)
local visible, curr = 0
curr = UnitPower('player', cb.type)
for k, bar in ipairs(cb.bars) do
if curr >= k then
visible = visible + 1
bar.bg:SetAlpha(1)
else
bar.bg:SetAlpha(.3)
end
end
end
----------------------------------------------------------- Update Totem Bars --
local function UpdateShamanBar(self, bar)
local haveTotem, totemName, startTime, duration = GetTotemInfo(bar.id)
if haveTotem then
bar.name = totemName
bar.tl = (startTime + duration - GetTime())
if bar.tl >= 0 then
local total = 0
ShowBar(self, bar)
bar:SetValue(1 - ((GetTime() - startTime) / duration))
bar:SetScript('OnUpdate', function(self, elapsed)
total = total + elapsed
if total > 0.1 then
total = 0
local haveTotem, totemName, startTime, duration = GetTotemInfo(self.id)
self:SetValue(1 - ((GetTime() - startTime) / duration))
self.tl = (startTime + duration - GetTime())
end
end)
else
bar:SetScript('OnUpdate', nil)
HideBar(self, bar)
end
else
HideBar(self, bar)
end
end
---------------------------------------------------- Update druid shroom bars --
local function UpdateDruidShrooms(self, bar)
if bar.id > 3 then return end
UpdateShamanBar(self, bar)
end
---------------------------------------------------------------- Update Runes --
local function UpdateDeathKnightBar(self, bar)
local type, startTime, duration, charged = GetRuneType(bar.id), GetRuneCooldown(bar.id)
if not type then return end
bar:SetStatusBarColor(unpack(self.ClassBars.colours[type]))
bar.tl = startTime + duration - GetTime()
if not charged then
bar:SetValue((GetTime() - startTime) / duration)
if not bar:GetScript('OnUpdate') then
bar.bg:SetAlpha(.3)
bar:SetScript('OnUpdate', function(self, elapsed)
self.elapsed = (self.elapsed or 0) + elapsed
if self.elapsed > 0.1 then
self.elapsed = 0
local startTime, duration = GetRuneCooldown(bar.id)
bar:SetValue((GetTime() - startTime) / duration)
end
end)
end
else
bar.bg:SetAlpha(1)
bar:SetValue(1)
bar:SetScript('OnUpdate', nil)
end
end
----------------------------------------------------------- Update druid bars --
local function UpdateDruid(self, event)
-- Eclipse -----------------------------------------------------------------
if event == 'UPDATE_SHAPESHIFT_FORM' or
event == 'PLAYER_ENTERING_WORLD' or
not event
then
-- toggle bars depending on form
if GetShapeshiftFormID() == MOONKIN_FORM then
ShowBar(self, cb.bars[4])
else
HideBar(self, cb.bars[4])
end
end
if cb.bars[4].bg:IsShown() then
-- set colour based on current direction
local curr = UnitPower('player', SPELL_POWER_ECLIPSE)
cb.bars[4]:SetValue(curr < 0 and -curr or curr)
if curr > 0 then
-- solar
cb.bars[4]:SetStatusBarColor(238/255, 200/255, 77/255)
cb.bars[4].col:SetVertexColor(238/255, 200/255, 77/255, .2)
else
-- lunar
cb.bars[4]:SetStatusBarColor(60/255, 150/255, 220/255)
cb.bars[4].col:SetVertexColor(60/255, 150/255, 220/255, .2)
end
-- get eclipse status
local hasEclipse = false
for i=0,40 do
local _,_,_,_,_,_,_,_,_,_, spellID = UnitBuff('player', i)
if spellID == ECLIPSE_BAR_SOLAR_BUFF_ID then
hasEclipse = { 238/255, 200/255, 77/255, .5 }
break
elseif spellID == ECLIPSE_BAR_LUNAR_BUFF_ID then
hasEclipse = { 60/255, 150/255, 220/255, .5 }
break
end
end
if hasEclipse then
cb.bars[4].bg:SetBackdropBorderColor(unpack(hasEclipse))
else
cb.bars[4].bg:SetBackdropBorderColor(0,0,0,0)
end
end
-- Mana --------------------------------------------------------------------
local shown = false
if UnitPowerType('player') ~= 0 then
local curr, max =
UnitPower('player', 0), UnitPowerMax('player', 0)
if curr ~= max then
cb.bars[5]:SetMinMaxValues(0, max)
cb.bars[5]:SetValue(curr)
ShowBar(self, cb.bars[5])
shown = true
end
end
if not shown and cb.bars[5].bg:IsShown() then
HideBar(self, cb.bars[5])
end
end
---------------------------------------------------------------- Demonic fury --
local function UpdateDemonicFury(self, event)
cb.bars[1]:SetValue(UnitPower('player', SPELL_POWER_DEMONIC_FURY))
end
-------------------------------------------------------------- Burning embers --
-- each bar fills to 10
local function UpdateBurningEmbers(self, event)
local power, bpower = UnitPower('player', SPELL_POWER_BURNING_EMBERS, true)
for k, bar in ipairs(cb.bars) do
bpower = power - ((k-1)*10)
bar:SetValue(bpower)
end
end
------------------------------------------------------- Post-create functions --
------------------------------------------------ Post-create for generic bars --
local function CreateGenericBar(self)
for k, bar in ipairs(cb.bars) do
if not bar.col then
-- create bar background
bar.col = bar:CreateTexture(nil, 'BACKGROUND')
bar.col:SetTexture(kui.m.t.sbar)
bar.col:SetAllPoints(bar)
end
local r,g,b = bar:GetStatusBarColor()
bar.col:SetVertexColor(r, g, b, .2)
end
end
--------------------------------------------------------------------- Eclipse --
local function CreateEclipseBar(self)
local bar = cb.bars[4]
local bg = bar.bg
bg:SetBackdrop({
bgFile = kui.m.t.solid,
edgeFile = kui.m.t.shadow, edgeSize = 2,
insets = { top = 2, right = 2, bottom = 2, left = 2 }
})
bar.hasBorder = 2
bg:SetBackdropColor(0,0,0,1)
bg:SetBackdropBorderColor(1,1,1,0)
bg:SetWidth(bg:GetWidth()+4)
bg:SetHeight(bg:GetHeight()+4)
bar:SetPoint('TOPLEFT', bg, 'TOPLEFT', 3, -3)
bar:SetPoint('BOTTOMRIGHT', bg, 'BOTTOMRIGHT', -3, 3)
CreateGenericBar(self)
end
--[[--------------------------------------------------------- Update function --
Calls cb.o.update.f(self)
Calls cb.o.update.b(self, bar) for each bar created by Create()
]]
local function Update(self, event, ...)
if event == 'UNIT_POWER' or
event == 'UNIT_POWER_FREQUENT' or
event == 'UNIT_AURA'
then
local unit = ...
if unit ~= self.unit then return end
end
cb = cb or self.ClassBars
if not cb.o then return end
if cb.o.update.f then
cb.o.update.f(self, event, ...)
end
if cb.o.update.b then
for k, b in pairs(cb.bars) do
cb.o.update.b(self, b)
end
end
end
--[[------------------------------------------------------------- Create bars --
Creates bars from cb.o object
Calls cb.o.create(self) upon completion
Also forces Update with nil event
]]
local function CreateBars(self)
cb.bars = cb.bars or {}
--print('CreateBars')
local space = 1
local groups = cb.o.groups
-- parse groups
if groups then
groups[0] = { bars = {} }
else
groups = { [0] = { bars = {} } }
end
cb.container.groups = groups
cb.container.space = space
-- create/update bars
for k,group in pairs(groups) do
if not group.bars then
group.bars = {}
end
end
-- create/update bars, default method
for k, v in ipairs(cb.o.bars) do
local b, bg
local group = groups[v.group or 0]
if cb.bars[k] then
-- update bar size, position...
b = cb.bars[k]
bg = b.bg
bg:ClearAllPoints()
else
-- create a bar
bg = CreateFrame("Frame", nil, cb.container)
b = CreateFrame("StatusBar", nil, bg)
bg:SetBackdrop({ bgFile = kui.m.t.sbar })
bg:SetBackdropColor(.05, .05, .05)
b:SetPoint('TOPLEFT', bg, 'TOPLEFT', 1, -1)
b:SetPoint('BOTTOMRIGHT', bg, 'BOTTOMRIGHT', -1, 1)
b:SetStatusBarTexture(kui.m.t.sbar)
if cb.o.tooltip then
-- enable tooltip
b:EnableMouse(true)
b:SetScript('OnEnter', function(self)
lines = cb.o.tooltip(self)
if lines and #lines > 0 then
GameTooltip:SetOwner(self.bg, 'ANCHOR_CURSOR')
for _, line in pairs(lines) do
if line.t and line.c then
GameTooltip:AddLine(line.t, line.c.r, line.c.g, line.c.b)
else
GameTooltip:AddLine(line)
end
end
GameTooltip:Show()
end
end)
b:SetScript('OnLeave', function()
GameTooltip:Hide()
end)
end
b.bg = bg
b.id = v.id or k
b.group = v.group or 0
b.hasBorder = 0
cb.bars[k] = b
end
tinsert(group.bars, b)
b:SetStatusBarColor(unpack(v.colour or cb.o.colour or {1,1,1}))
b:SetMinMaxValues(unpack(v.minmax or cb.o.minmax or {0,1}))
if not v.minmax and not cb.o.minmax then
b:SetValue(1)
end
bg:SetHeight(cb.height)
end
-- register events
for k, e in pairs(cb.o.events) do
--self:RegisterEvent(e, Update) -- TODO TEMP WORKAROUND FOR OUF BUG (possibly)
cb.container:RegisterEvent(e)
end
-- call post create
if cb.o.create then
cb.o.create(self)
end
Update(self)
-- size/position bars
ArrangeBars()
end
--------------------------------------------------------------- Update object --
-- updates the bar object when power type or max power changes
local function UpdateObject(self, event, unit, resource)
--print('UpdateObject')
cb = cb or self.ClassBars
if cb.level and UnitLevel('player') < cb.level then
return
end
if event == 'UNIT_MAXPOWER' then
if unit ~= 'player' then return end
resource = _G['SPELL_POWER_'..resource]
if resource ~= cb.type then
return
end
else
-- power type has changed, or set up for initial display
--print('Power type update (or initial call)')
if cb.types then
cb.type = cb.PowerTypes[GetSpecialization()]
end
if cb.types then
cb.o = cb.types[cb.type]
end
if not cb.o then
-- this specialization has no object attached to it
cb.container:Hide()
cb.container:UnregisterAllEvents()
return
else
cb.container:Show()
end
if not cb.o.bars then
cb.o.dynamic = true
cb.o.bars = {}
end
resource = cb.type
end
local powerMax = UnitPowerMax('player', resource)
if cb.bars then
-- cycle existing bars to destroy (in case powerMax has decreased)
for k, bar in ipairs(cb.bars) do
if (cb.o.dynamic and k > powerMax) or
(not cb.o.dynamic and k > #cb.o.bars)
then
-- destroy bars which are no longer needed
bar:UnregisterAllEvents()
HideBar(self, bar)
-- they'll be garbage collected... right?
cb.bars[k] = nil
cb.o.bars[k] = nil
end
end
end
if cb.o.dynamic then
--print('Dynamic bars')
-- determine bars to add to object (in case powerMax has increased)
for k=1,powerMax do
if not cb.o.bars[k] then
--print('Will create bar '..k)
cb.o.bars[k] = {}
end
end
end
if #cb.o.bars > 0 then
-- create/update bars
CreateBars(self)
end
end
-------------------------------------------------------- Enable ClassBars --
local function Enable(self, unit)
cb = self.ClassBars or nil
if not cb then return end
if cb.class ~= "SHAMAN" and cb.class ~= "DEATHKNIGHT" and
cb.class ~= "DRUID" and cb.class ~= "PALADIN" and
cb.class ~= "WARLOCK" and cb.class ~= "MONK" and cb.class ~= 'PRIEST'
then
cb = nil
return
end
if cb.class == 'WARLOCK' then
cb.PowerTypes = {
[1] = SPELL_POWER_SOUL_SHARDS,
[2] = SPELL_POWER_DEMONIC_FURY,
[3] = SPELL_POWER_BURNING_EMBERS
}
elseif cb.class == 'PRIEST' then
cb.PowerTypes = {
[3] = SPELL_POWER_SHADOW_ORBS
}
end
-- create container
cb.container = CreateFrame("Frame", nil, self)
cb.container:SetWidth(cb.width)
cb.container:SetHeight(cb.height)
-- TODO do this?
--if cb.horizcenter == nil or cb.horizcenter == true then
cb.container:SetPoint('BOTTOMLEFT', self, 'TOPLEFT',
floor((self:GetWidth()/2) - (cb.width/2)), 1)
--end
-- TODO TEMP WORKAROUND FOR OUF BUG (possibly)
cb.container:SetScript('OnEvent', function(_, event, ...)
Update(self, event, ...)
end)
cb.o = {}
------------------------------------------------------- Create Totem Bars --
if cb.class == 'SHAMAN' then
cb.o.bars = {
[1] = { colour = {255/255, 109/255, 22/255} }, -- fire
[2] = { colour = {120/255, 255/255, 60/255} }, -- earth
[3] = { colour = {112/255, 65/255, 255/255} }, -- air
[4] = { colour = {112/255, 255/255, 255/255} }, -- water
}
cb.o.events = { 'PLAYER_TOTEM_UPDATE' }
cb.o.update = { ['b'] = UpdateShamanBar }
cb.o.create = CreateGenericBar
cb.o.tooltip = ShamanTooltip
------------------------------------------------------------ Create Runes --
elseif cb.class == 'DEATHKNIGHT' then
cb.o.bars = {
[1] = {}, [2] = {},
[3] = { id = 5 }, [4] = { id = 6 },
[5] = { id = 3 }, [6] = { id = 4 },
}
cb.o.events = { 'RUNE_POWER_UPDATE', 'RUNE_TYPE_UPDATE' }
cb.colours = {
[1] = { .7, 0, 0 }, -- blood
[2] = { .3, .7, 0 }, -- unholy
[3] = { .2, .2, .8 }, -- frost
[4] = { 1, .5, 0 } -- death
}
cb.o.create = function(self)
RuneFrame:Hide()
RuneFrame.Show = function() return end
RuneFrame:UnregisterAllEvents()
end
cb.o.update = { ['b'] = UpdateDeathKnightBar }
---------------------------------------------------------- Create Eclipse --
elseif cb.class == 'DRUID' then
cb.o.rows = 1
cb.o.groups = { [1] = { rows = 1 }, [2] = { rows = 3 } }
cb.o.bars = {
[1] = { group = 2, colour = { 0, 1, .6 } },
[2] = { group = 2, colour = { 0, 1, .6 } },
[3] = { group = 2, colour = { 0, 1, .6 } },
[4] = { group = 1, colour = { 111/255, 186/255, 245/255 }, minmax = { 0, 100 } }, -- lunar
[5] = { group = 1, colour = { 78/255, 95/255, 190/255 } }, -- mana
}
cb.o.events = {
'PLAYER_TOTEM_UPDATE',
'UPDATE_SHAPESHIFT_FORM',
'ECLIPSE_DIRECTION_CHANGE',
'UNIT_POWER', 'UNIT_MAXPOWER', 'UNIT_AURA'
}
cb.o.create = CreateEclipseBar
cb.o.update = { ['f'] = UpdateDruid, ['b'] = UpdateDruidShrooms }
cb.o.tooltip = ShamanTooltip
----------------------------------------------------------- Create holy power --
elseif cb.class == 'PALADIN' then
cb.type = SPELL_POWER_HOLY_POWER
cb.o.colour = { 1, 1, 0 }
cb.o.events = { 'UNIT_POWER' }
cb.o.update = { ['f'] = UpdateGeneric }
------------------------------------------------------- Create warlock powers --
elseif cb.class == 'WARLOCK' then
cb.level = 10
cb.types = {
[SPELL_POWER_DEMONIC_FURY] = {
bars = { [1] = { minmax = { 0, 1000 } } },
colour = { .4, 1, 0 },
update = { ['f'] = UpdateDemonicFury },
create = CreateGenericBar,
events = { 'UNIT_POWER' }
},
[SPELL_POWER_BURNING_EMBERS] = {
colour = { 1, .3, 0 },
update = { ['f'] = UpdateBurningEmbers },
create = CreateGenericBar,
minmax = { 0, 10 },
events = { 'UNIT_POWER' }
},
[SPELL_POWER_SOUL_SHARDS] = {
colour = { .5, 0, 1 },
update = { ['f'] = UpdateGeneric },
events = { 'UNIT_POWER' }
}
}
------------------------------------------------------------ Shadow orbs (13) --
elseif cb.class == 'PRIEST' then
cb.types = {
[SPELL_POWER_SHADOW_ORBS] = {
colour = { .5, 0, 1 },
update = { ['f'] = UpdateGeneric },
events = { 'UNIT_POWER' }
}
}
-------------------------------------------------------------------- Chi (12) --
elseif cb.class == 'MONK' then
cb.type = SPELL_POWER_CHI
cb.o.colour = { .5, 1, 1 }
cb.o.events = { 'UNIT_POWER' }
cb.o.update = { ['f'] = UpdateGeneric }
end
self:RegisterEvent('PLAYER_ENTERING_WORLD', Update)
if cb.type or cb.types then
self:RegisterEvent('UNIT_MAXPOWER', UpdateObject)
self:RegisterEvent('PLAYER_TALENT_UPDATE', UpdateObject)
end
if cb.type or cb.types then
UpdateObject(self) -- parse object
else
CreateBars(self)
end
end
oUF:AddElement("ClassBars", nil, Enable, nil)