local oUF = select(2, ...).oUF local tags = oUF.Tags local tagMethods = tags.Methods local tagEvents = tags.Events local tagSharedEvents = tags.SharedEvents local gsub = string.gsub local format = string.format local floor = math.floor local DEAD_TEXTURE = [[|TInterface\RaidFrame\Raid-Icon-DebuffDisease:26|t]] local function Short(value) if(value >= 1e6) then return gsub(format('%.2fm', value / 1e6), '%.?0+([km])$', '%1') elseif(value >= 1e4) then return gsub(format('%.1fk', value / 1e3), '%.?0+([km])$', '%1') else return value end end local function Status(unit) if(not UnitIsConnected(unit)) then return 'Offline' elseif(UnitIsGhost(unit)) then return 'Ghost' elseif(UnitIsDead(unit)) then return 'Dead' end end local function GetAuraCount(unit, id) local index = 1 repeat local _, _, _, count, _, _, _, _, _, _, spellID = UnitAura(unit, index, 'HELPFUL') if(spellID == id) then return count end index = index + 1 until(not spellID) end local events = { curhp = 'UNIT_HEALTH_FREQUENT UNIT_MAXHEALTH', defhp = 'UNIT_HEALTH_FREQUENT UNIT_MAXHEALTH', maxhp = 'UNIT_HEALTH_FREQUENT UNIT_MAXHEALTH', perhp = 'UNIT_HEALTH_FREQUENT UNIT_MAXHEALTH', pethp = 'UNIT_HEALTH_FREQUENT UNIT_MAXHEALTH UNIT_PET', targethp = 'UNIT_HEALTH_FREQUENT UNIT_MAXHEALTH', curpp = 'UNIT_POWER_FREQUENT UNIT_MAXPOWER', altpp = 'UNIT_POWER_FREQUENT UNIT_MAXPOWER UNIT_DISPLAYPOWER', ptype = 'UNIT_DISPLAYPOWER', leader = 'PARTY_LEADER_CHANGED', combo = 'UNIT_COMBO_POINTS PLAYER_TARGET_CHANGED', anticipation = 'UNIT_AURA', maelstrom = 'UNIT_AURA', cast = 'UNIT_SPELLCAST_START UNIT_SPELLCAST_STOP UNIT_SPELLCAST_CHANNEL_START UNIT_SPELLCAST_CHANNEL_STOP', name = 'UNIT_SPELLCAST_START UNIT_SPELLCAST_STOP UNIT_SPELLCAST_CHANNEL_START UNIT_SPELLCAST_CHANNEL_STOP UNIT_NAME_UPDATE UNIT_REACTION UNIT_FACTION UNIT_CLASSIFICATION_CHANGED', color = 'UNIT_REACTION UNIT_FACTION', status = 'UNIT_CONNECTION UNIT_HEALTH' } for tag, func in next, { curhp = function(unit) if(Status(unit)) then return end return Short(UnitHealth(unit)) end, defhp = function(unit) if(Status(unit)) then return end local cur = UnitHealth(unit) local max = UnitHealthMax(unit) if(cur ~= max) then return Short(max - cur) end end, maxhp = function(unit) if(Status(unit)) then return end local max = UnitHealthMax(unit) if(max == UnitHealth(unit)) then return Short(max) end end, perhp = function(unit) if(Status(unit)) then return end local cur = UnitHealth(unit) local max = UnitHealthMax(unit) if(cur ~= max) then return floor(cur / max * 100) end end, pethp = function(unit) if(UnitIsUnit(unit, 'vehicle')) then return end local cur = UnitHealth(unit) local max = UnitHealthMax(unit) if(cur > 0) then return format('%s%d%%|r', Hex(ColorGradient(cur, max, 1, 0, 0, 1, 1, 0, 1, 1, 1)), cur / max * 100) elseif(UnitIsDead(unit)) then return DEAD_TEXTURE end end, targethp = function(unit) if(Status(unit)) then return end local cur = UnitHealth(unit) local max = UnitHealthMax(unit) if(UnitCanAttack('player', unit)) then return format('(%d|cff0090ff%%|r)', cur / max * 100) elseif(cur ~= max) then return format('|cff0090ff/|r %s', Short(max)) end end, curpp = function(unit) if(Status(unit)) then return end local cur = UnitPower(unit) if(cur > 0) then return Short(cur) end end, altpp = function(unit) local cur = UnitPower(unit, 0) local max = UnitPowerMax(unit, 0) if(UnitPowerType(unit) ~= 0 and cur ~= max) then return floor(cur / max * 100) end end, ptype = function(unit) local _, type = UnitPowerType(unit) return Hex(_COLORS.power[type] or _COLORS.power.MANA) end, leader = function(unit) return UnitIsGroupLeader(unit) and '|cffffff00!|r' end, cast = function(unit) return UnitCastingInfo(unit) or UnitChannelInfo(unit) end, name = function(unit) local name, _, _, _, _, _, _, _, notInterruptible = UnitCastingInfo(unit) if(name) then local color = notInterruptible and 'ff9000' or 'ff0000' return format('|cff%s%s|r', color, name) end name, _, _, _, _, _, _, notInterruptible = UnitChannelInfo(unit) if(name) then local color = notInterruptible and 'ff9000' or 'ff0000' return format('|cff%s%s|r', color, name) end name = UnitName(unit) local color = _TAGS['p3lim:color'](unit) name = color and format('%s%s|r', color, name) or name local rare = _TAGS['rare'](unit) return rare and format('%s |cff0090ff%s|r', name, rare) or name end, combo = function(unit) if(not UnitExists('target')) then return end local points = GetComboPoints(unit, 'target') if(points == 5) then return format('|cffcc3333%d|r', points) elseif(points == 4) then return format('|cffff6600%d|r', points) elseif(points > 0) then return format('|cffffcc00%d|r', points) end end, anticipation = function(unit) return UnitExists('target') and GetAuraCount(unit, 115189) end, maelstrom = function(unit) return UnitExists('target') and GetAuraCount(unit, 53817) end, color = function(unit) local reaction = UnitReaction(unit, 'player') if((UnitIsTapped(unit) and not UnitIsTappedByPlayer(unit)) or not UnitIsConnected(unit)) then return '|cff999999' elseif(not UnitIsPlayer(unit) and reaction) then return Hex(_COLORS.reaction[reaction]) elseif(UnitFactionGroup(unit) and UnitIsEnemy(unit, 'player') and UnitIsPVP(unit)) then return '|cffff0000' end end, status = Status } do tagMethods['p3lim:' .. tag] = func tagEvents['p3lim:' .. tag] = events[tag] end -- Modified version of the tags element local events = {} local frame = CreateFrame('Frame') frame:SetScript('OnEvent', function(self, event, unit) local strings = events[event] if(strings) then for _, fs in next, strings do if(fs:IsVisible() and (tagSharedEvents[event] or fs.parent.unit == unit or fs.overrideUnit == unit)) then fs:UpdateTag() end end end end) local OnUpdates = {} local eventlessUnits = {} local function createOnUpdate(timer) local OnUpdate = OnUpdates[timer] if(not OnUpdate) then local total = timer local strings = eventlessUnits[timer] local frame = CreateFrame('Frame') frame:SetScript('OnUpdate', function(self, elapsed) if(total >= timer) then for _, fs in next, strings do if(fs.parent:IsShown() and (UnitExists(fs.parent.unit) or UnitExists(fs.overrideUnit))) then fs:UpdateTag() end end total = 0 end total = total + elapsed end) OnUpdates[timer] = frame end end local function OnShow(self) for _, fs in next, self.__tags do fs:UpdateTag() end end local function getTagName(tag) local s = (tag:match('>+()') or 2) local e = tag:match('.*()<+') e = (e and e - 1) or -2 return tag:sub(s, e), s, e end local function RegisterEvent(fs, event) if(not events[event]) then events[event] = {} end frame:RegisterEvent(event) table.insert(events[event], fs) end local _PATTERN = '%[..-%]+' local function RegisterEvents(fs, tagStr) for tag in tagStr:gmatch(_PATTERN) do tag = getTagName(tag) local events = tagEvents[tag] if(events) then for event in events:gmatch('%S+') do RegisterEvent(fs, event) end end end end local function UnregisterEvents(fs) for event, data in next, events do for key, fs2 in next, data do if(fs2 == fs) then if(#data == 1) then frame:UnregisterEvent(event) end table.remove(data, key) end end end end local tagPool = {} local funcPool = {} local tmp = {} local function Tag(self, fs, tagStr) if(not fs or not tagStr) then return end if(not self.__tags) then self.__tags = {} table.insert(self.__elements, OnShow) else -- Since people ignore everything that is good practice; -- Unregister the tag if it already exists. for _, tag in next, self.__tags do if(fs == tag) then -- We don't need to remove it from the __tags table as Untag -- handles that for us. self:Untag(fs) end end end fs.parent = self local func = tagPool[tagStr] if(not func) then local format, numTags = tagStr:gsub('%%', '%%%%'):gsub(_PATTERN, '%%s') local args = {} for bracket in tagStr:gmatch(_PATTERN) do local tagFunc = funcPool[bracket] or tagMethods[bracket:sub(2, -2)] if(not tagFunc) then local tagName, s, e = getTagName(bracket) local tag = tagMethods[tagName] if(tag) then s = s - 2 e = e + 2 if(s ~= 0 and e ~= 0) then local pre = bracket:sub(2, s) local ap = bracket:sub(e, -2) tagFunc = function(u, r) local str = tag(u, r) if(str) then return pre .. str .. ap end end elseif(s ~= 0) then local pre = bracket:sub(2, s) tagFunc = function(u, r) local str = tag(u, r) if(str) then return pre .. str end end elseif(e ~= 0) then local ap = bracket:sub(e, -2) tagFunc = function(u, r) local str = tag(u, r) if(str) then return str .. ap end end end funcPool[bracket] = tagFunc end end if(tagFunc) then table.insert(args, tagFunc) else return error(('Attempted to use invalid tag %s.'):format(bracket), 3) end end if(numTags == 1) then func = function(self) local parent = self.parent local unit = parent.unit local overrideUnit = self.overrideUnit -- _ENV._COLORS = parent.colors return self:SetFormattedText( format, args[1](overrideUnit or unit, overrideUnit and unit) or '' ) end elseif(numTags == 2) then func = function(self) local parent = self.parent local unit = parent.unit local overrideUnit = self.overrideUnit -- _ENV._COLORS = parent.colors return self:SetFormattedText( format, args[1](overrideUnit or unit, overrideUnit and unit) or '', args[2](overrideUnit or unit, overrideUnit and unit) or '' ) end elseif(numTags == 3) then func = function(self) local parent = self.parent local unit = parent.unit local overrideUnit = self.overrideUnit -- _ENV._COLORS = parent.colors return self:SetFormattedText( format, args[1](overrideUnit or unit, overrideUnit and unit) or '', args[2](overrideUnit or unit, overrideUnit and unit) or '', args[3](overrideUnit or unit, overrideUnit and unit) or '' ) end else func = function(self) local parent = self.parent local unit = parent.unit local overrideUnit = self.overrideUnit -- _ENV._COLORS = parent.colors for i, func in next, args do tmp[i] = func(overrideUnit or unit, overrideUnit and unit) or '' end -- We do 1, numTags because tmp can hold several unneeded variables. return self:SetFormattedText(format, unpack(tmp, 1, numTags)) end end tagPool[tagStr] = func end fs.UpdateTag = func local unit = self.unit if(self.__eventless or fs.frequentUpdates) then local timer if(type(fs.frequentUpdates) == 'number') then timer = fs.frequentUpdates else timer = 1/2 end if(not eventlessUnits[timer]) then eventlessUnits = {} end table.insert(eventlessUnits[timer], fs) createOnUpdate(timer) else RegisterEvents(fs, tagStr) end table.insert(self.__tags, fs) end local function Untag(self, fs) if(not fs) then return end UnregisterEvents(fs) for _, timers in next, eventlessUnits do for key, fs2 in next, timers do if(fs2 == fs) then table.remove(timers, key) end end end for key, fs2 in next, self.__tags do if(fs2 == fs) then table.remove(self.__tags, key) end end fs.UpdateTag = nil end oUF:RegisterMetaFunction('CustomTag', Tag) oUF:RegisterMetaFunction('CustomUntag', Untag)