local E, L, V, P, G = unpack(ElvUI); --Import: Engine, Locales, PrivateDB, ProfileDB, GlobalDB local A = E:NewModule("Auras", "AceEvent-3.0"); local LSM = LibStub("LibSharedMedia-3.0"); --Cache global variables --Lua functions local GetTime = GetTime local _G = _G local unpack, select, pairs, ipairs = unpack, select, pairs, ipairs local floor, min, max, huge = math.floor, math.min, math.max, math.huge local format = string.format local wipe, tinsert, tsort, tremove = table.wipe, table.insert, table.sort, table.remove --WoW API / Variables local CreateFrame = CreateFrame local UnitAura = UnitAura local CancelItemTempEnchantment = CancelItemTempEnchantment local CancelUnitBuff = CancelUnitBuff local GetInventoryItemQuality = GetInventoryItemQuality local GetItemQualityColor = GetItemQualityColor local GetWeaponEnchantInfo = GetWeaponEnchantInfo local GetInventoryItemTexture = GetInventoryItemTexture local DIRECTION_TO_POINT = { DOWN_RIGHT = "TOPLEFT", DOWN_LEFT = "TOPRIGHT", UP_RIGHT = "BOTTOMLEFT", UP_LEFT = "BOTTOMRIGHT", RIGHT_DOWN = "TOPLEFT", RIGHT_UP = "BOTTOMLEFT", LEFT_DOWN = "TOPRIGHT", LEFT_UP = "BOTTOMRIGHT" } local DIRECTION_TO_HORIZONTAL_SPACING_MULTIPLIER = { DOWN_RIGHT = 1, DOWN_LEFT = -1, UP_RIGHT = 1, UP_LEFT = -1, RIGHT_DOWN = 1, RIGHT_UP = 1, LEFT_DOWN = -1, LEFT_UP = -1 } local DIRECTION_TO_VERTICAL_SPACING_MULTIPLIER = { DOWN_RIGHT = -1, DOWN_LEFT = -1, UP_RIGHT = 1, UP_LEFT = 1, RIGHT_DOWN = -1, RIGHT_UP = 1, LEFT_DOWN = -1, LEFT_UP = 1 } local IS_HORIZONTAL_GROWTH = { RIGHT_DOWN = true, RIGHT_UP = true, LEFT_DOWN = true, LEFT_UP = true } function A:UpdateTime(elapsed) if self.offset then local expiration = select(self.offset, GetWeaponEnchantInfo()) if expiration then self.timeLeft = expiration / 1e3 else self.timeLeft = 0 end else self.timeLeft = GetPlayerBuffTimeLeft(self.index) end if self.nextUpdate > 0 then self.nextUpdate = not self.offset and self.nextUpdate - elapsed or 1 return end local timerValue, formatID timerValue, formatID, self.nextUpdate = E:GetTimeInfo(self.timeLeft, A.db.fadeThreshold) self.time:SetText(format("%s%s|r", E.TimeColors[formatID], format(E.TimeFormats[formatID][2], timerValue))) if self.timeLeft > E.db.auras.fadeThreshold then -- E:StopFlash(self) else -- E:Flash(self, 1) end end local function UpdateTooltip() if this.offset then GameTooltip:SetInventoryItem("player", this.offset == 2 and 16 or 17) else GameTooltip:SetPlayerBuff(this.index) end end local function OnEnter() if not this:IsVisible() then return end GameTooltip:SetOwner(this, "ANCHOR_BOTTOMLEFT", -5, -5) this:UpdateTooltip() end local function OnLeave() GameTooltip:Hide() end local function OnClick() if this.index and this.index > 0 then CancelPlayerBuff(this.index) end end function A:CreateIcon(button) local font = LSM:Fetch("font", self.db.font) button:RegisterForClicks("RightButtonUp") button.texture = button:CreateTexture(nil, "BORDER") E:SetInside(button.texture) button.texture:SetTexCoord(unpack(E.TexCoords)) button.count = button:CreateFontString(nil, "ARTWORK") button.count:SetPoint("BOTTOMRIGHT", -1 + self.db.countXOffset, 1 + self.db.countYOffset) E:FontTemplate(button.count, font, self.db.fontSize, self.db.fontOutline) button.time = button:CreateFontString(nil, "ARTWORK") button.time:SetPoint("TOP", button, "BOTTOM", 1 + self.db.timeXOffset, 0 + self.db.timeYOffset) E:FontTemplate(button.time, font, self.db.fontSize, self.db.fontOutline) button.highlight = button:CreateTexture(nil, "HIGHLIGHT") button.highlight:SetTexture(1, 1, 1, 0.45) E:SetInside(button.highlight) button.UpdateTooltip = UpdateTooltip button:SetScript("OnEnter", OnEnter) button:SetScript("OnLeave", OnLeave) button:SetScript("OnClick", OnClick) E:SetTemplate(button, "Default") end local enchantableSlots = { [1] = 16, [2] = 17 } local buttons = {} function A:ConfigureAuras(header, auraTable, weaponPosition) local headerName = header:GetName() local db = self.db.debuffs if header.filter == "HELPFUL" then db = self.db.buffs end local size = db.size local point = DIRECTION_TO_POINT[db.growthDirection] local xOffset = 0 local yOffset = 0 local wrapXOffset = 0 local wrapYOffset = 0 local wrapAfter = db.wrapAfter local maxWraps = db.maxWraps local minWidth = 0 local minHeight = 0 if IS_HORIZONTAL_GROWTH[db.growthDirection] then minWidth = ((wrapAfter == 1 and 0 or db.horizontalSpacing) + size) * wrapAfter minHeight = (db.verticalSpacing + size) * maxWraps xOffset = DIRECTION_TO_HORIZONTAL_SPACING_MULTIPLIER[db.growthDirection] * (db.horizontalSpacing + size) yOffset = 0 wrapXOffset = 0 wrapYOffset = DIRECTION_TO_VERTICAL_SPACING_MULTIPLIER[db.growthDirection] * (db.verticalSpacing + size) else minWidth = (db.horizontalSpacing + size) * maxWraps minHeight = ((wrapAfter == 1 and 0 or db.verticalSpacing) + size) * wrapAfter xOffset = 0 yOffset = DIRECTION_TO_VERTICAL_SPACING_MULTIPLIER[db.growthDirection] * (db.verticalSpacing + size) wrapXOffset = DIRECTION_TO_HORIZONTAL_SPACING_MULTIPLIER[db.growthDirection] * (db.horizontalSpacing + size) wrapYOffset = 0 end wipe(buttons) local button local numWeapon = 0 if weaponPosition then local hasMainHandEnchant, mainHandExpiration, _, hasOffHandEnchant, offHandExpiration = GetWeaponEnchantInfo() for weapon = 2, 1, -1 do button = _G["ElvUIPlayerBuffsTempEnchant"..weapon] if select(weapon, hasMainHandEnchant, hasOffHandEnchant) then numWeapon = numWeapon + 1 if not button then button = CreateFrame("Button", "$parentTempEnchant"..weapon, header) self:CreateIcon(button) end if button then if button:IsShown() then button:Hide() end local index = enchantableSlots[weapon] local quality = GetInventoryItemQuality("player", index) button.texture:SetTexture(GetInventoryItemTexture("player", index)) if quality then button:SetBackdropBorderColor(GetItemQualityColor(quality)) end local expirationTime = select(weapon, mainHandExpiration, offHandExpiration) if expirationTime then button.offset = select(weapon, 2, 5) button:SetScript("OnUpdate", function() self.UpdateTime(this, arg1) end) button.nextUpdate = -1 A.UpdateTime(button, 0) else button.timeLeft = nil button.offset = nil button:SetScript("OnUpdate", nil) button.time:SetText("") end buttons[weapon] = button end else if button and type(button.Hide) == "function" then button.offset = nil button:Hide() end end end end for i = 1, getn(auraTable) do button = _G[headerName.."AuraButton"..i] if button then if button:IsShown() then button:Hide() end else button = CreateFrame("Button", "$parentAuraButton"..i, header) self:CreateIcon(button) end local buffInfo = auraTable[i] button.index = buffInfo.index if buffInfo.expires and buffInfo.expires > 0 then local timeLeft = buffInfo.expires if not button.timeLeft then button.timeLeft = timeLeft button:SetScript("OnUpdate", function() self.UpdateTime(this, arg1) end) else button.timeLeft = timeLeft end button.nextUpdate = -1 self.UpdateTime(button, 0) else button.timeLeft = nil button.time:SetText("") button:SetScript("OnUpdate", nil) end if buffInfo.count > 1 then button.count:SetText(buffInfo.count) else button.count:SetText("") end if buffInfo.filter == "HARMFUL" then local color = DebuffTypeColor[buffInfo.dispelType or ""] button:SetBackdropBorderColor(color.r, color.g, color.b) else button:SetBackdropBorderColor(unpack(E.media.bordercolor)) end button.texture:SetTexture(buffInfo.icon) buttons[i+numWeapon] = button end local display = getn(buttons) if wrapAfter and maxWraps then display = min(display, wrapAfter * maxWraps) end local left, right, top, bottom = huge, -huge, -huge, huge for index = 1, display do button = buttons[index] local tick, cycle = floor(mod((index - 1), wrapAfter)), floor((index - 1) / wrapAfter) button:ClearAllPoints() button:SetPoint(point, header, cycle * wrapXOffset + tick * xOffset, cycle * wrapYOffset + tick * yOffset) button:SetWidth(size) button:SetHeight(size) if button.time then local font = LSM:Fetch("font", self.db.font) button.time:ClearAllPoints() button.time:SetPoint("TOP", button, "BOTTOM", 1 + self.db.timeXOffset, 0 + self.db.timeYOffset) E:FontTemplate(button.time, font, self.db.fontSize, self.db.fontOutline) button.count:ClearAllPoints() button.count:SetPoint("BOTTOMRIGHT", -1 + self.db.countXOffset, 0 + self.db.countYOffset) E:FontTemplate(button.count, font, self.db.fontSize, self.db.fontOutline) end button:Show() left = min(left, button:GetLeft() or huge) right = max(right, button:GetRight() or -huge) top = max(top, button:GetTop() or -huge) bottom = min(bottom, button:GetBottom() or huge) end local deadIndex = (getn(auraTable) + numWeapon) + 1 button = _G[headerName.."AuraButton"..deadIndex] while button do if button:IsShown() then button:Hide() end deadIndex = deadIndex + 1 button = _G[headerName.."AuraButton"..deadIndex] end if display >= 1 then header:SetWidth(max(right - left, minWidth)) header:SetHeight(max(top - bottom, minHeight)) else header:SetWidth(minWidth) header:SetHeight(minHeight) end end local freshTable local releaseTable do local tableReserve = {} freshTable = function () local t = next(tableReserve) or {} tableReserve[t] = nil return t end releaseTable = function (t) tableReserve[t] = wipe(t) end end local function sortFactory(key, separateOwn, reverse) if separateOwn ~= 0 then if reverse then return function(a, b) if a.filter == b.filter then local ownA, ownB = a.caster == "player", b.caster == "player" if ownA ~= ownB then return ownA == (separateOwn > 0) end return a[key] > b[key] else return a.filter < b.filter end end; else return function(a, b) if a.filter == b.filter then local ownA, ownB = a.caster == "player", b.caster == "player" if ownA ~= ownB then return ownA == (separateOwn > 0) end return a[key] < b[key] else return a.filter < b.filter end end; end else if reverse then return function(a, b) if a.filter == b.filter then return a[key] > b[key] else return a.filter < b.filter end end; else return function(a, b) if a.filter == b.filter then return a[key] < b[key] else return a.filter < b.filter end end; end end end local sorters = {} for _, key in ipairs{"index", "expires"} do local label = string.upper(key) sorters[label] = {} for bool in pairs{[true] = true, [false] = false} do sorters[label][bool] = {} for sep = -1, 1 do sorters[label][bool][sep] = sortFactory(key, sep, bool) end end end sorters.TIME = sorters.EXPIRES local sortingTable = {} function A:UpdateHeader(header) local filter = header.filter local db = self.db.debuffs wipe(sortingTable) local weaponPosition if filter == "HELPFUL" then db = self.db.buffs weaponPosition = 1 end for i = 0, 23 do local aura, _ = freshTable() aura.index, aura.untilCancelled = GetPlayerBuff(i, filter) if aura.index < 0 then releaseTable(aura) else aura.icon, aura.count, aura.dispelType, aura.expires = GetPlayerBuffTexture(aura.index), GetPlayerBuffApplications(aura.index), GetPlayerBuffDispelType(aura.index), GetPlayerBuffTimeLeft(aura.index) aura.filter = filter sortingTable[i+1] = aura end end local sortMethod = (sorters[db.sortMethod] or sorters["INDEX"])[db.sortDir == "-"][db.seperateOwn] tsort(sortingTable, sortMethod) self:ConfigureAuras(header, sortingTable, weaponPosition) while sortingTable[1] do releaseTable(wipe(sortingTable)) end end function A:CreateAuraHeader(filter) local name = "ElvUIPlayerDebuffs" if filter == "HELPFUL" then name = "ElvUIPlayerBuffs" end local header = CreateFrame("Frame", name, UIParent) header:SetClampedToScreen(true) header.filter = filter header:RegisterEvent("PLAYER_AURAS_CHANGED") header:SetScript("OnEvent", function() A:UpdateHeader(this) end) self:UpdateHeader(header) return header end function A:Initialize() if self.db then return end if E.private.auras.disableBlizzard then E:Kill(BuffFrame) E:Kill(TemporaryEnchantFrame) end if not E.private.auras.enable then return end self.db = E.db.auras self.BuffFrame = self:CreateAuraHeader("HELPFUL") self.BuffFrame:SetPoint("TOPRIGHT", MMHolder, "TOPLEFT", -(6 + E.Border), -E.Border - E.Spacing) E:CreateMover(self.BuffFrame, "BuffsMover", L["Player Buffs"]) self.BuffFrame.GetUpdateWeaponEnchant = function(self) local hasMainHandEnchant, _, _, hasOffHandEnchant = GetWeaponEnchantInfo() if hasMainHandEnchant and not self.hasMainHandEnchant then self.hasMainHandEnchant = true return true elseif hasOffHandEnchant and not self.hasOffHandEnchant then self.hasOffHandEnchant = true return true elseif self.hasMainHandEnchant and not hasMainHandEnchant then self.hasMainHandEnchant = false return true elseif self.hasOffHandEnchant and not hasOffHandEnchant then self.hasOffHandEnchant = false return true end end self.BuffFrame:SetScript("OnUpdate", function() if this:GetUpdateWeaponEnchant() then A:UpdateHeader(this) end end) self.DebuffFrame = self:CreateAuraHeader("HARMFUL") self.DebuffFrame:SetPoint("BOTTOMRIGHT", MMHolder, "BOTTOMLEFT", -(6 + E.Border), E.Border + E.Spacing) E:CreateMover(self.DebuffFrame, "DebuffsMover", L["Player Debuffs"]) end local function InitializeCallback() A:Initialize() end E:RegisterModule(A:GetName(), InitializeCallback)