forked from Dusk/ShaguTweaks-ClassicAPI
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019W3rwusM4i1CYaHQAYixJ7
295 lines
8.2 KiB
Lua
295 lines
8.2 KiB
Lua
-- Totem Timers (Shaman)
|
|
--
|
|
-- One icon per active totem (Fire, Earth, Water, Air) with the time left.
|
|
-- Left-click targets the totem, hovering shows its tooltip. The data comes
|
|
-- from ClassicAPI's GetTotemInfo / GetTotemTimeLeft, and the display is
|
|
-- redrawn on PLAYER_TOTEM_UPDATE, with a light timer only while a totem is up.
|
|
|
|
local _G = ShaguTweaks.GetGlobalEnv()
|
|
local T = ShaguTweaks.T
|
|
|
|
local module = ShaguTweaks:register({
|
|
title = T["Totem Timers"],
|
|
description = T["Shamans: shows the totems you have out as icons with the time left. Left-click targets a totem. Hold <Ctrl>, <Shift> and <Alt> to move them; right-click to reset."],
|
|
expansions = { ["vanilla"] = true },
|
|
category = T["Interface"],
|
|
enabled = false,
|
|
})
|
|
|
|
local SIZE = 34
|
|
local GAP = 4
|
|
local THROTTLE = .1
|
|
local POSITION_KEY = "TotemTimersPosition"
|
|
|
|
-- slot 1 Fire, 2 Earth, 3 Water, 4 Air
|
|
local SLOT_COLORS = {
|
|
{ 1, .45, .1 }, -- fire
|
|
{ .7, .55, .3 }, -- earth
|
|
{ .3, .6, 1 }, -- water
|
|
{ .8, .85, 1 }, -- air
|
|
}
|
|
|
|
local PREVIEW_ICONS = {
|
|
"Interface\\Icons\\Spell_Fire_SearingTotem",
|
|
"Interface\\Icons\\Spell_Nature_StrengthOfEarthTotem02",
|
|
"Interface\\Icons\\Spell_Nature_ManaRegenTotem",
|
|
"Interface\\Icons\\Spell_Nature_InvisibilityTotem",
|
|
}
|
|
|
|
-- One table for state: Lua 5.0 allows only 32 upvalues per function.
|
|
local S = { unlocked = false, elapsed = 0, buttons = {} }
|
|
|
|
local function Supported()
|
|
return type(_G.GetTotemInfo) == "function" and type(_G.GetTotemTimeLeft) == "function"
|
|
end
|
|
|
|
local function FormatTime(seconds)
|
|
if seconds >= 60 then
|
|
return string.format("%d:%02d", math.floor(seconds / 60), math.floor(seconds - math.floor(seconds / 60) * 60))
|
|
end
|
|
return string.format("%d", seconds)
|
|
end
|
|
|
|
local function Layout()
|
|
local shown = 0
|
|
for slot = 1, 4 do
|
|
local b = S.buttons[slot]
|
|
if b:IsShown() then
|
|
b:ClearAllPoints()
|
|
b:SetPoint("LEFT", S.frame, "LEFT", (slot - 1) * (SIZE + GAP), 0)
|
|
shown = shown + 1
|
|
end
|
|
end
|
|
return shown
|
|
end
|
|
|
|
-- Redraw the time and bar of the visible buttons; returns how many are up.
|
|
local function UpdateTimes()
|
|
local active = 0
|
|
for slot = 1, 4 do
|
|
local b = S.buttons[slot]
|
|
if b:IsShown() and not S.unlocked then
|
|
local left = _G.GetTotemTimeLeft(slot) or 0
|
|
if left > 0 then
|
|
active = active + 1
|
|
b.time:SetText(FormatTime(left))
|
|
if left <= 5 then
|
|
b.time:SetTextColor(1, .2, .2)
|
|
elseif left <= 10 then
|
|
b.time:SetTextColor(1, .9, .2)
|
|
else
|
|
b.time:SetTextColor(1, 1, 1)
|
|
end
|
|
if b.duration and b.duration > 0 then
|
|
b.bar:SetWidth(math.max(1, SIZE * math.min(left / b.duration, 1)))
|
|
end
|
|
else
|
|
-- duration ran out, the creature is about to go
|
|
b.time:SetText("")
|
|
b.bar:SetWidth(1)
|
|
end
|
|
end
|
|
end
|
|
return active
|
|
end
|
|
|
|
-- Re-read the four slots. Active totem = start time above 0; "have a totem"
|
|
-- is also true when you only carry the totem tool.
|
|
local function Refresh()
|
|
if S.unlocked then return end
|
|
|
|
local any = false
|
|
for slot = 1, 4 do
|
|
local b = S.buttons[slot]
|
|
local have, name, start, duration, icon = _G.GetTotemInfo(slot)
|
|
if start and start > 0 then
|
|
any = true
|
|
b.duration = duration
|
|
if icon and icon ~= "" then b.icon:SetTexture(icon) end
|
|
b:Show()
|
|
else
|
|
b.duration = nil
|
|
b:Hide()
|
|
end
|
|
end
|
|
|
|
if any then
|
|
Layout()
|
|
UpdateTimes()
|
|
if not S.frame:IsShown() then S.frame:Show() end
|
|
else
|
|
S.frame:Hide()
|
|
end
|
|
end
|
|
|
|
local function OnUpdate()
|
|
S.elapsed = S.elapsed + (arg1 or 0)
|
|
if S.elapsed < THROTTLE then return end
|
|
S.elapsed = 0
|
|
UpdateTimes()
|
|
end
|
|
|
|
local function RestorePosition()
|
|
local pos = ShaguTweaks_config and ShaguTweaks_config[POSITION_KEY]
|
|
S.frame:ClearAllPoints()
|
|
if pos and pos[1] and pos[2] then
|
|
S.frame:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", pos[1], pos[2])
|
|
else
|
|
S.frame:SetPoint("BOTTOM", UIParent, "BOTTOM", 0, 200)
|
|
end
|
|
end
|
|
|
|
local function ModifiersDown()
|
|
local API = ShaguTweaks.API or {}
|
|
local shift = API.IsShiftKeyDown or IsShiftKeyDown
|
|
local ctrl = API.IsControlKeyDown or IsControlKeyDown
|
|
local alt = API.IsAltKeyDown or IsAltKeyDown
|
|
return shift() and ctrl() and alt() and true or false
|
|
end
|
|
|
|
-- Hold Ctrl+Shift+Alt to show four preview icons and drag them.
|
|
local function SetUnlocked(on)
|
|
if S.unlocked == on then return end
|
|
S.unlocked = on
|
|
S.frame:EnableMouse(on)
|
|
|
|
if on then
|
|
for slot = 1, 4 do
|
|
local b = S.buttons[slot]
|
|
b:EnableMouse(false) -- let the drag reach the container
|
|
b.icon:SetTexture(PREVIEW_ICONS[slot])
|
|
b.time:SetText(FormatTime(60 - slot * 10))
|
|
b.time:SetTextColor(1, 1, 1)
|
|
b.bar:SetWidth(SIZE)
|
|
b:Show()
|
|
end
|
|
Layout()
|
|
S.frame:Show()
|
|
else
|
|
S.frame:StopMovingOrSizing()
|
|
for slot = 1, 4 do S.buttons[slot]:EnableMouse(true); S.buttons[slot]:Hide() end
|
|
S.frame:Hide()
|
|
Refresh()
|
|
end
|
|
end
|
|
|
|
local function CreateButton(slot)
|
|
local b = CreateFrame("Button", "ShaguTweaksTotem" .. slot, S.frame)
|
|
b:SetWidth(SIZE)
|
|
b:SetHeight(SIZE)
|
|
b:Hide()
|
|
|
|
b.border = b:CreateTexture(nil, "BACKGROUND")
|
|
b.border:SetTexture("Interface\\Buttons\\WHITE8X8")
|
|
b.border:SetPoint("TOPLEFT", b, "TOPLEFT", -1, 1)
|
|
b.border:SetPoint("BOTTOMRIGHT", b, "BOTTOMRIGHT", 1, -1)
|
|
b.border:SetVertexColor(SLOT_COLORS[slot][1], SLOT_COLORS[slot][2], SLOT_COLORS[slot][3])
|
|
|
|
b.icon = b:CreateTexture(nil, "ARTWORK")
|
|
b.icon:SetAllPoints(b)
|
|
b.icon:SetTexCoord(.08, .92, .08, .92)
|
|
|
|
b.time = b:CreateFontString(nil, "OVERLAY")
|
|
b.time:SetFont("Fonts\\FRIZQT__.TTF", 14, "OUTLINE")
|
|
b.time:SetPoint("CENTER", b, "CENTER", 0, 0)
|
|
b.time:SetTextColor(1, 1, 1)
|
|
|
|
-- remaining time as a thin bar under the icon
|
|
b.bar = b:CreateTexture(nil, "OVERLAY")
|
|
b.bar:SetTexture("Interface\\Buttons\\WHITE8X8")
|
|
b.bar:SetHeight(3)
|
|
b.bar:SetPoint("TOPLEFT", b, "BOTTOMLEFT", 0, -2)
|
|
b.bar:SetVertexColor(SLOT_COLORS[slot][1], SLOT_COLORS[slot][2], SLOT_COLORS[slot][3])
|
|
|
|
b:RegisterForClicks("LeftButtonUp")
|
|
b:SetScript("OnClick", function()
|
|
if S.unlocked then return end
|
|
if type(_G.TargetTotem) == "function" then _G.TargetTotem(slot) end
|
|
end)
|
|
b:SetScript("OnEnter", function()
|
|
if S.unlocked then return end
|
|
if GameTooltip.SetTotem then
|
|
-- shows itself, no :Show() needed
|
|
GameTooltip:SetOwner(this, "ANCHOR_TOP")
|
|
GameTooltip:SetTotem(slot)
|
|
end
|
|
end)
|
|
b:SetScript("OnLeave", function() GameTooltip:Hide() end)
|
|
|
|
S.buttons[slot] = b
|
|
end
|
|
|
|
local function CreateTimers()
|
|
if S.frame then return end
|
|
|
|
local f = CreateFrame("Frame", "ShaguTweaksTotemTimers", UIParent)
|
|
f:SetWidth(4 * SIZE + 3 * GAP)
|
|
f:SetHeight(SIZE)
|
|
f:SetFrameStrata("MEDIUM")
|
|
f:SetMovable(true)
|
|
f:SetClampedToScreen(true)
|
|
f:EnableMouse(false)
|
|
f:Hide()
|
|
S.frame = f
|
|
|
|
for slot = 1, 4 do CreateButton(slot) end
|
|
|
|
f:SetScript("OnMouseDown", function()
|
|
if arg1 == "LeftButton" then this:StartMoving() end
|
|
end)
|
|
f:SetScript("OnMouseUp", function()
|
|
this:StopMovingOrSizing()
|
|
ShaguTweaks_config = ShaguTweaks_config or {}
|
|
if arg1 == "RightButton" then
|
|
ShaguTweaks_config[POSITION_KEY] = nil
|
|
else
|
|
ShaguTweaks_config[POSITION_KEY] = { this:GetLeft(), this:GetBottom() }
|
|
end
|
|
RestorePosition()
|
|
end)
|
|
|
|
-- Runs only while a totem is out (a hidden frame gets no OnUpdate).
|
|
f:SetScript("OnUpdate", OnUpdate)
|
|
|
|
S.events = CreateFrame("Frame")
|
|
S.events:SetScript("OnEvent", function()
|
|
if event == "MODIFIER_STATE_CHANGED" then
|
|
SetUnlocked(ModifiersDown())
|
|
else
|
|
Refresh()
|
|
end
|
|
end)
|
|
|
|
RestorePosition()
|
|
end
|
|
|
|
module.enable = function(self)
|
|
local _, class = UnitClass("player")
|
|
if class ~= "SHAMAN" or not Supported() then return end
|
|
|
|
CreateTimers()
|
|
|
|
S.events:RegisterEvent("PLAYER_TOTEM_UPDATE")
|
|
S.events:RegisterEvent("PLAYER_ENTERING_WORLD")
|
|
S.events:RegisterEvent("MODIFIER_STATE_CHANGED")
|
|
|
|
SLASH_SHAGUTOTEMTIMERS1 = "/totemtimers"
|
|
SlashCmdList["SHAGUTOTEMTIMERS"] = function(msg)
|
|
msg = string.lower(msg or "")
|
|
if msg == "reset" then
|
|
ShaguTweaks_config = ShaguTweaks_config or {}
|
|
ShaguTweaks_config[POSITION_KEY] = nil
|
|
RestorePosition()
|
|
end
|
|
end
|
|
|
|
Refresh()
|
|
end
|
|
|
|
module.disable = function(self)
|
|
if not S.frame then return end
|
|
if S.unlocked then SetUnlocked(false) end
|
|
S.events:UnregisterAllEvents()
|
|
S.frame:Hide()
|
|
end
|