Files
moon-well-ui/data/addon/ForeverUI/TargetFrameAuras.lua
T
2026-10-01 13:38:02 +02:00

245 lines
8.3 KiB
Lua

-- Auras under the target frame, as camelot's TargetFrameAuraContainer lays them out
-- (shared/targetframeauracontainer.lua, targetframeaurashared.lua, targetframeaurabutton.xml)
-- and TargetFrameMixin anchors them (mainline/targetframe.lua, loaded by camelot's family):
-- rows of 122 from the frame art's BOTTOMLEFT (5, 9), 3 apart; the first two rows 101 wide
-- while the target of target shows. Hostile target: debuffs first, then buffs on a new row;
-- friendly target: the reverse. Auras from the player, pet or vehicle are 21, others 17.
-- 3.3.5 gives the caster as a unit token only: an aura from a player outside the group has
-- none, so camelot's "from a player or a player's pet" test only knows the casters it can see.
local ForeverUI = ForeverUI or {}
_G.ForeverUI = ForeverUI
local MAX_BUFFS, MAX_DEBUFFS = 32, 16
local SMALL_SIZE, LARGE_SIZE = 17, 21
local SPACING = 3 -- between auras, and between rows
local LINE_SIZE = 122
local TOT_LINE_SIZE, TOT_LINES = 101, 2 -- rows beside the target of target
local START_X, START_Y = 5, 9 -- from the frame art's BOTTOMLEFT
-- Buff border when the buff can be stolen (the target is not the player); debuff border in
-- the dispel type's color, 1 px around the icon.
local STEALABLE_BORDER = "Interface\\TargetingFrame\\UI-TargetingFrame-Stealable"
local STEALABLE_SIZE = 24
local DEBUFF_BORDER = "Interface\\Buttons\\UI-Debuff-Overlays"
local PLAYER_UNITS = { "player", "vehicle", "pet" }
local frame = ForeverUI.TargetFrame
local targetOfTarget = _G["ForeverUITargetOfTarget"]
local container = CreateFrame("Frame", "ForeverUITargetFrameAuras", frame)
container:SetWidth(1)
container:SetHeight(1)
container:SetPoint("TOPLEFT", frame.art, "BOTTOMLEFT", START_X, START_Y)
local buttons = { HELPFUL = {}, HARMFUL = {} }
-- ---------------------------------------------------------------- Rules
-- The player, its vehicle or its pet cast it (camelot ShouldShowAuraWithLargeSize)
local function fromPlayer(caster)
if not caster then
return false
end
for _, token in ipairs(PLAYER_UNITS) do
if UnitIsUnit(caster, token) then
return true
end
end
return false
end
-- camelot ShouldShowAuraAsDebuff: on a hostile creature, debuffs from other players are left out
local function keepDebuff(caster, friendly)
if fromPlayer(caster) or UnitIsUnit("player", "target") then
return true
end
local targetIsPlayer = UnitIsPlayer("target")
local targetIsPet = UnitPlayerControlled("target") and not targetIsPlayer
if not targetIsPlayer and not targetIsPet and not friendly and caster and UnitPlayerControlled(caster) then
return false
end
return true
end
-- Auras of one kind, in client order; filter: "HELPFUL" or "HARMFUL"; maximum: how many
-- at most; friendly: the target's reaction
local function collect(filter, maximum, friendly)
local list = {}
local index = 1
while #list < maximum do
local name, _, icon, count, dispelType, duration, expiration, caster, stealable = UnitAura("target", index, filter)
if not name then
break
end
if filter == "HELPFUL" or keepDebuff(caster, friendly) then
list[#list + 1] = {
index = index, icon = icon, count = count, dispelType = dispelType,
duration = duration, expiration = expiration, stealable = stealable,
size = fromPlayer(caster) and LARGE_SIZE or SMALL_SIZE,
}
end
index = index + 1
end
return list
end
-- ---------------------------------------------------------------- Buttons
local function createButton(filter, rank)
local kind = filter == "HELPFUL" and "Buff" or "Debuff"
local button = CreateFrame("Button", container:GetName() .. kind .. rank, container)
button.filter = filter
local icon = button:CreateTexture(nil, "BACKGROUND")
icon:SetAllPoints(button)
button.icon = icon
local cooldown = CreateFrame("Cooldown", button:GetName() .. "Cooldown", button)
cooldown:SetPoint("TOPLEFT", button, "TOPLEFT", 0, -1)
cooldown:SetPoint("BOTTOMRIGHT", button, "BOTTOMRIGHT", 0, -1)
cooldown:SetReverse(true)
if cooldown.SetDrawEdge then
cooldown:SetDrawEdge(true)
end
button.cooldown = cooldown
-- The cooldown is a child frame, drawn above the button: the count and the borders go
-- in a frame above it
local overlay = CreateFrame("Frame", nil, button)
overlay:SetAllPoints(button)
overlay:SetFrameLevel(cooldown:GetFrameLevel() + 1)
local count = overlay:CreateFontString(nil, "ARTWORK", "NumberFontNormalSmall")
count:SetJustifyH("RIGHT")
count:SetPoint("BOTTOMRIGHT", button, "BOTTOMRIGHT", 1, 0)
button.count = count
local border = overlay:CreateTexture(nil, "OVERLAY")
if filter == "HELPFUL" then
border:SetTexture(STEALABLE_BORDER)
border:SetBlendMode("ADD")
border:SetWidth(STEALABLE_SIZE)
border:SetHeight(STEALABLE_SIZE)
border:SetPoint("CENTER", button, "CENTER", 0, 0)
else
border:SetTexture(DEBUFF_BORDER)
border:SetTexCoord(0.296875, 0.5703125, 0, 0.515625)
border:SetPoint("TOPLEFT", button, "TOPLEFT", -1, 1)
border:SetPoint("BOTTOMRIGHT", button, "BOTTOMRIGHT", 1, -1)
end
button.border = border
button:SetScript("OnEnter", function(self)
GameTooltip:SetOwner(self, "ANCHOR_BOTTOMRIGHT", 15, -25)
if self.filter == "HELPFUL" then
GameTooltip:SetUnitBuff("target", self.index)
else
GameTooltip:SetUnitDebuff("target", self.index)
end
end)
button:SetScript("OnLeave", function()
GameTooltip:Hide()
end)
buttons[filter][rank] = button
return button
end
-- aura: an entry of collect; playerIsTarget: no stealable border on the player's own buffs
local function fill(button, aura, playerIsTarget)
button.index = aura.index
button.icon:SetTexture(aura.icon)
if aura.count and aura.count > 1 then
button.count:SetText(aura.count)
button.count:Show()
else
button.count:Hide()
end
if aura.duration and aura.duration > 0 and aura.expiration then
CooldownFrame_SetTimer(button.cooldown, aura.expiration - aura.duration, aura.duration, 1)
else
button.cooldown:Hide()
end
if button.filter == "HELPFUL" then
if aura.stealable and not playerIsTarget then
button.border:Show()
else
button.border:Hide()
end
else
local color = DebuffTypeColor[aura.dispelType or "none"] or DebuffTypeColor["none"]
button.border:SetVertexColor(color.r, color.g, color.b)
end
end
-- ---------------------------------------------------------------- Layout
-- Flow layout of camelot's container: each aura right of the previous one, a new row when
-- the next one would pass the row's width, the second group always on a new row.
local function layout()
local used = { HELPFUL = 0, HARMFUL = 0 }
if UnitExists("target") then
local friendly = UnitIsFriend("player", "target")
local playerIsTarget = UnitIsUnit("player", "target")
local buffs = { filter = "HELPFUL", list = collect("HELPFUL", MAX_BUFFS, friendly) }
local debuffs = { filter = "HARMFUL", list = collect("HARMFUL", MAX_DEBUFFS, friendly) }
local groups = friendly and { buffs, debuffs } or { debuffs, buffs }
local constrained = (targetOfTarget and targetOfTarget:IsShown()) and TOT_LINES or 0
local x, y, line, lineHeight = 0, 0, 1, 0
local function newLine()
y = y + lineHeight + SPACING
x, line, lineHeight = 0, line + 1, 0
end
for _, group in ipairs(groups) do
if #group.list > 0 and x > 0 then
newLine()
end
for rank, aura in ipairs(group.list) do
local limit = line <= constrained and TOT_LINE_SIZE or LINE_SIZE
if x > 0 and x + aura.size > limit then
newLine()
end
local button = buttons[group.filter][rank] or createButton(group.filter, rank)
button:ClearAllPoints()
button:SetPoint("TOPLEFT", container, "TOPLEFT", x, -y)
button:SetWidth(aura.size)
button:SetHeight(aura.size)
fill(button, aura, playerIsTarget)
button:Show()
x = x + aura.size + SPACING
lineHeight = math.max(lineHeight, aura.size)
end
used[group.filter] = #group.list
end
end
for filter, list in pairs(buttons) do
for rank = used[filter] + 1, #list do
list[rank]:Hide()
end
end
end
container:SetScript("OnEvent", function(_, event, unit)
if event ~= "UNIT_AURA" and event ~= "UNIT_FACTION" or unit == "target" then
layout()
end
end)
for _, event in ipairs({ "PLAYER_ENTERING_WORLD", "PLAYER_TARGET_CHANGED", "UNIT_AURA", "UNIT_FACTION" }) do
container:RegisterEvent(event)
end
-- The first rows narrow while the target of target shows
if targetOfTarget then
targetOfTarget:HookScript("OnShow", layout)
targetOfTarget:HookScript("OnHide", layout)
end