Files
ShaguTweaks-ClassicAPI/mods/swing-timer.lua
T

701 lines
23 KiB
Lua

-- Swing Timer and Wand Timer
--
-- Ported from brues-code/pfUI's swingtimer module (MIT, Shagu and
-- contributors) and restyled as classic cast bars with the weapon icon.
-- ClassicAPI's PLAYER_SWING event reports the server's attack timers
-- (white hits, Heroic Strike/Maul, parry haste, extra attacks, Auto Shot
-- and wand Shoot), so nothing here guesses from the combat log.
local _G = ShaguTweaks.GetGlobalEnv()
local T = ShaguTweaks.T
local API = ShaguTweaks.API or {}
local swing = ShaguTweaks:register({
title = T["Swing Timer"],
description = T["Shows main-hand and off-hand swing timers above the cast bar, with the weapon's icon. Hold <Ctrl>, <Shift> and <Alt> to move each bar on its own; right-click a bar to reset it."],
expansions = { ["vanilla"] = true },
category = T["Action Bar"],
enabled = false,
})
local wand = ShaguTweaks:register({
title = T["Wand Timer"],
description = T["Shows the time until your next wand shot (or Auto Shot) above the cast bar, with the weapon's icon."],
expansions = { ["vanilla"] = true },
category = T["Action Bar"],
enabled = false,
})
local WIDTH, HEIGHT = 195, 13
local SPACING = 24 -- one bar plus its cast bar border
local GRACE = .15 -- bridge the frame or two until the next PLAYER_SWING
local THROTTLE = .02
local AUTOSHOT_WINDUP = .5
local COLORS = {
mainhand = { 1, .7, 0 }, -- the cast bar's own gold
offhand = { 0, 1, 0 }, -- the channel bar's green
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"] }
-- Everything lives in one table: Lua 5.0 allows only 32 upvalues per function.
local S = {
melee = false, ranged = false,
autoattack = false, autorepeat = false, combat = false,
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
if utils and utils.IsEventValid and not utils.IsEventValid("PLAYER_SWING") then return end
return true
end
local ModifiersDown = ShaguTweaks.API.AreMoveModifiersDown
local function HasOffhandWeapon()
local items = _G.C_Item
if items and items.GetItemInventoryType then
local invtype = items.GetItemInventoryType({ equipmentSlotIndex = SLOTS.offhand })
return invtype == 13 or invtype == 22 -- one-hand, off-hand weapon
end
local link = GetInventoryItemLink("player", SLOTS.offhand)
local _, _, id = link and string.find(link, "item:(%d+)")
if not id then return false end
local _, _, _, _, _, _, _, equip = GetItemInfo(tonumber(id))
return equip == "INVTYPE_WEAPON" or equip == "INVTYPE_WEAPONOFFHAND"
end
local function UpdateSpeeds()
local mh, oh = UnitAttackSpeed("player")
S.bars.mainhand.speed = (mh and mh > 0) and mh or S.bars.mainhand.speed or 0
S.bars.offhand.speed = (HasOffhandWeapon() and oh and oh > 0) and oh or 0
local ra = UnitRangedDamage("player")
S.bars.ranged.speed = (ra and ra > 0) and ra or 0
end
local function UpdateIcon(bar)
local texture = GetInventoryItemTexture("player", bar.slot)
if texture then
bar.icon.texture:SetTexture(texture)
bar.icon:Show()
else
bar.icon:Hide()
end
end
local function Wanted(bar)
if bar.key == "ranged" then return S.ranged end
return S.melee
end
-- Every bar can have its own saved spot ("SwingTimerPosition_<bar>").
local function BarPosition(key)
local pos = ShaguTweaks_config and ShaguTweaks_config["SwingTimerPosition_" .. key]
if pos and pos[1] and pos[2] then return pos end
end
-- Bars without a saved spot stack upward from the container, in a fixed
-- order; bars with one sit where they were put.
local function Layout()
local slot = 0
for i = 1, 3 do
local bar = S.bars[ORDER[i]]
if bar:IsShown() then
local pos = BarPosition(bar.key)
bar:ClearAllPoints()
if pos then
bar:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", pos[1], pos[2])
else
bar:SetPoint("BOTTOM", S.container, "BOTTOM", 0, slot * SPACING)
slot = slot + 1
end
end
end
end
local function SetProgress(bar, progress, r, g, b)
bar:SetValue(progress)
bar:SetStatusBarColor(r, g, b)
bar.spark:SetPoint("CENTER", bar, "LEFT", progress * WIDTH, 2)
end
local function StopBar(bar)
if bar.wake then bar.wake:Cancel(); bar.wake = nil end
if bar.windupWake then bar.windupWake:Cancel(); bar.windupWake = nil end
if bar.binding then bar.binding:Disable() end
bar.active = false
bar.inWindup = false
bar.timer = 0
bar.graceAt = nil
if not S.preview then bar:Hide() end
end
local function Running(bar)
return bar.key == "ranged" and S.autorepeat
or (bar.key ~= "ranged" and S.autoattack and S.combat)
end
local function UpdateColors()
if S.preview then return end
ReconcileQueue()
for i = 1, 3 do
local bar = S.bars[ORDER[i]]
local color = COLORS[bar.key]
if bar.key == "mainhand" and S.queued then color = COLORS[S.queued] end
if bar.key == "ranged" and S.hunter and bar.active and bar.inWindup then color = COLORS.windup end
if S.far[bar.swingType] then color = COLORS.far end
bar:SetStatusBarColor(unpack(color))
end
end
local function StopWaitingBars()
if not S.native then return end
local changed = false
for i = 1, 3 do
local bar = S.bars[ORDER[i]]
if bar.active and bar.graced and not Running(bar) then StopBar(bar); changed = true end
end
if changed then Layout() end
end
local function StartBar(bar, remaining)
if not Wanted(bar) or S.preview then return end
if type(remaining) ~= "number" or remaining ~= remaining or remaining < 0 then return end
UpdateSpeeds()
if bar.speed <= 0 then return end
-- remaining can be shorter than the weapon speed (parry haste, Auto Shot
-- wind-up): keep the full swing as the bar length and start part-way.
bar.max = math.max(bar.speed, remaining)
bar.timer = remaining
bar.graceAt = nil
bar.active = true
bar.graced = nil
bar.inWindup = bar.key == "ranged" and S.hunter and remaining > 0 and remaining <= AUTOSHOT_WINDUP
UpdateIcon(bar)
if S.native then
if bar.wake then bar.wake:Cancel() end
if bar.windupWake then bar.windupWake:Cancel(); bar.windupWake = nil end
bar.duration:SetTimeFromEnd(GetTime() + remaining, bar.max)
bar:SetTimerDuration(bar.duration, _G.Enum.StatusBarInterpolation.Immediate,
_G.Enum.StatusBarTimerDirection.ElapsedTime)
bar.binding:Enable()
bar.wake = _G.C_Timer.NewTimer(remaining, bar.due)
if bar.key == "ranged" and S.hunter and remaining > AUTOSHOT_WINDUP then
bar.windupWake = _G.C_Timer.NewTimer(remaining - AUTOSHOT_WINDUP, bar.windup)
end
UpdateColors()
end
if not bar:IsShown() then
bar:Show()
Layout()
end
if not S.native then S.driver:Show() end
end
local function ResetAll()
for i = 1, 3 do StopBar(S.bars[ORDER[i]]) end
Layout()
end
local function Tick(bar, delta, now)
if not bar.active then return end
bar.timer = bar.timer - delta
if bar.timer <= 0 then
bar.timer = 0
-- Keep a full bar while auto-attack or Shoot is still on (the target may
-- just be out of reach); hide it once the attacks stopped.
local running = bar.key == "ranged" and S.autorepeat or (bar.key ~= "ranged" and S.autoattack and S.combat)
if not bar.graceAt then
bar.graceAt = now + GRACE
elseif now >= bar.graceAt and not running then
StopBar(bar)
Layout()
return
end
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
if S.far[bar.swingType] then
r, g, b = unpack(COLORS.far)
end
SetProgress(bar, 1 - bar.timer / bar.max, r, g, b)
local tenth = math.floor(bar.timer * 10)
if bar.lastTenth ~= tenth then
bar.lastTenth = tenth
bar.text:SetText(string.format("%.1f", tenth / 10))
end
end
local function OnUpdate()
S.elapsed = S.elapsed + arg1
if S.elapsed < THROTTLE then return end
local delta = S.elapsed
S.elapsed = 0
local now = GetTime()
local any = false
for i = 1, 3 do
local bar = S.bars[ORDER[i]]
Tick(bar, delta, now)
any = any or bar.active
end
if not any then this:Hide() end
end
-- Classic cast bar look: dark background, UI-StatusBar fill, the cast bar
-- border and spark, and an icon frame like Improved Castbar's.
local function CreateBar(key)
local bar = CreateFrame("StatusBar", "ShaguTweaksSwingTimer_" .. key, S.container)
bar:SetWidth(WIDTH)
bar:SetHeight(HEIGHT)
bar:SetStatusBarTexture("Interface\\TargetingFrame\\UI-StatusBar")
bar:SetMinMaxValues(0, 1)
bar:SetValue(0)
bar:Hide()
bar.bg = bar:CreateTexture(nil, "BACKGROUND")
bar.bg:SetAllPoints(bar)
bar.bg:SetTexture(0, 0, 0, .5)
bar.border = bar:CreateTexture(nil, "ARTWORK")
bar.border:SetTexture("Interface\\CastingBar\\UI-CastingBar-Border")
bar.border:SetWidth(256)
bar.border:SetHeight(64)
bar.border:SetPoint("TOP", bar, "TOP", 0, 28)
bar.spark = bar:CreateTexture(nil, "OVERLAY")
bar.spark:SetTexture("Interface\\CastingBar\\UI-CastingBar-Spark")
bar.spark:SetBlendMode("ADD")
bar.spark:SetWidth(32)
bar.spark:SetHeight(32)
bar.spark:SetPoint("CENTER", bar, "LEFT", 0, 2)
bar.text = bar:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
bar.text:SetPoint("CENTER", bar, "CENTER", 0, 3)
-- As in pfUI, native duration objects own the fill and countdown. The
-- spark follows the native fill texture without a Lua position updater.
if S.native and type(bar.SetTimerDuration) ~= "function" then S.native = false end
if S.native then
bar.duration = _G.C_DurationUtil.CreateDuration()
bar.binding = _G.C_DurationUtil.CreateDurationTextBinding()
bar.binding:SetFontString(bar.text)
bar.binding:SetDuration(bar.duration)
bar.binding:SetFormatter(S.formatter)
bar.binding:SetExpiredText("0.0")
bar.binding:Disable()
bar.spark:ClearAllPoints()
bar.spark:SetPoint("CENTER", bar:GetStatusBarTexture(), "RIGHT", 0, 2)
-- Allocate callbacks once per bar, not on every swing reset.
bar.grace = function()
bar.wake = nil
bar.graced = true
if not Running(bar) then StopBar(bar); Layout() end
end
bar.due = function()
bar.inWindup = false
bar.binding:Disable()
if not S.preview then bar.text:SetText("0.0") end
bar.wake = _G.C_Timer.NewTimer(GRACE, bar.grace)
UpdateColors()
end
bar.windup = function()
bar.windupWake = nil
bar.inWindup = true
UpdateColors()
end
end
bar.icon = CreateFrame("Frame", nil, bar)
bar.icon:SetPoint("RIGHT", bar, "LEFT", -10, 2)
bar.icon:SetWidth(28)
bar.icon:SetHeight(28)
bar.icon:SetBackdrop({
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
tile = true, tileSize = 8, edgeSize = 12,
insets = { left = 2, right = 2, top = 2, bottom = 2 }
})
if ShaguTweaks.DarkMode then
bar.icon:SetBackdropBorderColor(.3, .3, .3, .9)
end
bar.icon.texture = bar.icon:CreateTexture(nil, "BACKGROUND")
bar.icon.texture:SetPoint("CENTER", 0, 0)
bar.icon.texture:SetWidth(24)
bar.icon.texture:SetHeight(24)
bar.icon.texture:SetTexCoord(.08, .92, .08, .92)
-- Each bar is dragged on its own while the preview is up.
bar:SetMovable(true)
bar:SetClampedToScreen(true)
bar:EnableMouse(false)
bar:SetScript("OnMouseDown", function()
if arg1 == "LeftButton" then this:StartMoving() end
end)
bar:SetScript("OnMouseUp", function()
this:StopMovingOrSizing()
ShaguTweaks_config = ShaguTweaks_config or {}
if arg1 == "RightButton" then
ShaguTweaks_config["SwingTimerPosition_" .. this.key] = nil
else
ShaguTweaks_config["SwingTimerPosition_" .. this.key] = { this:GetLeft(), this:GetBottom() }
end
Layout()
end)
bar.key = key
bar.slot = SLOTS[key]
bar.swingType = _G.Enum.PlayerSwingType[key == "mainhand" and "MainHand" or key == "offhand" and "OffHand" or "Ranged"]
bar.speed, bar.timer, bar.max = 0, 0, 1
return bar
end
-- Hold Ctrl+Shift+Alt to show every enabled bar and drag each one on its own;
-- right-click a bar to put just that one back.
local function SetPreview(on)
if S.preview == on then return end
S.preview = on
for i = 1, 3 do
local bar = S.bars[ORDER[i]]
bar:EnableMouse(on)
if not on then bar:StopMovingOrSizing() end
if on and Wanted(bar) then
if bar.binding then bar.binding:Disable(); bar:SetMinMaxValues(0, 1) end
UpdateIcon(bar)
local r, g, b = unpack(COLORS[bar.key])
SetProgress(bar, 1, r, g, b)
bar.text:SetText(PREVIEW[bar.key])
bar.lastTenth = nil
bar:Show()
elseif S.native and bar.active then
bar.spark:ClearAllPoints()
bar.spark:SetPoint("CENTER", bar:GetStatusBarTexture(), "RIGHT", 0, 2)
bar:SetTimerDuration(bar.duration, _G.Enum.StatusBarInterpolation.Immediate,
_G.Enum.StatusBarTimerDirection.ElapsedTime)
if bar.duration:GetRemainingDuration() > 0 then bar.binding:Enable()
else bar.text:SetText("0.0") end
elseif not bar.active then
bar:Hide()
end
end
Layout()
if S.native then UpdateColors() end
end
-- Keep the bars where they are when something they were stacked on (the cast
-- bar) is dragged: every bar that still follows the default spot is pinned.
ShaguTweaks.FreezeSwingBars = function()
if not S.container then return end
for i = 1, 3 do
local bar = S.bars[ORDER[i]]
local key = "SwingTimerPosition_" .. bar.key
local left, bottom = bar:GetLeft(), bar:GetBottom()
if bar:IsShown() and left and bottom and not BarPosition(bar.key) then
ShaguTweaks_config = ShaguTweaks_config or {}
ShaguTweaks_config[key] = { left, bottom }
end
end
Layout()
end
local function RestorePosition()
local pos = ShaguTweaks_config and ShaguTweaks_config["SwingTimerPosition"]
S.container:ClearAllPoints()
if pos and pos[1] and pos[2] then
S.container:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", pos[1], pos[2])
else
-- default: stacked just above the cast bar
S.container:SetPoint("BOTTOM", CastingBarFrame, "TOP", 0, 20)
end
end
local function CreateCore()
if S.container then return true end
if not Supported() then return end
S.container = CreateFrame("Frame", "ShaguTweaksSwingTimer", UIParent)
S.container:SetWidth(WIDTH)
S.container:SetHeight(HEIGHT)
S.container:EnableMouse(false)
S.native = API.durationtimers and true or false
if S.native then
S.formatter = _G.C_StringUtil.CreateNumericRuleFormatter()
S.formatter:AddBreakpoint({ threshold = 0, step = .1,
rounding = _G.Enum.NumericRuleFormatRounding.Down, format = "%.1f" })
end
for i = 1, 3 do S.bars[ORDER[i]] = CreateBar(ORDER[i]) end
if not S.native then
S.driver = CreateFrame("Frame", nil, UIParent)
S.driver:Hide()
S.driver:SetScript("OnUpdate", OnUpdate)
end
S.events = CreateFrame("Frame")
S.events:SetScript("OnEvent", function()
if event == "PLAYER_SWING" then
local swingType = _G.Enum.PlayerSwingType
if arg2 == swingType.MainHand then
StartBar(S.bars.mainhand, arg1)
elseif arg2 == swingType.OffHand then
StartBar(S.bars.offhand, arg1)
elseif arg2 == swingType.Ranged then
StartBar(S.bars.ranged, arg1)
end
elseif event == "PLAYER_SWING_RANGE_UPDATE" then
-- arg1 swing type, arg2 in range, arg3 a check was possible
S.far[arg1] = arg3 and not arg2
elseif event == "PLAYER_TARGET_CHANGED" then
for i = 1, 3 do
local swingType = S.bars[ORDER[i]].swingType
S.far[swingType] = _G.C_SwingTimer.IsTargetWithinSwingRange(swingType) == false
end
elseif event == "PLAYER_ENTER_COMBAT" or event == "START_AUTOATTACK" then
S.autoattack = true
elseif event == "PLAYER_LEAVE_COMBAT" or event == "STOP_AUTOATTACK" then
S.autoattack = false
elseif event == "START_AUTOREPEAT_SPELL" then
S.autorepeat = true
elseif event == "STOP_AUTOREPEAT_SPELL" then
S.autorepeat = false
elseif event == "PLAYER_REGEN_DISABLED" then
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 == "ACTIONBAR_UPDATE_STATE" then
-- Native timers do not poll action buttons. This event covers cancelling
-- a queued attack by pressing it again or pressing Escape.
ReconcileQueue()
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
UpdateSpeeds()
for i = 1, 3 do
local bar = S.bars[ORDER[i]]
UpdateIcon(bar)
if bar.active and bar.speed <= 0 then StopBar(bar) end
end
Layout()
elseif event == "MODIFIER_STATE_CHANGED" then
SetPreview(ModifiersDown() and true or false)
elseif event == "PLAYER_DEAD" then
ClearQueue()
ResetAll()
elseif event == "PLAYER_ENTERING_WORLD" then
local _, class = UnitClass("player")
S.hunter = class == "HUNTER"
UpdateSpeeds()
RestorePosition()
end
if S.native then StopWaitingBars(); UpdateColors() end
end)
local events = {
"PLAYER_SWING", "PLAYER_SWING_RANGE_UPDATE", "PLAYER_TARGET_CHANGED",
"PLAYER_ENTER_COMBAT", "PLAYER_LEAVE_COMBAT",
"START_AUTOREPEAT_SPELL", "STOP_AUTOREPEAT_SPELL",
"PLAYER_REGEN_DISABLED", "PLAYER_REGEN_ENABLED",
"PLAYER_DEAD", "PLAYER_ENTERING_WORLD", "MODIFIER_STATE_CHANGED",
-- ClassicAPI v1.18.14+; the 1.12 combat events above cover older versions
"START_AUTOATTACK", "STOP_AUTOATTACK",
}
local utils = _G.C_EventUtils
for i = 1, table.getn(events) do
if not utils or not utils.IsEventValid or utils.IsEventValid(events[i]) then
S.events:RegisterEvent(events[i])
end
end
-- ClassicAPI's per-slot equipment event when present, else the 1.12 one
if utils and utils.IsEventValid and utils.IsEventValid("PLAYER_EQUIPMENT_CHANGED") then
S.events:RegisterEvent("PLAYER_EQUIPMENT_CHANGED")
else
S.events:RegisterEvent("UNIT_INVENTORY_CHANGED")
end
local _, class = UnitClass("player")
S.hunter = class == "HUNTER"
S.combat = UnitAffectingCombat("player") and true or false
UpdateSpeeds()
RestorePosition()
return true
end
-- The range event fires only on a change, so read the state once.
local function EnableRangeCheck(bar)
_G.C_SwingTimer.EnableRangeCheck(bar.swingType, true)
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")
events:RegisterEvent("ACTIONBAR_UPDATE_STATE")
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)
if not CreateCore() then return end
S.ranged = true
EnableRangeCheck(S.bars.ranged)
end