Swing Timer: color the main-hand bar for queued Heroic Strike, Cleave and Maul

Ported from pfUI's swing timer. UNIT_SPELLCAST_SENT with
C_Spell.IsNextMeleeSpell and the spell name sets the queue; it clears on
UNIT_SPELLCAST_SUCCEEDED or _FAILED, on leaving combat, or when the
ability's action button goes dark after being lit (Esc or a second press
un-queues it without an event). Heroic Strike is light blue instead of
pfUI's yellow, which would not stand out on the gold bar; Cleave green
and Maul orange as in pfUI. Out-of-range gray still wins.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019W3rwusM4i1CYaHQAYixJ7
This commit is contained in:
2026-11-02 01:57:32 +00:00
co-authored by Claude Opus 5.5
parent 151abf3280
commit 0a0cd055c4
2 changed files with 127 additions and 0 deletions
+1
View File
@@ -9,6 +9,7 @@ This fork is based on [Dusk/ShaguTweaks-ClassicAPI](https://octowow.st/git/Dusk/
### Added
- **Swing Timer** (Action Bar, off by default): main-hand and off-hand swing timers styled like the classic cast bar, with the weapon's icon on the left. They sit just above the cast bar; hold Ctrl+Shift+Alt to drag them, right-click to reset. A bar turns gray while the target is out of reach.
- **Swing Timer queue colors:** the main-hand bar shows a queued Heroic Strike in light blue and Cleave in green (warriors), and Maul in orange (druids). It goes back to gold when the swing goes off, is refused, or is cancelled.
- **Wand Timer** (Action Bar, off by default): the time until your next wand shot in the same style. It also times Auto Shot for bows, guns and crossbows, and turns red during the last half second, when moving would cancel the shot.
- Both are ported from brues-code/pfUI's swing timer and need ClassicAPI v1.15.10 or newer (`PLAYER_SWING`).
+126
View File
@@ -38,8 +38,15 @@ local COLORS = {
ranged = { .3, .6, 1 },
far = { .5, .5, .5 }, -- target out of range
windup = { 1, 0, 0 }, -- Auto Shot wind-up: don't move
-- queued on-next-swing abilities, on the main-hand bar
hs = { .4, .8, 1 }, -- Heroic Strike: light blue (gold would hide pfUI's yellow)
cleave = { .2, .9, .2 }, -- Cleave: green, as in pfUI
maul = { 1, .55, 0 }, -- Maul: orange, as in pfUI
}
-- Rank 1 spell IDs; every rank shares the localized name.
local QUEUE_SPELLS = { hs = 78, cleave = 845, maul = 6807 }
local SLOTS = { mainhand = 16, offhand = 17, ranged = 18 }
local ORDER = { "mainhand", "offhand", "ranged" }
local PREVIEW = { mainhand = T["Main Hand"], offhand = T["Off Hand"], ranged = T["Ranged"] }
@@ -51,8 +58,72 @@ local S = {
hunter = false, preview = false, elapsed = 0,
far = {},
bars = {},
-- Heroic Strike / Cleave / Maul queue
queue = false, -- this class can queue one of them
queued = nil, -- "hs", "cleave", "maul" or nil
queuedSeen = false, -- its action button has been seen lit
queueNames = {}, -- localized name -> kind
queueSlots = {}, -- kind -> action slots holding it
nextMelee = {}, -- spellID -> kind or false
}
-- Classify a spell ID as "hs", "cleave", "maul" or nil.
local function QueueKind(spellID)
if not spellID or spellID <= 0 then return end
local known = S.nextMelee[spellID]
if known ~= nil then return known or nil end
local kind = false
if _G.C_Spell.IsNextMeleeSpell(spellID) then
kind = S.queueNames[_G.C_Spell.GetSpellName(spellID) or ""] or false
end
S.nextMelee[spellID] = kind
return kind or nil
end
-- Find the action buttons that hold each ability. A queued ability lights
-- its button (IsCurrentAction); Esc or a second press un-queues it without
-- any event, so the lit buttons are how a cancel is noticed.
local function ScanQueueSlots()
S.queueSlots = { hs = {}, cleave = {}, maul = {} }
if not S.queue then return end
for slot = 1, 120 do
local kind, id = GetActionInfo(slot)
local name
if kind == "spell" then
name = _G.C_Spell.GetSpellName(id)
elseif kind == "macro" then
name = GetMacroSpell(id)
end
local queueKind = name and S.queueNames[name]
if queueKind then table.insert(S.queueSlots[queueKind], slot) end
end
end
local function ClearQueue()
S.queued = nil
S.queuedSeen = false
end
-- Keep the queued color while a button of that ability is lit. Once seen
-- lit and then dark, it was cancelled. Never seen lit (cast from a macro or
-- keybind not on the bars): keep it until the spell goes off or fails.
local function ReconcileQueue()
if not S.queued then return end
local slots = S.queueSlots[S.queued]
if not slots then return end
for i = 1, table.getn(slots) do
if IsCurrentAction(slots[i]) then
S.queuedSeen = true
return
end
end
if S.queuedSeen then ClearQueue() end
end
local function Supported()
if not _G.C_SwingTimer or not _G.Enum or not _G.Enum.PlayerSwingType then return end
local utils = _G.C_EventUtils
@@ -174,6 +245,10 @@ local function Tick(bar, delta, now)
end
local r, g, b = unpack(COLORS[bar.key])
if bar.key == "mainhand" and S.queued then
ReconcileQueue()
if S.queued then r, g, b = unpack(COLORS[S.queued]) end
end
if bar.key == "ranged" and S.hunter and bar.timer > 0 and bar.timer <= AUTOSHOT_WINDUP then
r, g, b = unpack(COLORS.windup)
end
@@ -368,6 +443,26 @@ local function CreateCore()
S.combat = true
elseif event == "PLAYER_REGEN_ENABLED" then
S.combat = false
ClearQueue()
elseif event == "UNIT_SPELLCAST_SENT" then
-- arg1 unit, arg4 spellID: Heroic Strike, Cleave or Maul queued
if arg1 == "player" and S.queue then
local kind = QueueKind(arg4)
if kind then
S.queued = kind
S.queuedSeen = false
end
end
elseif event == "UNIT_SPELLCAST_SUCCEEDED" or event == "UNIT_SPELLCAST_FAILED" then
-- arg1 unit, arg3 spellID: the queued swing went off or was refused
if arg1 == "player" and S.queued and QueueKind(arg3) then
ClearQueue()
end
elseif event == "ACTIONBAR_SLOT_CHANGED" then
if S.queue then ScanQueueSlots() end
elseif event == "PLAYER_EQUIPMENT_CHANGED" or event == "UNIT_INVENTORY_CHANGED" then
if event == "PLAYER_EQUIPMENT_CHANGED" and arg1 ~= 16 and arg1 ~= 17 and arg1 ~= 18 then return end
@@ -383,6 +478,7 @@ local function CreateCore()
SetPreview(ModifiersDown() and true or false)
elseif event == "PLAYER_DEAD" then
ClearQueue()
ResetAll()
elseif event == "PLAYER_ENTERING_WORLD" then
@@ -430,11 +526,41 @@ local function EnableRangeCheck(bar)
S.far[bar.swingType] = _G.C_SwingTimer.IsTargetWithinSwingRange(bar.swingType) == false
end
-- Heroic Strike / Cleave (warriors) and Maul (druids) queue colors.
local function EnableQueueColors()
local spell = _G.C_Spell
if not spell or not spell.IsNextMeleeSpell or not spell.GetSpellName or not GetActionInfo then return end
local _, class = UnitClass("player")
local kinds = class == "WARRIOR" and { "hs", "cleave" } or class == "DRUID" and { "maul" }
if not kinds then return end
for i = 1, table.getn(kinds) do
local name = spell.GetSpellName(QUEUE_SPELLS[kinds[i]])
if name then S.queueNames[name] = kinds[i] end
end
S.queue = true
ScanQueueSlots()
-- player-only where ClassicAPI can filter: SUCCEEDED fires for every unit
local events = S.events
local unitEvents = { "UNIT_SPELLCAST_SENT", "UNIT_SPELLCAST_SUCCEEDED", "UNIT_SPELLCAST_FAILED" }
for i = 1, table.getn(unitEvents) do
if events.RegisterUnitEvent then
events:RegisterUnitEvent(unitEvents[i], "player")
else
events:RegisterEvent(unitEvents[i])
end
end
events:RegisterEvent("ACTIONBAR_SLOT_CHANGED")
end
swing.enable = function(self)
if not CreateCore() then return end
S.melee = true
EnableRangeCheck(S.bars.mainhand)
EnableRangeCheck(S.bars.offhand)
EnableQueueColors()
end
wand.enable = function(self)