59d261ee0e
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
176 lines
6.2 KiB
Lua
176 lines
6.2 KiB
Lua
-- Active totems, as camelot's TotemFrame (mainline/totemframe.lua and .xml, loaded by
|
|
-- camelot's family): one 37 button per active totem slot, in the shaman's order (earth, fire,
|
|
-- water, air) or slot order for other classes, side by side 6 px overlapped, centered where
|
|
-- camelot stacks it under the player frame. Each button: the icon, round, 22 in the totem
|
|
-- ring (30), the time left under it, and a reverse sweep showing what remains.
|
|
-- Movable in edit mode; until the player moves it, it follows the player frame.
|
|
-- 3.3.5 has no texture masks: SetPortraitToTexture rounds the icon. The sweep stays the
|
|
-- client's square, 21 wide so its corners stay under the ring.
|
|
|
|
local ForeverUI = ForeverUI or {}
|
|
_G.ForeverUI = ForeverUI
|
|
local L = ForeverUI.L
|
|
local Layout = ForeverUI.Layout
|
|
|
|
local BUTTON_SIZE = 37
|
|
local SPACING = -6
|
|
local ICON_SIZE = 22
|
|
local SWEEP_SIZE = 21
|
|
local RING_SIZE = 30
|
|
local RING_X, RING_Y = 1, -1.5
|
|
local DURATION_Y = 5 -- the text's top, 5 above the button's bottom
|
|
local SLOTS = MAX_TOTEMS or 4
|
|
local FRAME_WIDTH = SLOTS * BUTTON_SIZE + (SLOTS - 1) * SPACING
|
|
|
|
-- Where camelot's PlayerBottomManagedFrameContainer puts it: the container's top at the
|
|
-- player frame's bottom (30, 25), half the frame's 15 left padding added, its -2 top padding
|
|
-- taken off; under the class resources when the player has some (the death knight's runes).
|
|
local OFFSET_X, OFFSET_Y = 30 + 7.5, 25 + 2
|
|
local UNDER_RESOURCES_X, UNDER_RESOURCES_Y = 7.5, 2
|
|
|
|
-- Default before the player frame is placed: its default (10, -10), 232 x 100
|
|
local DEFAULT_X, DEFAULT_Y = 10 + 232 / 2 + OFFSET_X, -10 - 100 + OFFSET_Y
|
|
|
|
local SHAMAN_ORDER = { EARTH_TOTEM_SLOT or 2, FIRE_TOTEM_SLOT or 1, WATER_TOTEM_SLOT or 3, AIR_TOTEM_SLOT or 4 }
|
|
local STANDARD_ORDER = { 1, 2, 3, 4 }
|
|
local WARNING_TIME = 90 -- BUFF_DURATION_WARNING_TIME
|
|
|
|
local frame = CreateFrame("Frame", "ForeverUITotemFrame", UIParent)
|
|
frame:SetWidth(FRAME_WIDTH)
|
|
frame:SetHeight(BUTTON_SIZE)
|
|
frame:Hide()
|
|
|
|
local buttons = {}
|
|
|
|
-- AuraButtonMixin:UpdateDuration: the buff duration setting decides; white under 90 s
|
|
local function updateDuration(button)
|
|
local timeLeft = GetTotemTimeLeft(button.slot)
|
|
if timeLeft and SHOW_BUFF_DURATIONS == "1" then
|
|
timeLeft = math.ceil(timeLeft)
|
|
button.duration:SetFormattedText(SecondsToTimeAbbrev(timeLeft))
|
|
local color = timeLeft < WARNING_TIME and HIGHLIGHT_FONT_COLOR or NORMAL_FONT_COLOR
|
|
button.duration:SetVertexColor(color.r, color.g, color.b)
|
|
button.duration:Show()
|
|
else
|
|
button.duration:Hide()
|
|
end
|
|
end
|
|
|
|
local function createButton(rank)
|
|
local button = CreateFrame("Frame", "ForeverUITotemFrameTotem" .. rank, frame)
|
|
button:SetWidth(BUTTON_SIZE)
|
|
button:SetHeight(BUTTON_SIZE)
|
|
|
|
local icon = button:CreateTexture(nil, "BACKGROUND")
|
|
icon:SetWidth(ICON_SIZE)
|
|
icon:SetHeight(ICON_SIZE)
|
|
icon:SetPoint("CENTER", button, "CENTER", 0, 0)
|
|
button.icon = icon
|
|
|
|
local cooldown = CreateFrame("Cooldown", button:GetName() .. "Cooldown", button)
|
|
cooldown:SetWidth(SWEEP_SIZE)
|
|
cooldown:SetHeight(SWEEP_SIZE)
|
|
cooldown:SetPoint("CENTER", button, "CENTER", 0, 0)
|
|
cooldown:SetReverse(true)
|
|
button.cooldown = cooldown
|
|
|
|
-- The ring above the sweep, which is a child frame
|
|
local ringHolder = CreateFrame("Frame", nil, button)
|
|
ringHolder:SetAllPoints(button)
|
|
ringHolder:SetFrameLevel(cooldown:GetFrameLevel() + 1)
|
|
local ring = ringHolder:CreateTexture(nil, "OVERLAY")
|
|
ForeverUI.SetAtlas(ring, "ui-hud-unitframe-totemframe", true)
|
|
ring:SetWidth(RING_SIZE)
|
|
ring:SetHeight(RING_SIZE)
|
|
ring:SetPoint("CENTER", button, "CENTER", RING_X, RING_Y)
|
|
|
|
local duration = button:CreateFontString(nil, "BACKGROUND", "GameFontNormalSmall")
|
|
duration:SetPoint("TOP", button, "BOTTOM", 0, DURATION_Y)
|
|
button.duration = duration
|
|
|
|
button:SetScript("OnUpdate", updateDuration)
|
|
button:Hide()
|
|
buttons[rank] = button
|
|
return button
|
|
end
|
|
|
|
for rank = 1, SLOTS do
|
|
createButton(rank)
|
|
end
|
|
|
|
-- Fills one button per active slot, in order, centered in the frame. Shown with a totem, or
|
|
-- empty while the layout is edited so that it can be moved.
|
|
local function update()
|
|
local _, class = UnitClass("player")
|
|
local order = class == "SHAMAN" and SHAMAN_ORDER or STANDARD_ORDER
|
|
local count = 0
|
|
for _, slot in ipairs(order) do
|
|
local haveTotem, _, startTime, duration, icon = GetTotemInfo(slot)
|
|
if haveTotem and duration and duration > 0 then
|
|
count = count + 1
|
|
local button = buttons[count]
|
|
button.slot = slot
|
|
SetPortraitToTexture(button.icon, icon)
|
|
CooldownFrame_SetTimer(button.cooldown, startTime, duration, 1)
|
|
updateDuration(button)
|
|
end
|
|
end
|
|
|
|
local width = count * BUTTON_SIZE + math.max(0, count - 1) * SPACING
|
|
for rank, button in ipairs(buttons) do
|
|
if rank <= count then
|
|
button:ClearAllPoints()
|
|
button:SetPoint("TOPLEFT", frame, "TOPLEFT",
|
|
(FRAME_WIDTH - width) / 2 + (rank - 1) * (BUTTON_SIZE + SPACING), 0)
|
|
button:Show()
|
|
else
|
|
button:Hide()
|
|
end
|
|
end
|
|
|
|
if count > 0 or Layout.editing then
|
|
frame:Show()
|
|
else
|
|
frame:Hide()
|
|
end
|
|
end
|
|
|
|
-- Default position under the player frame, or under its class resources; it changes with the
|
|
-- player frame until the player moves the totems (SetDefaults leaves a moved element alone).
|
|
local function follow()
|
|
local resources = _G["ForeverUIClassResourceContainer"]
|
|
local anchor, x, y = ForeverUI.PlayerFrame, OFFSET_X, OFFSET_Y
|
|
if resources then
|
|
anchor, x, y = resources, UNDER_RESOURCES_X, UNDER_RESOURCES_Y
|
|
end
|
|
local left, right, bottom = anchor:GetLeft(), anchor:GetRight(), anchor:GetBottom()
|
|
if not (left and right and bottom) then
|
|
return
|
|
end
|
|
local scale = anchor:GetEffectiveScale() / UIParent:GetEffectiveScale()
|
|
Layout.SetDefaults("totems", "TOP", "BOTTOMLEFT",
|
|
((left + right) / 2 + x) * scale, (bottom + y) * scale)
|
|
end
|
|
|
|
Layout.Register(frame, "totems", L.TOTEMFRAME_EDIT_LABEL, "TOP", "TOPLEFT", DEFAULT_X, DEFAULT_Y)
|
|
|
|
hooksecurefunc(Layout, "Apply", function(id)
|
|
if id == "playerframe" then
|
|
follow()
|
|
end
|
|
end)
|
|
-- Shown empty while the layout is edited, hidden again after
|
|
hooksecurefunc(Layout, "BeginEdit", update)
|
|
hooksecurefunc(Layout, "Commit", update)
|
|
hooksecurefunc(Layout, "Revert", update)
|
|
|
|
frame:SetScript("OnEvent", function(_, event)
|
|
if event == "PLAYER_LOGIN" then
|
|
follow()
|
|
end
|
|
update()
|
|
end)
|
|
for _, event in ipairs({ "PLAYER_LOGIN", "PLAYER_ENTERING_WORLD", "PLAYER_TOTEM_UPDATE" }) do
|
|
frame:RegisterEvent(event)
|
|
end
|