local E, L, V, P, G = unpack(ElvUI) local mod = E:GetModule("NamePlates") local LSM = LibStub("LibSharedMedia-3.0") local select, unpack, pairs = select, unpack, pairs local tonumber = tonumber local band = bit.band local gsub = string.gsub local tinsert, tremove, wipe = table.insert, table.remove, table.wipe local CreateFrame = CreateFrame local UnitAura = UnitAura local UnitGUID = UnitGUID local GetSpellTexture = GetSpellTexture local RAID_CLASS_COLORS = RAID_CLASS_COLORS local AURA_TYPE_BUFF = AURA_TYPE_BUFF local AURA_TYPE_DEBUFF = AURA_TYPE_DEBUFF local RaidIconBit = { ["STAR"] = "0x00100000", ["CIRCLE"] = "0x00200000", ["DIAMOND"] = "0x00400000", ["TRIANGLE"] = "0x00800000", ["MOON"] = "0x01000000", ["SQUARE"] = "0x02000000", ["CROSS"] = "0x04000000", ["SKULL"] = "0x08000000" } local RaidIconIndex = { "STAR", "CIRCLE", "DIAMOND", "TRIANGLE", "MOON", "SQUARE", "CROSS", "SKULL" } local ByRaidIcon = {} local ByName = {} local auraCache = {} local buffCache = {} local debuffCache = {} local auraList = {} local auraSpellID = {} local auraName = {} local auraExpiration = {} local auraStacks = {} local auraCaster = {} local auraDuration = {} local auraTexture = {} local auraType = {} local cachedAuraDurations = {} local TimeColors = { [0] = "|cffeeeeee", [1] = "|cffeeeeee", [2] = "|cffeeeeee", [3] = "|cffFFEE00", [4] = "|cfffe0000" } local AURA_UPDATE_INTERVAL = 0.1 local PolledHideIn do local Framelist = {} local Watcherframe = CreateFrame("Frame") local WatcherframeActive = false local timeToUpdate = 0 local function CheckFramelist() local curTime = GetTime() if curTime < timeToUpdate then return end local framecount = 0 timeToUpdate = curTime + AURA_UPDATE_INTERVAL for frame, expiration in pairs(Framelist) do if expiration < curTime then frame:Hide() Framelist[frame] = nil else if frame.Poll then frame:Poll(expiration) end framecount = framecount + 1 end end if framecount == 0 then Watcherframe:SetScript("OnUpdate", nil); WatcherframeActive = false end end function PolledHideIn(frame, expiration) if expiration == 0 then frame:Hide() Framelist[frame] = nil else Framelist[frame] = expiration frame:Show() if not WatcherframeActive then Watcherframe:SetScript("OnUpdate", CheckFramelist) WatcherframeActive = true end end end end local function GetSpellDuration(spellID) if spellID then return cachedAuraDurations[spellID] end end local function SetSpellDuration(spellID, duration) if spellID then cachedAuraDurations[spellID] = duration end end local function UpdateAuraTime(frame, expiration) local timeleft = expiration - GetTime() local timervalue, formatid = E:GetTimeInfo(timeleft, 4) local timeFormat = E.TimeFormats[3][2] if timervalue < 4 then timeFormat = E.TimeFormats[4][2] end frame.timeLeft:SetFormattedText(("%s%s|r"):format(TimeColors[formatid], timeFormat), timervalue) end local function RemoveAuraInstance(guid, spellID, caster) if guid and spellID and auraList[guid] then local instanceID = tostring(guid)..tostring(spellID)..(tostring(caster or "UNKNOWN_CASTER")) local auraID = spellID..(tostring(caster or "UNKNOWN_CASTER")) if auraList[guid][auraID] then auraSpellID[instanceID] = nil auraName[instanceID] = nil auraExpiration[instanceID] = nil auraStacks[instanceID] = nil auraCaster[instanceID] = nil auraDuration[instanceID] = nil auraTexture[instanceID] = nil auraType[instanceID] = nil auraList[guid][auraID] = nil end end end local function GetAuraList(guid) if guid and auraList[guid] then return auraList[guid] end end local function GetAuraInstance(guid, auraID) if guid and auraID then local instanceID = guid..auraID local spellID, name, expiration, stacks, caster, duration, texture, type spellID = auraSpellID[instanceID] name = auraName[instanceID] expiration = auraExpiration[instanceID] stacks = auraStacks[instanceID] caster = auraCaster[instanceID] duration = auraDuration[instanceID] texture = auraTexture[instanceID] type = auraType[instanceID] return spellID, name, expiration, stacks, caster, duration, texture, type end end local function SetAuraInstance(guid, name, spellID, expiration, stacks, caster, duration, texture, type) if guid and spellID and caster and texture then local auraID = spellID..(tostring(caster or "UNKNOWN_CASTER")) local instanceID = guid..auraID auraList[guid] = auraList[guid] or {} auraList[guid][auraID] = instanceID auraSpellID[instanceID] = spellID auraName[instanceID] = name auraExpiration[instanceID] = expiration auraStacks[instanceID] = stacks auraCaster[instanceID] = caster auraDuration[instanceID] = duration auraTexture[instanceID] = texture auraType[instanceID] = type end end local function WipeAuraList(guid) if guid and auraList[guid] then local unitAuraList = auraList[guid] for auraID, instanceID in pairs(unitAuraList) do auraSpellID[instanceID] = nil auraName[instanceID] = nil auraExpiration[instanceID] = nil auraStacks[instanceID] = nil auraCaster[instanceID] = nil auraDuration[instanceID] = nil auraTexture[instanceID] = nil auraType[instanceID] = nil unitAuraList[auraID] = nil end end end function mod:CleanAuraLists() local currentTime = GetTime() for guid, instanceList in pairs(auraList) do local auraCount = 0 for auraID, instanceID in pairs(instanceList) do local expiration = auraExpiration[instanceID] if expiration and expiration < currentTime then auraList[guid][auraID] = nil auraSpellID[instanceID] = nil auraName[instanceID] = nil auraExpiration[instanceID] = nil auraStacks[instanceID] = nil auraCaster[instanceID] = nil auraDuration[instanceID] = nil auraTexture[instanceID] = nil auraType[instanceID] = nil else auraCount = auraCount + 1 end end if auraCount == 0 then auraList[guid] = nil end end end function mod:SetAura(aura, icon, count, expirationTime) aura.icon:SetTexture(icon) if count > 1 then aura.count:SetText(count) else aura.count:SetText("") end aura:Show() PolledHideIn(aura, expirationTime) end function mod:HideAuraIcons(auras) for i = 1, getn(auras.icons) do PolledHideIn(auras.icons[i], 0) end end local currentAura = {} function mod:UpdateElement_Auras(frame) if not frame.HealthBar:IsShown() then return end local guid = frame.guid if not guid then if RAID_CLASS_COLORS[frame.UnitClass] then local name = gsub(frame.oldName:GetText(), "%s%(%*%)","") guid = ByName[name] elseif frame.RaidIcon:IsShown() then guid = ByRaidIcon[frame.RaidIconType] end if guid then frame.guid = guid else return end end local hasDebuffs = false local hasBuffs = false local numDebuff = 0 local numBuff = 0 local aurasOnUnit = GetAuraList(guid) debuffCache = wipe(debuffCache) buffCache = wipe(buffCache) if aurasOnUnit then local numAuras = 0 local aura for instanceid in pairs(aurasOnUnit) do numAuras = (numDebuff + numBuff) + 1 aura = wipe(currentAura[numAuras] or {}) aura.spellID, aura.name, aura.expirationTime, aura.count, aura.caster, aura.duration, aura.icon, aura.type = GetAuraInstance(guid, instanceid) local filter = false local db = self.db.units[frame.UnitType].buffs.filters if aura.type == AURA_TYPE_DEBUFF then db = self.db.units[frame.UnitType].debuffs.filters end if db.personal and aura.caster == UnitGUID("player") then filter = true end local trackFilter = E.global["unitframe"]["aurafilters"][db.filter] if db.filter and trackFilter then local spell = trackFilter.spells[tonumber(aura.spellID)] or trackFilter.spells[aura.name] if trackFilter.type == "Whitelist" then if spell and spell.enable then filter = true end elseif trackFilter.type == "Blacklist" and spell and spell.enable then filter = false end end if filter ~= true then numAuras = numAuras - 1 RemoveAuraInstance(guid, aura.spellID, aura.caster) wipe(aura) end if tonumber(aura.spellID) then aura.unit = frame.unit if aura.expirationTime > GetTime() then if aura.type == "BUFF" then numBuff = numBuff + 1 buffCache[numBuff] = aura else numDebuff = numDebuff + 1 debuffCache[numDebuff] = aura end end end end wipe(currentAura) end local frameNum = 1 local maxAuras = getn(frame.Debuffs.icons) local maxDuration = self.db.units[frame.UnitType].debuffs.filters.maxDuration self:HideAuraIcons(frame.Debuffs) if numDebuff > 0 and self.db.units[frame.UnitType].debuffs.enable then for index = 1, getn(debuffCache) do if frameNum > maxAuras then break end local debuff = debuffCache[index] if debuff.spellID and debuff.expirationTime and debuff.duration <= maxDuration then self:SetAura(frame.Debuffs.icons[frameNum], debuff.icon, debuff.count, debuff.expirationTime) frameNum = frameNum + 1 hasDebuffs = true end end end frameNum = 1 maxAuras = getn(frame.Buffs.icons) maxDuration = self.db.units[frame.UnitType].buffs.filters.maxDuration self:HideAuraIcons(frame.Buffs) if numBuff > 0 and self.db.units[frame.UnitType].buffs.enable then for index = 1, getn(buffCache) do if frameNum > maxAuras then break end local buff = buffCache[index] if buff.spellID and buff.expirationTime and buff.duration <= maxDuration then self:SetAura(frame.Buffs.icons[frameNum], buff.icon, buff.count, buff.expirationTime) frameNum = frameNum + 1 hasBuffs = true end end end debuffCache = wipe(debuffCache) buffCache = wipe(buffCache) local TopLevel = frame.HealthBar local TopOffset = ((self.db.units[frame.UnitType].showName and select(2, frame.Name:GetFont()) + 5) or 0) if hasDebuffs then TopOffset = TopOffset + 3 frame.Debuffs:SetPoint("BOTTOMLEFT", TopLevel, "TOPLEFT", 0, TopOffset) frame.Debuffs:SetPoint("BOTTOMRIGHT", TopLevel, "TOPRIGHT", 0, TopOffset) TopLevel = frame.Debuffs TopOffset = 3 end if hasBuffs then if not hasDebuffs then TopOffset = TopOffset + 3 end frame.Buffs:SetPoint("BOTTOMLEFT", TopLevel, "TOPLEFT", 0, TopOffset) frame.Buffs:SetPoint("BOTTOMRIGHT", TopLevel, "TOPRIGHT", 0, TopOffset) TopLevel = frame.Buffs TopOffset = 3 end if frame.TopLevelFrame ~= TopLevel then frame.TopLevelFrame = TopLevel frame.TopOffset = TopOffset end end function mod:UpdateElement_AurasByUnitID(unit) --[[local guid = UnitGUID(unit) WipeAuraList(guid) local index = 1 local name, _, texture, count, _, duration, expirationTime, unitCaster, _, _, spellID = UnitAura(unit, index, "HARMFUL") while name do SetSpellDuration(spellID, duration) SetAuraInstance(guid, name, spellID, expirationTime, count, UnitGUID(unitCaster or ""), duration, texture, AURA_TYPE_DEBUFF) index = index + 1 name , _, texture, count, _, duration, expirationTime, unitCaster, _, _, spellID = UnitAura(unit, index, "HARMFUL") end index = 1 local name, _, texture, count, _, duration, expirationTime, unitCaster, _, _, spellID = UnitAura(unit, index, "HELPFUL") while name do SetSpellDuration(spellID, duration) SetAuraInstance(guid, name, spellID, expirationTime, count, UnitGUID(unitCaster or ""), duration, texture, AURA_TYPE_BUFF) index = index + 1 name, _, texture, count, _, duration, expirationTime, unitCaster, _, _, spellID = UnitAura(unit, index, "HELPFUL") end local raidIcon, name if UnitPlayerControlled(unit) then name = UnitName(unit) end raidIcon = RaidIconIndex[GetRaidTargetIndex(unit) or ""] if raidIcon then ByRaidIcon[raidIcon] = guid end local frame = self:SearchForFrame(guid, raidIcon, name) if frame then self:UpdateElement_Auras(frame) end]] end function mod:COMBAT_LOG_EVENT_UNFILTERED(_, _, event, sourceGUID, _, _, destGUID, destName, destFlags, ...) if destGUID == UnitGUID("target") then return end --if band(destFlags, COMBATLOG_OBJECT_REACTION_FRIENDLY) ~= 0 then then return if not (event == "SPELL_AURA_APPLIED" or event == "SPELL_AURA_REFRESH" or event == "SPELL_AURA_APPLIED_DOSE" or event == "SPELL_AURA_REMOVED_DOSE" or event == "SPELL_AURA_BROKEN" or event == "SPELL_AURA_BROKEN_SPELL" or event == "SPELL_AURA_REMOVED") then return end --local spellID, spellName, _, auraType, stackCount = ... if event == "SPELL_AURA_APPLIED" or event == "SPELL_AURA_REFRESH" then local duration = GetSpellDuration(spellID) local texture = GetSpellTexture(spellID) SetAuraInstance(destGUID, spellName, spellID, GetTime() + (duration or 0), 1, sourceGUID, duration, texture, auraType) elseif event == "SPELL_AURA_APPLIED_DOSE" or event == "SPELL_AURA_REMOVED_DOSE" then local duration = GetSpellDuration(spellID) local texture = GetSpellTexture(spellID) SetAuraInstance(destGUID, spellName, spellID, GetTime() + (duration or 0), stackCount, sourceGUID, duration, texture, auraType) elseif event == "SPELL_AURA_BROKEN" or event == "SPELL_AURA_BROKEN_SPELL" or event == "SPELL_AURA_REMOVED" then RemoveAuraInstance(destGUID, spellID, sourceGUID) end local name, raidIcon if band(destFlags, COMBATLOG_OBJECT_CONTROL_PLAYER) > 0 then local rawName = strsplit("-", destName) ByName[rawName] = destGUID name = rawName end for iconName, bitmask in pairs(RaidIconBit) do if band(destFlags, bitmask) > 0 then ByRaidIcon[iconName] = destGUID raidIcon = iconName break end end local frame = self:SearchForFrame(destGUID, raidIcon, name) if frame then self:UpdateElement_Auras(frame) end end function mod:CreateAuraIcon(parent) local aura = CreateFrame("Frame", nil, parent) self:StyleFrame(aura, true) aura.icon = aura:CreateTexture(nil, "OVERLAY") aura.icon:SetAllPoints() aura.icon:SetTexCoord(unpack(E.TexCoords)) aura.timeLeft = aura:CreateFontString(nil, "OVERLAY") aura.timeLeft:SetPoint("TOPLEFT", 2, 2) aura.timeLeft:SetFont(LSM:Fetch("font", self.db.font), self.db.fontSize, self.db.fontOutline) aura.count = aura:CreateFontString(nil, "OVERLAY") aura.count:SetPoint("BOTTOMRIGHT", 0, 0) aura.count:SetFont(LSM:Fetch("font", self.db.font), self.db.fontSize, self.db.fontOutline) aura.Poll = parent.PollFunction return aura end function mod:Auras_SizeChanged(width) local numAuras = getn(self.icons) for i = 1, numAuras do self.icons[i]:SetWidth(((width - numAuras) / numAuras) - (E.private.general.pixelPerfect and 0 or 3)) self.icons[i]:SetHeight((self.db.baseHeight or 18) * (self:GetParent().HealthBar.currentScale or 1)) end self:SetHeight((self.db.baseHeight or 18) * (self:GetParent().HealthBar.currentScale or 1)) end function mod:UpdateAuraIcons(auras) local maxAuras = auras.db.numAuras local numCurrentAuras = getn(auras.icons) if(numCurrentAuras > maxAuras) then for i = maxAuras, numCurrentAuras do tinsert(auraCache, auras.icons[i]) auras.icons[i]:Hide() auras.icons[i] = nil end end if(numCurrentAuras ~= maxAuras) then self.Auras_SizeChanged(auras, auras:GetWidth(), auras:GetHeight()) end for i = 1, maxAuras do auras.icons[i] = auras.icons[i] or tremove(auraCache) or mod:CreateAuraIcon(auras) auras.icons[i]:SetParent(auras) auras.icons[i]:ClearAllPoints() auras.icons[i]:Hide() auras.icons[i]:SetHeight(auras.db.baseHeight or 18) if(auras.side == "LEFT") then if(i == 1) then auras.icons[i]:SetPoint("BOTTOMLEFT", auras, "BOTTOMLEFT") else auras.icons[i]:SetPoint("LEFT", auras.icons[i-1], "RIGHT", E.Border + E.Spacing*3, 0) end else if(i == 1) then auras.icons[i]:SetPoint("BOTTOMRIGHT", auras, "BOTTOMRIGHT") else auras.icons[i]:SetPoint("RIGHT", auras.icons[i-1], "LEFT", -(E.Border + E.Spacing*3), 0) end end end end function mod:ConstructElement_Auras(frame, side) local auras = CreateFrame("Frame", nil, frame) auras:SetScript("OnSizeChanged", mod.Auras_SizeChanged) auras:SetHeight(18) auras.side = side auras.PollFunction = UpdateAuraTime auras.icons = {} return auras end