local Ellipsis = _G['Ellipsis'] local L = LibStub('AceLocale-3.0'):GetLocale('Ellipsis') local LSM = LibStub('LibSharedMedia-3.0') local Aura = CreateFrame('Frame') local auraPool = Ellipsis.auraPool local activeAuras = Ellipsis.activeAuras local auraID = 1 -- unique ID for each aura object created local TIME_ABRV_HOUR = '%dhr' local TIME_ABRV_MINS = '%dm' local TIME_ABRV_SECS = '%ds' local TIME_ABRV_TENS = '%.1fs' local TIME_FULL_HOUR = '%d:%02d:%02d' local TIME_FULL_MINS = '%d:%02d' local TIME_FULL_SECS = '%.0f' local TIME_FULL_TENS = '%.1f' local FORMAT_UNIT_AURA = '%s: %s' local floor, ceil = math.floor, math.ceil local tinsert, tremove = table.insert, table.remove local unpack, pairs = unpack, pairs local auraDB -- variables configured by user options local highR, highG, highB, medR, medG, medB, lowR, lowG, lowB local pointBarTop, pointBarBot, pointIconTop, pointIconBot local unitWidth, sendAlerts, ghostingEnabled, ghostDuration local tickRate = 1 -- set to initial value to delay OnUpdate until options are configured local FormatRemainingTime -- function ref, set by user options Ellipsis.Aura = Aura -- ------------------------ -- AURA DISPLAY HELPERS -- ------------------------ local function ColourizeRemainingTime(time) if (time > 10) then -- high (> 10s) return highR, highG, highB else local colourMod, colourInv if (time > 5) then -- med (5 - 10s) colourMod = (time / 5) - 1 colourInv = 1 - colourMod return (medR * colourInv + highR * colourMod), (medG * colourInv + highG * colourMod), (medB * colourInv + highB * colourMod) else -- low (<= 5s) colourMod = (time / 5) colourInv = 1 - colourMod return (lowR * colourInv + medR * colourMod), (lowG * colourInv + medG * colourMod), (lowB * colourInv + medB * colourMod) end end end local function SecondsToTime_Abrv(time) if (time < 10) then return TIME_ABRV_TENS, time -- tenths of a second elseif (time < 61) then return TIME_ABRV_SECS, time -- seconds elseif (time < 3601) then return TIME_ABRV_MINS, ceil(time / 60) -- minutes else return TIME_ABRV_HOUR, ceil(time / 3600) -- hours end end local function SecondsToTime_Trun(time) if (time < 10) then return TIME_FULL_TENS, time -- tenths of a second elseif (time < 61) then return TIME_FULL_SECS, time -- seconds elseif (time < 3601) then local mins = floor(time / 60) -- minutes return TIME_FULL_MINS, mins, time - (mins * 60) else return TIME_ABRV_HOUR, ceil(time / 3600) -- hours end end local function SecondsToTime_Full(time) if (time < 10) then return TIME_FULL_TENS, time -- tenths of a second elseif (time < 61) then return TIME_FULL_SECS, time -- seconds elseif (time < 3601) then local mins = floor(time / 60) -- minutes return TIME_FULL_MINS, mins, time - (mins * 60) else local hours = floor(time / 3600) -- hours local mins = floor((time - (hours * 3600)) / 60) return TIME_FULL_HOUR, hours, mins, time - (hours * 3600) - (mins * 60) end end -- ------------------------ -- ONUPDATE SCRIPT HANDLER do ------------------------ local GetTime = GetTime local throttle = 0 local function OnUpdate(self, elapsed) throttle = throttle + elapsed if (throttle >= tickRate) then local currentTime = GetTime() local remaining for _, aura in pairs(activeAuras) do if (aura.expireTime > 0) then -- we only care about updating the data of non-passive auras remaining = aura.expireTime - currentTime if (remaining > 12) then -- active, and doesn't need to be colourized aura.bar:SetValue(remaining / aura.duration) aura.time:SetFormattedText(FormatRemainingTime(remaining)) elseif (remaining > 0) then -- active, and needs to be colourized aura.border:SetVertexColor(ColourizeRemainingTime(remaining)) aura.bar:SetValue(remaining / aura.duration) aura.bar:SetStatusBarColor(ColourizeRemainingTime(remaining)) aura.time:SetFormattedText(FormatRemainingTime(remaining)) elseif (remaining <= ghostDuration) then -- ghost timer has expired, remove aura aura:Release() elseif (not aura.expired) then -- aura hasn't been set as expired (but is), set expired state aura:SetExpired(currentTime) end end end throttle = throttle - tickRate end end Aura:SetScript('OnUpdate', OnUpdate) end -- ------------------------ -- AURA SCRIPT HANDLERS -- ------------------------ local function OnClick(self, button) if (button == 'LeftButton') then Ellipsis:Announce(self) elseif (button == 'RightButton') then if (IsShiftKeyDown()) then if (self.spellID > 0) then -- only allow blocking of actual spellIDs and not 'faked' ones Ellipsis:BlacklistAdd(self.spellID) end else self:Release() end end end local function OnEnter(self) if (not self.isMouseOver) then self.isMouseOver = true GameTooltip:SetOwner(self, 'ANCHOR_BOTTOMLEFT') if (auraDB.tooltips == 'FULL' and self.spellID > 0) then GameTooltip:SetSpellByID(self.spellID) else GameTooltip:SetText(self.spellName, 1, 1, 1) end GameTooltip:AddLine(self.spellID > 0 and L.AuraTooltip or L.AuraTooltipNoBlock) GameTooltip:Show() end end local function OnLeave(self) if (self.isMouseOver) then self.isMouseOver = false GameTooltip:Hide() end end -- ------------------------ -- AURA CREATION -- ------------------------ local function CreateAura(parentUnit) local new = CreateFrame('Button', nil, parentUnit) local widget -- main gui widgets widget = new:CreateTexture(nil, 'BORDER') widget:SetPoint(pointIconTop, new, pointIconTop) widget:SetPoint(pointIconBot, new, pointIconBot) widget:SetTexCoord(0.08, 0.92, 0.08, 0.92) new.icon = widget widget = new:CreateTexture(nil, 'ARTWORK') widget:SetPoint('TOPLEFT', new.icon, 'TOPLEFT') widget:SetPoint('BOTTOMRIGHT', new.icon, 'BOTTOMRIGHT') widget:SetTexCoord(0.03125, 0.96875, 0.03125, 0.96875) widget:SetTexture('Interface\\AddOns\\Ellipsis\\IconBorder') new.border = widget widget = CreateFrame('StatusBar', nil, new) widget:SetPoint(pointBarTop, new, pointBarTop) widget:SetPoint(pointBarBot, new, pointBarBot) widget:SetFrameLevel(widget:GetFrameLevel() - 1) -- ensure bar is behind the base frame (where text is anchored) widget:SetMinMaxValues(0, 1) new.bar = widget widget = new.bar:CreateTexture(nil, 'BACKGROUND') widget:SetAllPoints() new.barBG = widget -- fontStrings widget = new:CreateFontString(nil, 'OVERLAY', 'NumberFontNormal') widget:SetPoint('BOTTOMRIGHT', new.icon, 'BOTTOMRIGHT', 0.5, 0) widget:SetJustifyH('RIGHT') widget:SetJustifyV('BOTTOM') new.stacks = widget widget = new:CreateFontString(nil, 'OVERLAY', 'GameFontNormalSmall') new.time = widget widget = new:CreateFontString(nil, 'OVERLAY', 'GameFontNormalSmall') widget:SetJustifyH('LEFT') widget:SetJustifyV('MIDDLE') new.name = widget new.parentUnit = parentUnit new.auraID = auraID auraID = auraID + 1 new['Release'] = Aura.Release new['Configure'] = Aura.Configure new['SetExpired'] = Aura.SetExpired new['Update'] = Aura.Update return new end function Aura:New(currentTime, parentUnit, spellID, spellName, spellIcon, duration, expireTime, stackCount) local new = tremove(auraPool, 1) -- grab an aura from the inactive pool (if any) if (not new) then -- no inactive auras, create new new = CreateAura(parentUnit) new:Configure() else -- existing object, set physical parent as well as parent lookup new:SetParent(parentUnit) new.parentUnit = parentUnit end new.created = currentTime new.updated = currentTime new.expired = false -- new aura, cannot be expired new.spellID = spellID new.spellName = spellName new.duration = duration new.expireTime = expireTime new.stackCount = stackCount new.icon:SetTexture(spellIcon) new.icon:SetDesaturated(false) new.stacks:SetText(stackCount > 1 and stackCount or '') if (auraDB.textFormat == 'AURA') then new.name:SetText(spellName) elseif (auraDB.textFormat == 'UNIT') then new.name:SetText(new.parentUnit.unitName) else new.name:SetFormattedText(FORMAT_UNIT_AURA, new.parentUnit.unitName, spellName) end if (duration == 0) then -- passive effect new.border:SetVertexColor(unpack(auraDB.colourHigh)) new.bar:SetValue(1) new.bar:SetStatusBarColor(unpack(auraDB.colourHigh)) new.time:SetText(L.Aura_Passive) else local remaining = expireTime - currentTime new.border:SetVertexColor(ColourizeRemainingTime(remaining)) new.bar:SetValue(remaining / duration) new.bar:SetStatusBarColor(ColourizeRemainingTime(remaining)) new.time:SetFormattedText(FormatRemainingTime(remaining)) end new:Show() activeAuras[new.auraID] = new -- add new aura to primary aura lookup return new end -- ------------------------ -- AURA FUNCTIONS -- ------------------------ function Aura:Release(flagBurst) self:Hide() activeAuras[self.auraID] = nil -- remove self from aura lookup if (not flagBurst) then -- tell parent Unit to remove ourselves, but only if this isn't a burst removal (eg, Unit death) self.parentUnit:RemoveAura(self.spellID) end tinsert(auraPool, self) -- add self back into the auraPool (do last so we can't be used again before our Unit lets us go) end function Aura:Configure() -- common configuration between styles if (auraDB.interactive) then self:EnableMouse(true) self:SetScript('OnClick', OnClick) self:RegisterForClicks('LeftButtonUp', 'RightButtonUp') if (auraDB.tooltips ~= 'OFF') then self:SetScript('OnEnter', OnEnter) self:SetScript('OnLeave', OnLeave) else self:SetScript('OnEnter', nil) self:SetScript('OnLeave', nil) end else self:EnableMouse(false) -- non-interactive, disable all mouse capture end self.stacks:SetFont(LSM:Fetch('font', auraDB.stacksFont), auraDB.stacksFontSize, 'OUTLINE') self.stacks:SetTextColor(unpack(auraDB.colourStacks)) self.time:SetFont(LSM:Fetch('font', auraDB.textFont), auraDB.textFontSize) self.time:SetTextColor(unpack(auraDB.colourText)) self.time:ClearAllPoints() self.name:ClearAllPoints() if (auraDB.style == 'BAR') then self:SetWidth(unitWidth) self:SetHeight(auraDB.barSize) self.icon:SetWidth(auraDB.barSize) self.bar:SetWidth(unitWidth - auraDB.barSize) -- bar is the full width of the aura object minus the width of the icon self.bar:SetStatusBarTexture(LSM:Fetch('statusbar', auraDB.barTexture)) self.bar:Show() self.barBG:SetTexture(LSM:Fetch('statusbar', auraDB.barTexture)) self.barBG:SetVertexColor(unpack(auraDB.colourBarBackground)) self.time:SetPoint('RIGHT', self.bar, 'RIGHT', -3, 0) self.time:SetJustifyH('RIGHT') self.time:SetJustifyV('MIDDLE') self.name:SetHeight(auraDB.barSize) self.name:SetPoint('LEFT', self.bar, 'LEFT', 3, 0) self.name:SetPoint('RIGHT', self.time, 'LEFT', -4, 0) self.name:SetFont(LSM:Fetch('font', auraDB.textFont), auraDB.textFontSize) self.name:SetTextColor(unpack(auraDB.colourText)) self.name:Show() else -- auraDB.style == 'ICON' self:SetWidth(auraDB.iconSize) self:SetHeight(auraDB.iconSize) self.icon:SetWidth(auraDB.iconSize) self.bar:Hide() self.time:SetPoint('TOP', self, 'BOTTOM', 0, -1) self.time:SetJustifyH('CENTER') self.time:SetJustifyV('TOP') self.name:Hide() end end function Aura:SetExpired(currentTime) if (self.expired) then return end -- already set as expired currentTime = currentTime or GetTime() -- we need a time value, make sure we have one local broken = (currentTime + 0.5) < self.expireTime -- check if aura expired early (with a bit of slush time) if (sendAlerts) then Ellipsis:AlertAura(broken, self) -- send appropriate alert if watching for alerts end if (not ghostingEnabled) then -- not using the ghosting system, aura is gone self:Release() else self.expired = true self.expireTime = currentTime -- make sure to update expiration for proper ghost duration self.stackCount = 0 -- if ghosting, then aura has faded, thus stacks have too self.icon:SetDesaturated(true) self.border:SetVertexColor(unpack(auraDB.colourGhosting)) self.bar:SetValue(1) self.bar:SetStatusBarColor(unpack(auraDB.colourGhosting)) self.stacks:SetText('') self.time:SetText('') if (broken) then self.parentUnit:UpdateDisplay(true) -- parent Unit needs to update display due to an early break end end end function Aura:Update(currentTime, duration, expireTime, stackCount) if (self.expired) then -- a verified, active aura obviously isnt expired anymore, undo ghosting if active self.expired = false self.icon:SetDesaturated(false) end self.updated = currentTime self.duration = duration self.expireTime = expireTime if (duration == 0) then -- passive effect self.border:SetVertexColor(unpack(auraDB.colourHigh)) self.bar:SetValue(1) self.bar:SetStatusBarColor(unpack(auraDB.colourHigh)) self.time:SetText(L.Aura_Passive) else local remaining = expireTime - currentTime self.border:SetVertexColor(ColourizeRemainingTime(remaining)) self.bar:SetValue(remaining / duration) self.bar:SetStatusBarColor(ColourizeRemainingTime(remaining)) self.time:SetFormattedText(FormatRemainingTime(remaining)) end if (stackCount ~= self.stackCount) then -- stack change self.stackCount = stackCount self.stacks:SetText((stackCount > 1) and stackCount or '') end end -- ------------------------ -- AURA OBJECT FUNCTIONS -- ------------------------ function Ellipsis:InitializeAuras() auraDB = self.db.profile.auras self:ConfigureAuras() end function Ellipsis:ConfigureAuras() unitWidth = self.db.profile.units.width tickRate = self.db.profile.advanced.tickRate if (auraDB.flipIcon) then pointBarTop = 'TOPLEFT' pointBarBot = 'BOTTOMLEFT' pointIconTop = 'TOPRIGHT' pointIconBot = 'BOTTOMRIGHT' else pointBarTop = 'TOPRIGHT' pointBarBot = 'BOTTOMRIGHT' pointIconTop = 'TOPLEFT' pointIconBot = 'BOTTOMLEFT' end ghostingEnabled = auraDB.ghosting ghostDuration = auraDB.ghostDuration * -1 sendAlerts = (self.db.profile.notify.auraBrokenAlerts or self.db.profile.notify.auraExpiredAlerts) highR, highG, highB = unpack(auraDB.colourHigh) medR, medG, medB = unpack(auraDB.colourMed) lowR, lowG, lowB = unpack(auraDB.colourLow) if (auraDB.timeFormat == 'ABRV') then FormatRemainingTime = SecondsToTime_Abrv elseif (auraDB.timeFormat == 'FULL') then FormatRemainingTime = SecondsToTime_Full else -- auraDB.timeFormat == 'TRUN' FormatRemainingTime = SecondsToTime_Trun end end function Ellipsis:UpdateExistingAuras() for _, aura in pairs(auraPool) do aura:Configure() end for _, aura in pairs(activeAuras) do aura:Configure() if (aura.expired) then -- expired aura aura.border:SetVertexColor(unpack(auraDB.colourGhosting)) aura.bar:SetStatusBarColor(unpack(auraDB.colourGhosting)) else -- actively timed aura, set to 'high' colour (if its <12s OnUpdate will correct this, bit lazy but lot less hassle to code) aura.border:SetVertexColor(unpack(auraDB.colourHigh)) aura.bar:SetStatusBarColor(unpack(auraDB.colourHigh)) end if (auraDB.textFormat == 'AURA') then aura.name:SetText(aura.spellName) elseif (auraDB.textFormat == 'UNIT') then aura.name:SetText(aura.parentUnit.unitName) else aura.name:SetFormattedText(FORMAT_UNIT_AURA, aura.parentUnit.unitName, aura.spellName) end aura.icon:ClearAllPoints() aura.icon:SetPoint(pointIconTop, aura, pointIconTop) aura.icon:SetPoint(pointIconBot, aura, pointIconBot) aura.bar:ClearAllPoints() aura.bar:SetPoint(pointBarTop, aura, pointBarTop) aura.bar:SetPoint(pointBarBot, aura, pointBarBot) end end