Files
ElvUIModernized/ElvUI/Modules/Auras/Auras.lua
T
2026-07-26 11:31:09 +02:00

505 lines
14 KiB
Lua

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 _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 getn, wipe, tinsert, tsort, tremove = table.getn, table.wipe, table.insert, table.sort, table.remove
--WoW API / Variables
local CreateFrame = CreateFrame
local GetInventoryItemQuality = GetInventoryItemQuality
local GetItemQualityColor = GetItemQualityColor
local GetWeaponEnchantInfo = GetWeaponEnchantInfo
local GetInventoryItemTexture = GetInventoryItemTexture
local GetPlayerBuff = GetPlayerBuff
local GetPlayerBuffTexture = GetPlayerBuffTexture
local GetPlayerBuffApplications = GetPlayerBuffApplications
local GetPlayerBuffDispelType = GetPlayerBuffDispelType
local GetPlayerBuffTimeLeft = GetPlayerBuffTimeLeft
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(self)
if not self:IsVisible() then return end
GameTooltip:SetOwner(self, "ANCHOR_BOTTOMLEFT", -5, -5)
self:UpdateTooltip()
end
local function OnLeave()
GameTooltip:Hide()
end
local function OnClick(self)
if self.index and self.index >= 0 then
CancelPlayerBuff(self.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", function() OnEnter(this) end)
button:SetScript("OnLeave", function() OnLeave() end)
button:SetScript("OnClick", function() OnClick(this) end)
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 ""] or DebuffTypeColor.none
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) + 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
local i, aura, buffIndex, icon = 0
while true do
buffIndex = GetPlayerBuff(i, filter)
icon = GetPlayerBuffTexture(buffIndex)
if not icon then break end
aura = freshTable()
aura.count, aura.dispelType, aura.expires = GetPlayerBuffApplications(buffIndex), GetPlayerBuffDispelType(buffIndex), GetPlayerBuffTimeLeft(buffIndex)
aura.icon = icon
aura.index = buffIndex
aura.filter = filter
tinsert(sortingTable, aura)
i = i + 1
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(tremove(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)