mirror of
https://github.com/brues-code/pfUI.git
synced 2026-11-03 17:36:56 +00:00
Each bar fills itself from a duration object of the swing, a text binding counts it down, and the marker with its glows rides the fill's edge. One C_Timer per bar ends the swing, runs the melee grace and switches a hunter's reload to the deadzone. Colors update on the events that change them, ACTIONBAR_UPDATE_STATE standing in for the per-tick IsCurrentAction reconcile; a bar waiting past its grace hides on STOP_AUTOATTACK or leaving combat. The three bar builders become one. The Swing Timer throttle setting only paced the OnUpdate, so it goes.
581 lines
22 KiB
Lua
581 lines
22 KiB
Lua
pfUI:RegisterModule("swingtimer", function ()
|
|
local rawborder, border = GetBorderSize()
|
|
|
|
-- SPELL_QUEUE_EVENT codes (EVENTS.md)
|
|
local ON_SWING_QUEUED = 0
|
|
local ON_SWING_QUEUE_POPPED = 1
|
|
|
|
-- seconds a melee bar stays full after its swing is due, to bridge the gap
|
|
-- until PLAYER_SWING arrives; it then hides unless auto attack goes on
|
|
local GRACE = 0.15
|
|
|
|
-- the last half second of a hunter's ranged swing, when moving cancels the shot
|
|
local DEADZONE = 0.5
|
|
|
|
-- Consolidate state into a table to avoid Lua 5.0 upvalue limit (32 max)
|
|
local S = {
|
|
mhSpeed = 0, ohSpeed = 0, raSpeed = 0,
|
|
autoAttackActive = false,
|
|
inCombat = false,
|
|
hsQueued = false, cleaveQueued = false, maulQueued = false,
|
|
hsSeenCurrent = false, cleaveSeenCurrent = false, maulSeenCurrent = false,
|
|
isWarrior = false,
|
|
isDruid = false,
|
|
cachedHSSlots = {}, cachedCleaveSlots = {}, cachedMaulSlots = {},
|
|
useSpellQueueEvent = false,
|
|
onSwingCache = {},
|
|
outOfRange = {}, -- by Enum.PlayerSwingType
|
|
}
|
|
|
|
local MAINHAND = Enum.PlayerSwingType.MainHand
|
|
local OFFHAND = Enum.PlayerSwingType.OffHand
|
|
local RANGED = Enum.PlayerSwingType.Ranged
|
|
|
|
-- On-next-swing spells replace the next auto-attack swing.
|
|
-- Covers Raptor Strike, Maul, Mongoose Bite, Holy Strike, etc. automatically.
|
|
local function IsOnSwingSpell(spellId)
|
|
if S.onSwingCache[spellId] ~= nil then return S.onSwingCache[spellId] end
|
|
local result = C_Spell.IsNextMeleeSpell(spellId)
|
|
S.onSwingCache[spellId] = result
|
|
return result
|
|
end
|
|
|
|
-- HS / Cleave / Maul are detected dynamically via IsOnSwingSpell (the
|
|
-- SPELL_ATTR_ON_NEXT_SWING bit) + name comparison against the rank-1
|
|
-- canonical names cached below — no per-rank ID maintenance.
|
|
|
|
-- Read config
|
|
local sw_width = tonumber(C.unitframes.swingtimerwidth) or 200
|
|
local sw_height = tonumber(C.unitframes.swingtimerheight) or 12
|
|
local sw_texture = C.unitframes.swingtimertexture or "Interface\\AddOns\\pfUI\\img\\bar"
|
|
local sw_showtext = C.unitframes.swingtimertext ~= "0"
|
|
local sw_showlabel = C.unitframes.swingtimerlabel ~= "0"
|
|
local sw_showoh = C.unitframes.swingtimeroffhand ~= "0"
|
|
local sw_showranged = C.unitframes.swingtimerranged ~= "0"
|
|
local sw_fontsize = tonumber(C.unitframes.swingtimerfontsize) or 12
|
|
local sw_hsqueue = C.unitframes.swingtimerhsqueue ~= "0"
|
|
local sw_showspeed = C.unitframes.swingtimerattackspeed == "1"
|
|
|
|
local function ParseColor(str, dr, dg, db, da)
|
|
if not str or str == "" then return dr, dg, db, da end
|
|
local _, _, r, g, b, a = string.find(str, "([%d%.]+),([%d%.]+),([%d%.]+),([%d%.]+)")
|
|
if r then
|
|
return tonumber(r) or dr, tonumber(g) or dg, tonumber(b) or db, tonumber(a) or da
|
|
end
|
|
return dr, dg, db, da
|
|
end
|
|
|
|
local mhR, mhG, mhB, mhA = ParseColor(C.unitframes.swingtimermhcolor, 0.8, 0.3, 0.3, 1)
|
|
local ohR, ohG, ohB, ohA = ParseColor(C.unitframes.swingtimerohcolor, 0.3, 0.8, 0.3, 1)
|
|
local raR, raG, raB, raA = ParseColor(C.unitframes.swingtimerrangedcolor, 0.3, 0.6, 1.0, 1)
|
|
local rwR, rwG, rwB, rwA = ParseColor(C.unitframes.swingtimerrangedwarncolor, 0.9, 0.0, 0.0, 1)
|
|
local orR, orG, orB = ParseColor(C.unitframes.swingtimeroutrangecolor, 0.5, 0.5, 0.5, 1)
|
|
local isHunter = UnitClassBase("player") == "HUNTER"
|
|
|
|
-- the countdown text, cut down to tenths
|
|
local countdown = C_StringUtil.CreateNumericRuleFormatter()
|
|
countdown:AddBreakpoint({ threshold = 0, step = 0.1, rounding = Enum.NumericRuleFormatRounding.Down, format = "%.1f" })
|
|
|
|
-- the container keeps the public API
|
|
pfUI.swingtimer = CreateFrame("Frame", "pfSwingTimer", UIParent)
|
|
pfUI.swingtimer:Hide()
|
|
|
|
-- A bar fills by itself from `swing`, the time span of the whole swing, and
|
|
-- its text binding counts it down. The marker and its glows ride the edge
|
|
-- of the fill. `shown` is whether the config lets the bar show at all.
|
|
local function CreateSwingBar(name, label, shown)
|
|
local bar = CreateFrame("StatusBar", name, UIParent)
|
|
bar:SetWidth(sw_width)
|
|
bar:SetHeight(sw_height)
|
|
bar:SetStatusBarTexture(sw_texture)
|
|
bar:Hide()
|
|
bar.shown = shown
|
|
|
|
bar.swing = C_DurationUtil.CreateDuration()
|
|
bar:SetTimerDuration(bar.swing, Enum.StatusBarInterpolation.Immediate, Enum.StatusBarTimerDirection.ElapsedTime)
|
|
|
|
bar.text = bar:CreateFontString("Status", "DIALOG", "GameFontNormal")
|
|
bar.text:SetPoint("CENTER", bar, "CENTER", 0, 0)
|
|
bar.text:SetFont(pfUI.font_default, sw_fontsize, "OUTLINE")
|
|
bar.text:SetTextColor(1, 1, 1, 1)
|
|
bar.text:SetShown(sw_showtext)
|
|
|
|
bar.timer = C_DurationUtil.CreateDurationTextBinding()
|
|
bar.timer:SetFontString(bar.text)
|
|
bar.timer:SetDuration(bar.swing)
|
|
bar.timer:SetFormatter(countdown)
|
|
|
|
bar.label = bar:CreateFontString("Status", "DIALOG", "GameFontNormal")
|
|
bar.label:SetPoint("RIGHT", bar, "LEFT", -4, 0)
|
|
bar.label:SetFont(pfUI.font_default, sw_fontsize, "OUTLINE")
|
|
bar.label:SetTextColor(0.8, 0.8, 0.8, 1)
|
|
bar.label:SetText(sw_showlabel and label or "")
|
|
|
|
bar.speed = bar:CreateFontString("Status", "DIALOG", "GameFontNormal")
|
|
bar.speed:SetPoint("LEFT", bar, "RIGHT", 4, 0)
|
|
bar.speed:SetFont(pfUI.font_default, sw_fontsize, "OUTLINE")
|
|
bar.speed:SetTextColor(0.8, 0.8, 0.8, 1)
|
|
bar.speed:SetShown(sw_showspeed)
|
|
|
|
CreateBackdrop(bar)
|
|
|
|
bar.edge = CreateFrame("Frame", nil, bar)
|
|
bar.edge:SetAllPoints(bar)
|
|
bar.edge:Hide()
|
|
|
|
bar.marker = bar.edge:CreateTexture(nil, "OVERLAY")
|
|
bar.marker:SetColorTexture(1, 1, 1, 1.0)
|
|
bar.marker:SetWidth(2)
|
|
bar.marker:SetHeight(sw_height)
|
|
bar.marker:SetPoint("CENTER", bar:GetStatusBarTexture(), "RIGHT")
|
|
|
|
-- Left glow: fades from transparent to white (right edge = marker)
|
|
bar.markerGlowL = bar.edge:CreateTexture(nil, "OVERLAY")
|
|
bar.markerGlowL:SetColorTexture(1, 1, 1, 1)
|
|
bar.markerGlowL:SetGradientAlpha("HORIZONTAL", 1, 1, 1, 0, 1, 1, 1, 0.35)
|
|
bar.markerGlowL:SetWidth(10)
|
|
bar.markerGlowL:SetHeight(sw_height)
|
|
bar.markerGlowL:SetPoint("RIGHT", bar.marker, "LEFT", 0, 0)
|
|
|
|
-- Right glow: fades from white to transparent (left edge = marker)
|
|
bar.markerGlowR = bar.edge:CreateTexture(nil, "OVERLAY")
|
|
bar.markerGlowR:SetColorTexture(1, 1, 1, 1)
|
|
bar.markerGlowR:SetGradientAlpha("HORIZONTAL", 1, 1, 1, 0.35, 1, 1, 1, 0)
|
|
bar.markerGlowR:SetWidth(10)
|
|
bar.markerGlowR:SetHeight(sw_height)
|
|
bar.markerGlowR:SetPoint("LEFT", bar.marker, "RIGHT", 0, 0)
|
|
|
|
CreateBackdropShadow(bar)
|
|
return bar
|
|
end
|
|
|
|
local mainhand = CreateSwingBar("pfSwingTimerMainhand", "MH", true)
|
|
mainhand:SetPoint("BOTTOM", UIParent, "CENTER", -189, 103)
|
|
mainhand.showmarker = true
|
|
|
|
local offhand = CreateSwingBar("pfSwingTimerOffhand", "OH", sw_showoh)
|
|
offhand:SetPoint("TOP", mainhand, "BOTTOM", 0, -4)
|
|
offhand.showmarker = true
|
|
|
|
-- Hunters fill from the center: the bar drains toward the middle during
|
|
-- the reload, then the auto shot deadzone grows back out in the warn color.
|
|
-- Each phase is a time span of its own on `phase`.
|
|
local ranged = CreateSwingBar("pfSwingTimerRanged", "Ra", sw_showranged)
|
|
ranged:SetPoint("BOTTOM", UIParent, "CENTER", -189, 83)
|
|
ranged:SetFillStyle(isHunter and "CENTER" or "STANDARD")
|
|
ranged.showmarker = not isHunter
|
|
if isHunter then
|
|
ranged.phase = C_DurationUtil.CreateDuration()
|
|
ranged.timer:SetDuration(ranged.phase)
|
|
end
|
|
|
|
pfUI.swingtimer.mainhand = mainhand
|
|
pfUI.swingtimer.offhand = offhand
|
|
pfUI.swingtimer.ranged = ranged
|
|
|
|
UpdateMovable(mainhand)
|
|
UpdateMovable(ranged)
|
|
|
|
-- OH weapon detection: slot 17 (off hand) accepts one-hand (13) and off-hand
|
|
-- weapons (22); main-hand-only (21) can never sit there.
|
|
local OH_WEAPON_TYPES = { [13]=true, [22]=true }
|
|
local function HasOffhandWeapon()
|
|
local invType = C_Item.GetItemInventoryType({ equipmentSlotIndex = INVSLOT_OFFHAND })
|
|
return OH_WEAPON_TYPES[invType] == true
|
|
end
|
|
|
|
local function UpdateWeaponSpeeds()
|
|
local ms, os = UnitAttackSpeed("player")
|
|
S.mhSpeed = (ms and ms > 0) and ms or S.mhSpeed
|
|
S.ohSpeed = (HasOffhandWeapon() and os and os > 0) and os or 0
|
|
local rs = UnitRangedDamage("player")
|
|
S.raSpeed = (rs and rs > 0) and rs or 0
|
|
end
|
|
|
|
-- HS/Cleave/Maul helpers. Canonical rank-1 spellIDs resolve to the
|
|
-- localized spell name once, so per-spell classification is rank- and
|
|
-- locale-independent without per-rank ID hardcoding (every rank of
|
|
-- Heroic Strike etc. returns the same name).
|
|
local HS_NAME = C_Spell.GetSpellName(78) -- Heroic Strike (Rank 1)
|
|
local CLEAVE_NAME = C_Spell.GetSpellName(845) -- Cleave (Rank 1)
|
|
local MAUL_NAME = C_Spell.GetSpellName(6807) -- Maul (Rank 1)
|
|
|
|
-- Classify an on-next-swing spell as "hs" / "cleave" / "maul" / nil.
|
|
-- IsOnSwingSpell gates the family (ATTR_ON_NEXT_SWING bit 0x04) so
|
|
-- unrelated spells return nil cheaply.
|
|
local function ClassifyOnSwingSpell(spellId)
|
|
if not IsOnSwingSpell(spellId) then return nil end
|
|
local name = C_Spell.GetSpellName(spellId)
|
|
if name == HS_NAME then return "hs"
|
|
elseif name == CLEAVE_NAME then return "cleave"
|
|
elseif name == MAUL_NAME then return "maul"
|
|
end
|
|
end
|
|
|
|
-- Set the queue flags from a ClassifyOnSwingSpell result. nil = no-op
|
|
-- (preserves prior flag state when the queued spell isn't one we
|
|
-- color-code, matching the legacy table-lookup behavior).
|
|
local function SetQueuedKind(kind)
|
|
if not kind then return end
|
|
S.hsQueued = (kind == "hs")
|
|
S.cleaveQueued = (kind == "cleave")
|
|
S.maulQueued = (kind == "maul")
|
|
S.hsSeenCurrent, S.cleaveSeenCurrent, S.maulSeenCurrent = false, false, false
|
|
end
|
|
|
|
local function RebuildQueueSlotCache()
|
|
if not sw_hsqueue then return end
|
|
S.cachedHSSlots = {}
|
|
S.cachedCleaveSlots = {}
|
|
S.cachedMaulSlots = {}
|
|
if not (S.isWarrior or S.isDruid) then return end
|
|
for slot = 1, 120 do
|
|
local kind, id = GetActionInfo(slot)
|
|
local name
|
|
if kind == "spell" then
|
|
name = C_Spell.GetSpellName(id)
|
|
elseif kind == "macro" then
|
|
name = GetMacroSpell(id)
|
|
end
|
|
if name == HS_NAME then
|
|
table.insert(S.cachedHSSlots, slot)
|
|
elseif name == CLEAVE_NAME then
|
|
table.insert(S.cachedCleaveSlots, slot)
|
|
elseif name == MAUL_NAME then
|
|
table.insert(S.cachedMaulSlots, slot)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function CheckQueuedAction(slotList)
|
|
for i = 1, table.getn(slotList) do
|
|
if IsCurrentAction(slotList[i]) then return true end
|
|
end
|
|
return false
|
|
end
|
|
|
|
-- Reconcile a stale event-driven queue flag against the client's current
|
|
-- action. Not every de-queue emits a SPELL_QUEUE pop — pressing Esc or
|
|
-- re-pressing to cancel an on-swing spell doesn't — so the flag alone stays
|
|
-- set. IsCurrentAction, which the client clears on cancel, is the reconciling
|
|
-- signal, but only after it has confirmed the ability as current at least once
|
|
-- (`seen`): a nampower-initiated cast may never flip IsCurrentAction, and must
|
|
-- keep its color until its own pop/resolve rather than be cleared early.
|
|
-- Returns the updated (queued, seen).
|
|
local function ReconcileQueued(queued, slots, seen)
|
|
if not queued then return false, false end
|
|
if CheckQueuedAction(slots) then return true, true end
|
|
if seen then return false, false end -- was current, now gone -> cancelled
|
|
return true, false -- never confirmed current -> keep
|
|
end
|
|
|
|
local function IsHSOrCleaveQueued()
|
|
if not sw_hsqueue or not S.isWarrior then return false, false end
|
|
if S.useSpellQueueEvent then
|
|
S.hsQueued, S.hsSeenCurrent =
|
|
ReconcileQueued(S.hsQueued, S.cachedHSSlots, S.hsSeenCurrent)
|
|
S.cleaveQueued, S.cleaveSeenCurrent =
|
|
ReconcileQueued(S.cleaveQueued, S.cachedCleaveSlots, S.cleaveSeenCurrent)
|
|
return S.hsQueued, S.cleaveQueued
|
|
end
|
|
return CheckQueuedAction(S.cachedHSSlots), CheckQueuedAction(S.cachedCleaveSlots)
|
|
end
|
|
|
|
-- The bar colors follow the queued on-next-swing ability, the range state
|
|
-- and the hunter deadzone. Runs on the events that change any of them;
|
|
-- ACTIONBAR_UPDATE_STATE covers the IsCurrentAction reconcile.
|
|
local function UpdateColors()
|
|
local r, g, b = mhR, mhG, mhB
|
|
if sw_hsqueue then
|
|
if S.isDruid and S.useSpellQueueEvent then
|
|
S.maulQueued, S.maulSeenCurrent =
|
|
ReconcileQueued(S.maulQueued, S.cachedMaulSlots, S.maulSeenCurrent)
|
|
end
|
|
if S.maulQueued and S.isDruid then
|
|
r, g, b = 1.0, 0.55, 0.0 -- orange for druid maul queue
|
|
elseif S.isWarrior then
|
|
local hs, cl = IsHSOrCleaveQueued()
|
|
if cl then
|
|
r, g, b = 0.2, 0.9, 0.2
|
|
elseif hs then
|
|
r, g, b = 0.9, 0.9, 0.2
|
|
end
|
|
end
|
|
end
|
|
if S.outOfRange[MAINHAND] then r, g, b = orR, orG, orB end
|
|
mainhand:SetStatusBarColor(r, g, b, mhA)
|
|
|
|
if S.outOfRange[OFFHAND] then
|
|
offhand:SetStatusBarColor(orR, orG, orB, ohA)
|
|
else
|
|
offhand:SetStatusBarColor(ohR, ohG, ohB, ohA)
|
|
end
|
|
|
|
local a
|
|
if ranged.warn then
|
|
r, g, b, a = rwR, rwG, rwB, rwA
|
|
else
|
|
r, g, b, a = raR, raG, raB, raA
|
|
end
|
|
if S.outOfRange[RANGED] then r, g, b = orR, orG, orB end
|
|
ranged:SetStatusBarColor(r, g, b, a)
|
|
end
|
|
|
|
local function StopBar(bar)
|
|
if bar.wake then bar.wake:Cancel() end
|
|
bar.wake, bar.active, bar.graced = nil, nil, nil
|
|
bar.timer:Disable()
|
|
bar:Hide()
|
|
end
|
|
|
|
-- a melee bar that is due waits for the next swing; it hides after the
|
|
-- grace unless auto attack goes on, or on the event that stops it later
|
|
local function MeleeGrace(bar)
|
|
bar.graced = true
|
|
if not S.inCombat or not S.autoAttackActive then StopBar(bar) end
|
|
end
|
|
|
|
local function MeleeDue(bar)
|
|
bar.edge:Hide()
|
|
bar.wake = C_Timer.NewTimer(GRACE, function() MeleeGrace(bar) end)
|
|
end
|
|
|
|
local function RangedDue(bar)
|
|
StopBar(bar)
|
|
end
|
|
|
|
-- the hunter's reload is over: the deadzone grows out in the warn color
|
|
local function HunterDeadzone(bar)
|
|
local finish = bar.swing:GetEndTime()
|
|
bar.phase:SetTimeFromEnd(finish, DEADZONE)
|
|
bar:SetTimerDuration(bar.phase, Enum.StatusBarInterpolation.Immediate, Enum.StatusBarTimerDirection.ElapsedTime)
|
|
bar.warn = true
|
|
UpdateColors()
|
|
bar.wake = C_Timer.NewTimer(finish - GetTime(), function() RangedDue(bar) end)
|
|
end
|
|
|
|
-- Restarts a bar from a PLAYER_SWING reset. `remaining` can be shorter than
|
|
-- the weapon speed (parry haste, ranged wind-up), so the bar length stays
|
|
-- the full swing and the fill starts part-way along.
|
|
local function StartBar(bar, remaining, speed, due)
|
|
if bar.wake then bar.wake:Cancel() end
|
|
local now = GetTime()
|
|
bar.swing:SetTimeFromEnd(now + remaining, math.max(speed, remaining))
|
|
bar.active, bar.graced, bar.warn = true, nil, nil
|
|
if speed > 0 then bar.speed:SetFormattedText("%.2f", speed) end
|
|
|
|
if bar.phase then
|
|
if remaining > DEADZONE then
|
|
bar.phase:SetTimeFromEnd(now + remaining - DEADZONE, math.max(speed, remaining) - DEADZONE)
|
|
bar:SetTimerDuration(bar.phase, Enum.StatusBarInterpolation.Immediate, Enum.StatusBarTimerDirection.RemainingTime)
|
|
bar.wake = C_Timer.NewTimer(remaining - DEADZONE, function() HunterDeadzone(bar) end)
|
|
else
|
|
HunterDeadzone(bar)
|
|
end
|
|
else
|
|
bar.wake = C_Timer.NewTimer(remaining, function() due(bar) end)
|
|
end
|
|
|
|
UpdateColors()
|
|
if bar.shown then
|
|
bar.edge:SetShown(bar.showmarker)
|
|
if sw_showtext then bar.timer:Enable() end
|
|
bar:Show()
|
|
end
|
|
end
|
|
|
|
local function StartMH(remaining)
|
|
UpdateWeaponSpeeds()
|
|
StartBar(mainhand, remaining, S.mhSpeed, MeleeDue)
|
|
end
|
|
|
|
local function StartOH(remaining)
|
|
UpdateWeaponSpeeds()
|
|
-- The off-hand attack time stays set with a shield equipped
|
|
if S.ohSpeed <= 0 then return end
|
|
StartBar(offhand, remaining, S.ohSpeed, MeleeDue)
|
|
end
|
|
|
|
local function StartRanged(remaining)
|
|
if not sw_showranged then return end
|
|
UpdateWeaponSpeeds()
|
|
if S.raSpeed <= 0 then return end
|
|
StartBar(ranged, remaining, S.raSpeed, RangedDue)
|
|
end
|
|
|
|
-- melee bars waiting past their grace hide once auto attack stops
|
|
local function StopWaitingBars()
|
|
if mainhand.graced then StopBar(mainhand) end
|
|
if offhand.graced then StopBar(offhand) end
|
|
end
|
|
|
|
local function ResetAll()
|
|
StopBar(mainhand)
|
|
StopBar(offhand)
|
|
StopBar(ranged)
|
|
end
|
|
|
|
local events = CreateFrame("Frame")
|
|
events:RegisterEvent("PLAYER_SWING")
|
|
events:RegisterEvent("PLAYER_SWING_RANGE_UPDATE")
|
|
events:RegisterEvent("PLAYER_ENTERING_WORLD")
|
|
events:RegisterEvent("PLAYER_EQUIPMENT_CHANGED") -- ClassicAPI: per-slot, equipment-only
|
|
events:RegisterEvent("PLAYER_REGEN_DISABLED")
|
|
events:RegisterEvent("PLAYER_REGEN_ENABLED")
|
|
events:RegisterEvent("ACTIONBAR_SLOT_CHANGED")
|
|
events:RegisterEvent("ACTIONBAR_UPDATE_STATE")
|
|
events:RegisterEvent("PLAYER_DEAD")
|
|
events:RegisterUnitEvent("UNIT_SPELLCAST_SENT", "player")
|
|
events:RegisterUnitEvent("UNIT_SPELLCAST_SUCCEEDED", "player")
|
|
events:RegisterEvent("SPELL_QUEUE_EVENT")
|
|
events:RegisterEvent("START_AUTOATTACK")
|
|
events:RegisterEvent("STOP_AUTOATTACK")
|
|
|
|
events:SetScript("OnEvent", function()
|
|
if event == "PLAYER_SWING" then
|
|
-- ClassicAPI tracks the server's swing clock (white hits, on-next-swing
|
|
-- abilities, swing-reset casts, parry haste, extra attacks, weapon
|
|
-- swaps) and hands over the seconds left until the next swing.
|
|
if arg2 == MAINHAND then
|
|
StartMH(arg1)
|
|
elseif arg2 == OFFHAND then
|
|
StartOH(arg1)
|
|
elseif arg2 == RANGED then
|
|
StartRanged(arg1)
|
|
end
|
|
|
|
elseif event == "PLAYER_SWING_RANGE_UPDATE" then
|
|
-- arg1 = swingType, arg2 = isInRange, arg3 = checksRange; no check
|
|
-- (no target, nothing to attack, no weapon) is not out of range
|
|
S.outOfRange[arg1] = arg3 and not arg2
|
|
UpdateColors()
|
|
|
|
elseif event == "UNIT_SPELLCAST_SENT" then
|
|
-- arg4 = spellID: an on-next-swing ability (HS / Cleave / Maul) queued
|
|
local kind = ClassifyOnSwingSpell(arg4)
|
|
if kind then
|
|
S.useSpellQueueEvent = true
|
|
SetQueuedKind(kind)
|
|
UpdateColors()
|
|
end
|
|
|
|
elseif event == "UNIT_SPELLCAST_SUCCEEDED" then
|
|
-- arg3 = spellID: the on-next-swing ability resolved, which consumes
|
|
-- the queue, so drop the queued color
|
|
if IsOnSwingSpell(arg3) then
|
|
S.hsQueued = false; S.cleaveQueued = false; S.maulQueued = false
|
|
UpdateColors()
|
|
end
|
|
|
|
elseif event == "SPELL_QUEUE_EVENT" then
|
|
local eventCode = arg1 or -1
|
|
local spellId = arg2 or 0
|
|
if eventCode == ON_SWING_QUEUED then
|
|
S.useSpellQueueEvent = true
|
|
SetQueuedKind(ClassifyOnSwingSpell(spellId))
|
|
elseif eventCode == ON_SWING_QUEUE_POPPED then
|
|
S.hsQueued = false; S.cleaveQueued = false; S.maulQueued = false
|
|
end
|
|
UpdateColors()
|
|
|
|
elseif event == "ACTIONBAR_UPDATE_STATE" then
|
|
if sw_hsqueue then UpdateColors() end
|
|
|
|
elseif event == "START_AUTOATTACK" then
|
|
S.autoAttackActive = true
|
|
|
|
elseif event == "STOP_AUTOATTACK" then
|
|
S.autoAttackActive = false
|
|
StopWaitingBars()
|
|
|
|
elseif event == "PLAYER_ENTERING_WORLD" then
|
|
local class = UnitClassBase("player")
|
|
S.isWarrior = (class == "WARRIOR")
|
|
S.isDruid = (class == "DRUID")
|
|
UpdateWeaponSpeeds()
|
|
RebuildQueueSlotCache()
|
|
|
|
elseif event == "PLAYER_EQUIPMENT_CHANGED" then
|
|
-- arg1 = changed slot; only weapon slots (main/off/ranged) affect swing speed
|
|
if not INVSLOTS_EQUIPABLE_IN_COMBAT[arg1] then return end
|
|
UpdateWeaponSpeeds()
|
|
if S.ohSpeed == 0 then StopBar(offhand) end
|
|
if S.raSpeed == 0 then StopBar(ranged) end
|
|
|
|
elseif event == "ACTIONBAR_SLOT_CHANGED" then
|
|
RebuildQueueSlotCache()
|
|
|
|
elseif event == "PLAYER_REGEN_DISABLED" then
|
|
S.inCombat = true
|
|
UpdateWeaponSpeeds()
|
|
|
|
elseif event == "PLAYER_REGEN_ENABLED" then
|
|
S.inCombat = false
|
|
S.hsQueued = false
|
|
S.cleaveQueued = false
|
|
S.maulQueued = false
|
|
UpdateColors()
|
|
StopWaitingBars()
|
|
|
|
elseif event == "PLAYER_DEAD" then
|
|
ResetAll()
|
|
end
|
|
end)
|
|
|
|
local function Remaining(bar)
|
|
return bar.active and bar.swing:GetRemainingDuration() or 0
|
|
end
|
|
|
|
local function Progress(bar)
|
|
return bar.active and bar.swing:GetElapsedPercent() or 0
|
|
end
|
|
|
|
-- -------------------------------------------------------------------------
|
|
-- Public API for external addons (e.g. SCRM)
|
|
-- Only available when the swingtimer module is loaded.
|
|
-- Usage:
|
|
-- if pfUI and pfUI.swingtimer and pfUI.swingtimer.api then
|
|
-- local api = pfUI.swingtimer.api
|
|
-- local mhTimer = api.GetMHTimer()
|
|
-- end
|
|
-- -------------------------------------------------------------------------
|
|
pfUI.swingtimer.api = {
|
|
-- Remaining time in seconds until next swing
|
|
GetMHTimer = function() return Remaining(mainhand) end,
|
|
GetOHTimer = function() return Remaining(offhand) end,
|
|
GetRangedTimer = function() return Remaining(ranged) end,
|
|
|
|
-- Weapon speed (full swing duration in seconds)
|
|
GetMHSpeed = function() return S.mhSpeed end,
|
|
GetOHSpeed = function() return S.ohSpeed end,
|
|
GetRangedSpeed = function() return S.raSpeed end,
|
|
|
|
-- Whether each timer is currently running
|
|
IsMHActive = function() return mainhand.active == true end,
|
|
IsOHActive = function() return offhand.active == true end,
|
|
IsRangedActive = function() return ranged.active == true end,
|
|
|
|
-- Swing progress 0.0 (just reset) to 1.0 (about to swing)
|
|
GetMHProgress = function() return Progress(mainhand) end,
|
|
GetOHProgress = function() return Progress(offhand) end,
|
|
GetRangedProgress = function() return Progress(ranged) end,
|
|
|
|
-- Warrior HS/Cleave queue state
|
|
IsHSQueued = function() return S.hsQueued end,
|
|
IsCleaveQueued = function() return S.cleaveQueued end,
|
|
IsMaulQueued = function() return S.maulQueued end,
|
|
}
|
|
|
|
-- the range event fires only on a change, so read the state once
|
|
local function EnableRangeCheck(swingType)
|
|
C_SwingTimer.EnableRangeCheck(swingType, true)
|
|
S.outOfRange[swingType] = C_SwingTimer.IsTargetWithinSwingRange(swingType) == false
|
|
end
|
|
EnableRangeCheck(MAINHAND)
|
|
if sw_showoh then EnableRangeCheck(OFFHAND) end
|
|
if sw_showranged then EnableRangeCheck(RANGED) end
|
|
|
|
UpdateWeaponSpeeds()
|
|
UpdateColors()
|
|
end)
|