swingtimer rework

This commit is contained in:
Meow
2026-03-04 19:18:01 +01:00
parent 8f6b961b4a
commit bdfc13bb08
+394 -348
View File
@@ -2,43 +2,88 @@ pfUI:RegisterModule("swingtimer", "vanilla:tbc", function ()
local rawborder, border = GetBorderSize() local rawborder, border = GetBorderSize()
-- HitInfo flags (EVENTS.md) -- HitInfo flags (EVENTS.md)
local HITINFO_LEFTSWING = 4 -- 0x4: Off-hand attack local HITINFO_LEFTSWING = 4 -- 4: Off-hand attack
local HITINFO_NOACTION = 65536 -- 0x10000: server did not advance the swing clock local HITINFO_NOACTION = 65536 -- 65536: server did not advance the swing clock
-- SPELL_QUEUE_EVENT codes (EVENTS.md) -- SPELL_QUEUE_EVENT codes (EVENTS.md)
local ON_SWING_QUEUED = 0 local ON_SWING_QUEUED = 0
local ON_SWING_QUEUE_POPPED = 1 local ON_SWING_QUEUE_POPPED = 1
-- Swing state -- Countdown timers (seconds remaining, SP_SwingTimer style)
local swingState = { -- Counting down from speed -> 0. Reset to speed on each confirmed swing.
mainhand = { speed = 0, nextSwing = 0, swinging = false }, local mhTimer = 0
offhand = { speed = 0, nextSwing = 0, swinging = false }, local mhTimerMax = 1
ranged = { speed = 0, nextSwing = 0, swinging = false }, local ohTimer = 0
} local ohTimerMax = 1
local raTimer = 0
local raTimerMax = 1
-- Ranged spell IDs that trigger the ranged swing timer (replaces MH) -- Weapon speeds (seconds), updated via UnitAttackSpeed / UnitRangedDamage
local mhSpeed = 0
local ohSpeed = 0
local raSpeed = 0
-- Whether each bar should be shown at all
local mhActive = false
local ohActive = false
local raActive = false
-- Whether auto-attack is currently toggled on
local autoAttackActive = false
-- Tracks spellId from SPELL_START_SELF (cast-time spell in progress)
-- Used in SPELL_GO to detect cast-time resets vs instants
local pendingCastSpellId = nil
-- Ranged spell IDs
local RANGED_SPELLIDS = { local RANGED_SPELLIDS = {
[75] = true, -- Auto Shot (Hunter) [75] = true, -- Auto Shot (Hunter)
[2764] = true, -- Throw (Warrior/Rogue) [2764] = true, -- Throw (Warrior/Rogue)
} }
-- Create container frame -- Slam: has cast time but does NOT reset the swing timer (just delays it).
pfUI.swingtimer = CreateFrame("Frame", "pfSwingTimer", UIParent) -- We explicitly ignore these in SPELL_GO so they fall through to no-op.
pfUI.swingtimer:SetFrameStrata("MEDIUM") local slamSpellIDs = {
pfUI.swingtimer:Hide() [1464] = true, [8820] = true, [11604] = true, [11605] = true,
}
-- Read config once at load into locals -- SPELL_ATTR_ON_NEXT_SWING (bit 2, value 4): spell replaces next auto-attack swing.
-- Covers Raptor Strike, Maul, Mongoose Bite, Holy Strike, etc. automatically.
-- Cache results to avoid repeated GetSpellRec calls per SPELL_GO.
local ATTR_ON_NEXT_SWING = 4
local onSwingCache = {} -- [spellId] = true/false
local function IsOnSwingSpell(spellId)
if onSwingCache[spellId] ~= nil then return onSwingCache[spellId] end
local rec = GetSpellRec(spellId)
local result = rec and bit.band(rec.attributes, ATTR_ON_NEXT_SWING) ~= 0 or false
onSwingCache[spellId] = result
return result
end
-- Heroic Strike spell IDs (all ranks)
local hsSpellIDs = {
[78] = true, [284] = true, [285] = true, [1608] = true,
[11564] = true, [11565] = true, [11566] = true, [11567] = true,
}
-- Cleave spell IDs (all ranks)
local cleaveSpellIDs = {
[845] = true, [7369] = true, [11608] = true, [11609] = true,
[20569] = true,
}
-- Read config
local sw_width = tonumber(C.unitframes.swingtimerwidth) or 200 local sw_width = tonumber(C.unitframes.swingtimerwidth) or 200
local sw_height = tonumber(C.unitframes.swingtimerheight) or 12 local sw_height = tonumber(C.unitframes.swingtimerheight) or 12
local sw_texture = C.unitframes.swingtimertexture or "Interface\\AddOns\\pfUI\\img\\bar" local sw_texture = C.unitframes.swingtimertexture or "Interface\\AddOns\\pfUI\\img\\bar"
local sw_showtext = C.unitframes.swingtimertext ~= "0" local sw_showtext = C.unitframes.swingtimertext ~= "0"
local sw_showlabel = C.unitframes.swingtimerlabel ~= "0" local sw_showlabel = C.unitframes.swingtimerlabel ~= "0"
local sw_showoh = C.unitframes.swingtimeroffhand ~= "0" local sw_showoh = C.unitframes.swingtimeroffhand ~= "0"
local sw_showranged = C.unitframes.swingtimerranged ~= "0" local sw_showranged = C.unitframes.swingtimerranged ~= "0"
local sw_fontsize = tonumber(C.unitframes.swingtimerfontsize) or 12 local sw_fontsize = tonumber(C.unitframes.swingtimerfontsize) or 12
local sw_hsqueue = C.unitframes.swingtimerhsqueue ~= "0" local sw_hsqueue = C.unitframes.swingtimerhsqueue ~= "0"
local sw_showspeed = C.unitframes.swingtimerattackspeed == "1"
-- Parse color strings "r,g,b,a" into components
local function ParseColor(str, dr, dg, db, da) local function ParseColor(str, dr, dg, db, da)
if not str or str == "" then return dr, dg, db, da end if not str or str == "" then return dr, dg, db, da end
local _, _, r, g, b, a = string.find(str, "([%d%.]+),([%d%.]+),([%d%.]+),([%d%.]+)") local _, _, r, g, b, a = string.find(str, "([%d%.]+),([%d%.]+),([%d%.]+),([%d%.]+)")
@@ -53,10 +98,21 @@ pfUI:RegisterModule("swingtimer", "vanilla:tbc", function ()
local raR, raG, raB, raA = ParseColor(C.unitframes.swingtimerrangedcolor, 0.3, 0.6, 1.0, 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 rwR, rwG, rwB, rwA = ParseColor(C.unitframes.swingtimerrangedwarncolor, 0.9, 0.0, 0.0, 1)
local isHunter = UnitClass("player") == "Hunter" local isHunter = UnitClass("player") == "Hunter"
-- Store default MH color for HS/Cleave restore
local mhDefaultR, mhDefaultG, mhDefaultB = mhR, mhG, mhB local mhDefaultR, mhDefaultG, mhDefaultB = mhR, mhG, mhB
-- HS/Cleave queue state
local hsQueued = false
local cleaveQueued = false
local isWarrior = false
local cachedHSSlots = {}
local cachedCleaveSlots = {}
local useSpellQueueEvent = false
-- Create container frame
pfUI.swingtimer = CreateFrame("Frame", "pfSwingTimer", UIParent)
pfUI.swingtimer:SetFrameStrata("MEDIUM")
pfUI.swingtimer:Hide()
-- Mainhand bar -- Mainhand bar
pfUI.swingtimer.mainhand = CreateFrame("StatusBar", "pfSwingTimerMainhand", UIParent) pfUI.swingtimer.mainhand = CreateFrame("StatusBar", "pfSwingTimerMainhand", UIParent)
pfUI.swingtimer.mainhand:SetPoint("CENTER", UIParent, "CENTER", 0, -100) pfUI.swingtimer.mainhand:SetPoint("CENTER", UIParent, "CENTER", 0, -100)
@@ -81,6 +137,13 @@ pfUI:RegisterModule("swingtimer", "vanilla:tbc", function ()
pfUI.swingtimer.mainhand.label:SetTextColor(0.8, 0.8, 0.8, 1) pfUI.swingtimer.mainhand.label:SetTextColor(0.8, 0.8, 0.8, 1)
pfUI.swingtimer.mainhand.label:SetText(sw_showlabel and "MH" or "") pfUI.swingtimer.mainhand.label:SetText(sw_showlabel and "MH" or "")
pfUI.swingtimer.mainhand.speed = pfUI.swingtimer.mainhand:CreateFontString("Status", "DIALOG", "GameFontNormal")
pfUI.swingtimer.mainhand.speed:SetPoint("LEFT", pfUI.swingtimer.mainhand, "RIGHT", 4, 0)
pfUI.swingtimer.mainhand.speed:SetFont(pfUI.font_default, sw_fontsize, "OUTLINE")
pfUI.swingtimer.mainhand.speed:SetTextColor(0.8, 0.8, 0.8, 1)
pfUI.swingtimer.mainhand.speed:SetText("")
if not sw_showspeed then pfUI.swingtimer.mainhand.speed:Hide() end
CreateBackdrop(pfUI.swingtimer.mainhand) CreateBackdrop(pfUI.swingtimer.mainhand)
CreateBackdropShadow(pfUI.swingtimer.mainhand) CreateBackdropShadow(pfUI.swingtimer.mainhand)
@@ -111,16 +174,13 @@ pfUI:RegisterModule("swingtimer", "vanilla:tbc", function ()
CreateBackdrop(pfUI.swingtimer.offhand) CreateBackdrop(pfUI.swingtimer.offhand)
CreateBackdropShadow(pfUI.swingtimer.offhand) CreateBackdropShadow(pfUI.swingtimer.offhand)
-- Ranged bar (bow/gun/crossbow - triggered by SPELL_GO_SELF for Auto Shot / Throw) -- Ranged bar
-- Hunter uses a special "close from outside->in, open inside->out" animation
-- instead of a normal left->right StatusBar fill.
pfUI.swingtimer.ranged = CreateFrame("Frame", "pfSwingTimerRanged", UIParent) pfUI.swingtimer.ranged = CreateFrame("Frame", "pfSwingTimerRanged", UIParent)
pfUI.swingtimer.ranged:SetPoint("CENTER", UIParent, "CENTER", 0, -120) pfUI.swingtimer.ranged:SetPoint("CENTER", UIParent, "CENTER", 0, -120)
pfUI.swingtimer.ranged:SetWidth(sw_width) pfUI.swingtimer.ranged:SetWidth(sw_width)
pfUI.swingtimer.ranged:SetHeight(sw_height) pfUI.swingtimer.ranged:SetHeight(sw_height)
pfUI.swingtimer.ranged:Hide() pfUI.swingtimer.ranged:Hide()
-- Phase 1: left half, anchored to CENTER (right edge fixed), shrinks leftward = outside->in
pfUI.swingtimer.ranged.left = pfUI.swingtimer.ranged:CreateTexture(nil, "ARTWORK") pfUI.swingtimer.ranged.left = pfUI.swingtimer.ranged:CreateTexture(nil, "ARTWORK")
pfUI.swingtimer.ranged.left:SetTexture(sw_texture) pfUI.swingtimer.ranged.left:SetTexture(sw_texture)
pfUI.swingtimer.ranged.left:SetPoint("RIGHT", pfUI.swingtimer.ranged, "CENTER", 0, 0) pfUI.swingtimer.ranged.left:SetPoint("RIGHT", pfUI.swingtimer.ranged, "CENTER", 0, 0)
@@ -129,7 +189,6 @@ pfUI:RegisterModule("swingtimer", "vanilla:tbc", function ()
pfUI.swingtimer.ranged.left:SetTexCoord(0, 0.5, 0, 1) pfUI.swingtimer.ranged.left:SetTexCoord(0, 0.5, 0, 1)
pfUI.swingtimer.ranged.left:SetVertexColor(raR, raG, raB, raA) pfUI.swingtimer.ranged.left:SetVertexColor(raR, raG, raB, raA)
-- Phase 1: right half, anchored to CENTER (left edge fixed), shrinks rightward = outside->in
pfUI.swingtimer.ranged.right = pfUI.swingtimer.ranged:CreateTexture(nil, "ARTWORK") pfUI.swingtimer.ranged.right = pfUI.swingtimer.ranged:CreateTexture(nil, "ARTWORK")
pfUI.swingtimer.ranged.right:SetTexture(sw_texture) pfUI.swingtimer.ranged.right:SetTexture(sw_texture)
pfUI.swingtimer.ranged.right:SetPoint("LEFT", pfUI.swingtimer.ranged, "CENTER", 0, 0) pfUI.swingtimer.ranged.right:SetPoint("LEFT", pfUI.swingtimer.ranged, "CENTER", 0, 0)
@@ -138,7 +197,6 @@ pfUI:RegisterModule("swingtimer", "vanilla:tbc", function ()
pfUI.swingtimer.ranged.right:SetTexCoord(0.5, 1, 0, 1) pfUI.swingtimer.ranged.right:SetTexCoord(0.5, 1, 0, 1)
pfUI.swingtimer.ranged.right:SetVertexColor(raR, raG, raB, raA) pfUI.swingtimer.ranged.right:SetVertexColor(raR, raG, raB, raA)
-- Phase 2: warning color, anchored CENTER, grows outward
pfUI.swingtimer.ranged.warn = pfUI.swingtimer.ranged:CreateTexture(nil, "ARTWORK") pfUI.swingtimer.ranged.warn = pfUI.swingtimer.ranged:CreateTexture(nil, "ARTWORK")
pfUI.swingtimer.ranged.warn:SetTexture(sw_texture) pfUI.swingtimer.ranged.warn:SetTexture(sw_texture)
pfUI.swingtimer.ranged.warn:SetPoint("CENTER", pfUI.swingtimer.ranged, "CENTER", 0, 0) pfUI.swingtimer.ranged.warn:SetPoint("CENTER", pfUI.swingtimer.ranged, "CENTER", 0, 0)
@@ -163,41 +221,112 @@ pfUI:RegisterModule("swingtimer", "vanilla:tbc", function ()
CreateBackdrop(pfUI.swingtimer.ranged) CreateBackdrop(pfUI.swingtimer.ranged)
CreateBackdropShadow(pfUI.swingtimer.ranged) CreateBackdropShadow(pfUI.swingtimer.ranged)
-- Slam spell IDs (all ranks) - resets swing timer from now, not chained UpdateMovable(pfUI.swingtimer.mainhand)
local slamSpellIDs = { UpdateMovable(pfUI.swingtimer.ranged)
[1464] = true, [8820] = true, [11604] = true, [11605] = true,
}
-- HS/Cleave queue state -- OH weapon detection
local hsQueued = false local OH_WEAPON_TYPES = { [13]=true, [21]=true }
local cleaveQueued = false local function HasOffhandWeapon()
local isWarrior = false local l = GetInventoryItemLink("player", 17)
local cachedHSSlots = {} if not l then return false end
local cachedCleaveSlots = {} local _, _, id = string.find(l, "item:(%d+)")
local useSpellQueueEvent = false id = tonumber(id)
if not id then return false end
local s = GetItemStats and GetItemStats(id)
if not s then return false end
return OH_WEAPON_TYPES[s.inventoryType] == true
end
-- Heroic Strike spell IDs (all ranks) local function UpdateWeaponSpeeds()
local hsSpellIDs = { local ms, os = UnitAttackSpeed("player")
[78] = true, [284] = true, [285] = true, [1608] = true, mhSpeed = (ms and ms > 0) and ms or mhSpeed
[11564] = true, [11565] = true, [11566] = true, [11567] = true, ohSpeed = (HasOffhandWeapon() and os and os > 0) and os or 0
[25286] = true, local rs = UnitRangedDamage("player")
} raSpeed = (rs and rs > 0) and rs or 0
-- Cleave spell IDs (all ranks) end
local cleaveSpellIDs = {
[845] = true, [7369] = true, [11608] = true, [11609] = true,
[20569] = true,
}
-- Reset MH countdown to full speed (server confirmed swing)
local function ResetMH()
UpdateWeaponSpeeds()
pfUI.swingtimer.mhGraceAt = nil -- cancel any pending hide
mhTimerMax = mhSpeed
mhTimer = mhSpeed
mhActive = true
pfUI.swingtimer.mainhand:Show()
pfUI.swingtimer:Show()
end
-- Reset OH countdown to full speed
local function ResetOH()
UpdateWeaponSpeeds()
if ohSpeed <= 0 then return end
pfUI.swingtimer.ohGraceAt = nil -- cancel any pending hide
ohTimerMax = ohSpeed
ohTimer = ohSpeed
ohActive = true
if sw_showoh then pfUI.swingtimer.offhand:Show() end
pfUI.swingtimer:Show()
end
-- Reset ranged countdown
local function ResetRanged()
if not sw_showranged then return end
UpdateWeaponSpeeds()
if raSpeed <= 0 then return end
-- Ranged replaces MH bar
mhActive = false
pfUI.swingtimer.mainhand:Hide()
raTimerMax = raSpeed
raTimer = raSpeed
raActive = true
if isHunter then
pfUI.swingtimer.ranged.left:ClearAllPoints()
pfUI.swingtimer.ranged.left:SetPoint("RIGHT", pfUI.swingtimer.ranged, "CENTER", 0, 0)
pfUI.swingtimer.ranged.left:SetWidth(sw_width / 2)
pfUI.swingtimer.ranged.left:SetTexCoord(0, 0.5, 0, 1)
pfUI.swingtimer.ranged.right:SetWidth(sw_width / 2)
pfUI.swingtimer.ranged.right:SetTexCoord(0.5, 1, 0, 1)
else
pfUI.swingtimer.ranged.left:ClearAllPoints()
pfUI.swingtimer.ranged.left:SetPoint("TOPLEFT", pfUI.swingtimer.ranged, "TOPLEFT", 0, 0)
pfUI.swingtimer.ranged.left:SetWidth(0.1)
pfUI.swingtimer.ranged.left:Hide()
pfUI.swingtimer.ranged.left:SetTexCoord(0, 0, 0, 1)
pfUI.swingtimer.ranged.right:SetWidth(0.1)
pfUI.swingtimer.ranged.right:Hide()
end
pfUI.swingtimer.ranged.left:SetVertexColor(raR, raG, raB, raA)
pfUI.swingtimer.ranged.right:SetVertexColor(raR, raG, raB, raA)
pfUI.swingtimer.ranged.warn:SetWidth(1)
pfUI.swingtimer.ranged.warn:Hide()
pfUI.swingtimer.ranged:Show()
pfUI.swingtimer:Show()
end
local function ResetAll()
mhActive = false
ohActive = false
raActive = false
mhTimer = 0
ohTimer = 0
raTimer = 0
pfUI.swingtimer.mhGraceAt = nil
pfUI.swingtimer.ohGraceAt = nil
pfUI.swingtimer.mainhand:Hide()
pfUI.swingtimer.offhand:Hide()
pfUI.swingtimer.ranged:Hide()
pfUI.swingtimer:Hide()
end
-- HS/Cleave helpers
local function RebuildQueueSlotCache() local function RebuildQueueSlotCache()
if not isWarrior or not sw_hsqueue or useSpellQueueEvent then return end if not isWarrior or not sw_hsqueue or useSpellQueueEvent then return end
cachedHSSlots = {} cachedHSSlots = {}
cachedCleaveSlots = {} cachedCleaveSlots = {}
for slot = 1, 120 do for slot = 1, 120 do
local tex = GetActionTexture(slot) local tex = GetActionTexture(slot)
local name = GetActionText(slot) local name = GetActionText(slot)
if tex then if tex then
if string.find(tex, "Ability_Rogue_Ambush") then if string.find(tex, "Ability_Rogue_Ambush") then
table.insert(cachedHSSlots, slot) table.insert(cachedHSSlots, slot)
@@ -205,7 +334,6 @@ pfUI:RegisterModule("swingtimer", "vanilla:tbc", function ()
table.insert(cachedCleaveSlots, slot) table.insert(cachedCleaveSlots, slot)
end end
end end
if name then if name then
local lower = string.lower(name) local lower = string.lower(name)
if lower == "heroic strike" or lower == "heroicstrike" or lower == "hs" then if lower == "heroic strike" or lower == "heroicstrike" or lower == "hs" then
@@ -226,157 +354,70 @@ pfUI:RegisterModule("swingtimer", "vanilla:tbc", function ()
local function IsHSOrCleaveQueued() local function IsHSOrCleaveQueued()
if not sw_hsqueue or not isWarrior then return false, false end if not sw_hsqueue or not isWarrior then return false, false end
if useSpellQueueEvent then if useSpellQueueEvent then return hsQueued, cleaveQueued end
return hsQueued, cleaveQueued
end
return CheckQueuedAction(cachedHSSlots), CheckQueuedAction(cachedCleaveSlots) return CheckQueuedAction(cachedHSSlots), CheckQueuedAction(cachedCleaveSlots)
end end
UpdateMovable(pfUI.swingtimer.mainhand) -- OnUpdate: countdown all timers with delta, then render
UpdateMovable(pfUI.swingtimer.ranged)
-- VERSION B TEST: HasOffhandWeapon() removed.
-- The original used GetItemInfo() to detect OH weapon type, but GetItemInfo()
-- returns nil on first login before the item cache is populated, causing
-- offhand.speed to stay 0. Now we just read offhandAttackTime directly,
-- same as the old working version.
-- Check offhand slot for an actual weapon. GetItemInfo may return nil on first
-- login (item cache not yet populated), so we return nil in that case to signal
-- "unknown" rather than false, allowing the caller to keep the previous value.
-- inventoryType 13 = INVTYPE_WEAPONOFFHAND, 21 = INVTYPE_WEAPON (one-hand, dual wieldable)
-- Shields = 14, held-in-hand = 23, everything else = no swing
local OH_WEAPON_TYPES = { [13]=true, [21]=true }
local function HasOffhandWeapon()
local l = GetInventoryItemLink("player", 17)
if not l then return false end
local _, _, id = string.find(l, "item:(%d+)")
id = tonumber(id)
if not id then return false end
local s = GetItemStats and GetItemStats(id)
if not s then return false end
return OH_WEAPON_TYPES[s.inventoryType] == true
end
local function UpdateWeaponSpeeds()
if not GetUnitField then return end
local mhSpeed = GetUnitField("player", "baseAttackTime")
local ohSpeed = GetUnitField("player", "offhandAttackTime")
if mhSpeed and mhSpeed > 0 then
swingState.mainhand.speed = mhSpeed / 1000
end
if HasOffhandWeapon() and ohSpeed and ohSpeed > 0 then
swingState.offhand.speed = ohSpeed / 1000
else
swingState.offhand.speed = 0
end
local raSpeed = GetUnitField("player", "rangedAttackTime")
if raSpeed and raSpeed > 0 then
swingState.ranged.speed = raSpeed / 1000
else
swingState.ranged.speed = 0
end
end
local function StartSwing(isOffhand, resetFromNow)
local now = GetTime()
-- always refresh speeds to catch haste buffs/debuffs
UpdateWeaponSpeeds()
-- dual-wield guard: if MH swing just started (<100ms ago) and this isn't
-- flagged as offhand, it's likely an OH event with missing flag
if not isOffhand and swingState.offhand.speed > 0 then
local mhAge = now - (swingState.mainhand.nextSwing - swingState.mainhand.speed)
if swingState.mainhand.swinging and mhAge > 0 and mhAge < 0.1 then
isOffhand = true
end
end
if isOffhand and swingState.offhand.speed > 0 then
-- resetFromNow: hard reset (e.g. Slam) - always start from now
-- otherwise: chain from previous nextSwing to avoid timer jumps
local base = (not resetFromNow and swingState.offhand.swinging and swingState.offhand.nextSwing > (now - 0.5)) and swingState.offhand.nextSwing or now
swingState.offhand.nextSwing = base + swingState.offhand.speed
swingState.offhand.swinging = true
if sw_showoh then pfUI.swingtimer.offhand:Show() end
else
-- resetFromNow: hard reset (e.g. Slam) - always start from now
-- otherwise: chain from previous nextSwing to avoid timer jumps on HS/normal swing
local base = (not resetFromNow and swingState.mainhand.swinging and swingState.mainhand.nextSwing > (now - 0.5)) and swingState.mainhand.nextSwing or now
swingState.mainhand.nextSwing = base + swingState.mainhand.speed
swingState.mainhand.swinging = true
pfUI.swingtimer.mainhand:Show()
end
pfUI.swingtimer:Show()
end
local function StartRangedSwing()
if not sw_showranged then return end
UpdateWeaponSpeeds()
if swingState.ranged.speed <= 0 then return end
-- Ranged replaces MH: cancel mainhand swing
swingState.mainhand.swinging = false
pfUI.swingtimer.mainhand:Hide()
swingState.ranged.nextSwing = GetTime() + swingState.ranged.speed
swingState.ranged.swinging = true
if isHunter then
-- Hunter: left/right halves anchored to CENTER, shrink outside->in
pfUI.swingtimer.ranged.left:ClearAllPoints()
pfUI.swingtimer.ranged.left:SetPoint("RIGHT", pfUI.swingtimer.ranged, "CENTER", 0, 0)
pfUI.swingtimer.ranged.left:SetWidth(sw_width / 2)
pfUI.swingtimer.ranged.left:SetTexCoord(0, 0.5, 0, 1)
pfUI.swingtimer.ranged.right:SetWidth(sw_width / 2)
pfUI.swingtimer.ranged.right:SetTexCoord(0.5, 1, 0, 1)
else
-- Non-Hunter: left anchored to TOPLEFT, grows left->right like MH/OH
pfUI.swingtimer.ranged.left:ClearAllPoints()
pfUI.swingtimer.ranged.left:SetPoint("TOPLEFT", pfUI.swingtimer.ranged, "TOPLEFT", 0, 0)
pfUI.swingtimer.ranged.left:SetWidth(0.1)
pfUI.swingtimer.ranged.left:Hide()
pfUI.swingtimer.ranged.left:SetTexCoord(0, 0, 0, 1)
pfUI.swingtimer.ranged.right:SetWidth(0.1)
pfUI.swingtimer.ranged.right:Hide()
end
pfUI.swingtimer.ranged.left:SetVertexColor(raR, raG, raB, raA)
pfUI.swingtimer.ranged.right:SetVertexColor(raR, raG, raB, raA)
pfUI.swingtimer.ranged.warn:SetWidth(1)
pfUI.swingtimer.ranged.warn:Hide()
pfUI.swingtimer.ranged:Show()
pfUI.swingtimer:Show()
end
local function ResetSwingTimers()
swingState.mainhand.swinging = false
swingState.offhand.swinging = false
swingState.ranged.swinging = false
pfUI.swingtimer.mainhand:Hide()
pfUI.swingtimer.offhand:Hide()
pfUI.swingtimer.ranged:Hide()
pfUI.swingtimer:Hide()
end
local swingThrottle = 0 local swingThrottle = 0
pfUI.swingtimer:SetScript("OnUpdate", function() pfUI.swingtimer:SetScript("OnUpdate", function()
swingThrottle = swingThrottle + arg1 swingThrottle = swingThrottle + arg1
if swingThrottle < 0.016 then return end if swingThrottle < 0.016 then return end
local delta = swingThrottle
swingThrottle = 0 swingThrottle = 0
local now = GetTime()
local anyActive = false
-- Execute delayed regen reset (only if combat hasn't resumed) -- Delayed out-of-combat reset
if swingState.regenResetPending and now >= swingState.regenResetPending then if pfUI.swingtimer.regenResetAt and GetTime() >= pfUI.swingtimer.regenResetAt then
swingState.regenResetPending = nil pfUI.swingtimer.regenResetAt = nil
ResetSwingTimers() ResetAll()
return
end end
local anyActive = false
-- Tick timers down. When a timer expires we give a short grace period before
-- hiding the bar, to bridge the 1-2 frame gap until AUTO_ATTACK_SELF arrives.
-- If AUTO_ATTACK_SELF arrives during the grace period it resets the timer
-- normally and the grace timer is cleared. If not (e.g. auto-attack was
-- turned off), the bar hides after the grace period ends.
local GRACE = 0.15 -- seconds to wait after timer hits 0 before hiding
if mhActive then
mhTimer = mhTimer - delta
if mhTimer <= 0 then
mhTimer = 0
if not pfUI.swingtimer.mhGraceAt then
pfUI.swingtimer.mhGraceAt = GetTime() + GRACE
elseif GetTime() >= pfUI.swingtimer.mhGraceAt then
pfUI.swingtimer.mhGraceAt = nil
mhActive = false
pfUI.swingtimer.mainhand:Hide()
end
end
end
if ohActive then
ohTimer = ohTimer - delta
if ohTimer <= 0 then
ohTimer = 0
if not pfUI.swingtimer.ohGraceAt then
pfUI.swingtimer.ohGraceAt = GetTime() + GRACE
elseif GetTime() >= pfUI.swingtimer.ohGraceAt then
pfUI.swingtimer.ohGraceAt = nil
ohActive = false
pfUI.swingtimer.offhand:Hide()
end
end
end
if raActive then
raTimer = raTimer - delta
if raTimer <= 0 then
raTimer = 0
raActive = false
pfUI.swingtimer.ranged:Hide()
end
end
-- HS/Cleave color
local curR, curG, curB = mhDefaultR, mhDefaultG, mhDefaultB local curR, curG, curB = mhDefaultR, mhDefaultG, mhDefaultB
if sw_hsqueue and isWarrior then if sw_hsqueue and isWarrior then
local hs, cl = IsHSOrCleaveQueued() local hs, cl = IsHSOrCleaveQueued()
@@ -387,104 +428,83 @@ pfUI:RegisterModule("swingtimer", "vanilla:tbc", function ()
end end
end end
if swingState.mainhand.swinging then -- Render MH
local remaining = swingState.mainhand.nextSwing - now if mhActive then
local progress = 1 - (mhTimer / mhTimerMax)
if remaining <= 0 then pfUI.swingtimer.mainhand:SetValue(progress)
swingState.mainhand.swinging = false pfUI.swingtimer.mainhand:SetStatusBarColor(curR, curG, curB, mhA)
pfUI.swingtimer.mainhand:Hide() if sw_showtext then
else pfUI.swingtimer.mainhand.text:SetText(string.format("%.1f", math.floor(mhTimer * 10) / 10))
local progress = 1 - (remaining / swingState.mainhand.speed)
pfUI.swingtimer.mainhand:SetValue(progress)
pfUI.swingtimer.mainhand:SetStatusBarColor(curR, curG, curB, mhA)
if sw_showtext then
pfUI.swingtimer.mainhand.text:SetText(string.format("%.1f", remaining))
end
anyActive = true
end end
if sw_showspeed and mhSpeed > 0 then
pfUI.swingtimer.mainhand.speed:SetText(string.format("%.2f", mhSpeed))
end
anyActive = true
end end
if sw_showoh and swingState.offhand.swinging then -- Render OH
local remaining = swingState.offhand.nextSwing - now if sw_showoh and ohActive then
local progress = 1 - (ohTimer / ohTimerMax)
if remaining <= 0 then pfUI.swingtimer.offhand:SetValue(progress)
swingState.offhand.swinging = false if sw_showtext then
pfUI.swingtimer.offhand:Hide() pfUI.swingtimer.offhand.text:SetText(string.format("%.1f", math.floor(ohTimer * 10) / 10))
else
local progress = 1 - (remaining / swingState.offhand.speed)
pfUI.swingtimer.offhand:SetValue(progress)
if sw_showtext then
pfUI.swingtimer.offhand.text:SetText(string.format("%.1f", remaining))
end
anyActive = true
end end
anyActive = true
elseif not sw_showoh then elseif not sw_showoh then
pfUI.swingtimer.offhand:Hide() pfUI.swingtimer.offhand:Hide()
end end
if sw_showranged and swingState.ranged.swinging then -- Render Ranged
local remaining = swingState.ranged.nextSwing - now if sw_showranged and raActive then
local remaining = raTimer
if remaining <= 0 then if isHunter then
swingState.ranged.swinging = false local DEADZONE = 0.5
pfUI.swingtimer.ranged:Hide() local halfW = sw_width / 2
else if remaining > DEADZONE then
if isHunter then local elapsed = raTimerMax - remaining
-- Hunter ranged animation: two phases local phase1dur = raTimerMax - DEADZONE
-- Phase 1 (speed-0.5s): bar visible full, shrinks from outside->in toward center (normal color) local p = elapsed / phase1dur
-- Phase 2 (0.5s): bar grows from center outward (warning color) local w = halfW * (1 - p)
local DEADZONE = 0.5
local halfW = sw_width / 2
if remaining > DEADZONE then
-- Phase 1: left/right halves shrink from outside->in toward center
local elapsed = swingState.ranged.speed - remaining
local phase1dur = swingState.ranged.speed - DEADZONE
local p = elapsed / phase1dur -- 0 = full, 1 = gone
local w = halfW * (1 - p)
if w < 1 then w = 1 end
pfUI.swingtimer.ranged.left:Show()
pfUI.swingtimer.ranged.left:SetWidth(w)
pfUI.swingtimer.ranged.left:SetTexCoord(0, (1 - p) * 0.5, 0, 1)
pfUI.swingtimer.ranged.left:SetVertexColor(raR, raG, raB, raA)
pfUI.swingtimer.ranged.right:Show()
pfUI.swingtimer.ranged.right:SetWidth(w)
pfUI.swingtimer.ranged.right:SetTexCoord(1 - (1 - p) * 0.5, 1, 0, 1)
pfUI.swingtimer.ranged.right:SetVertexColor(raR, raG, raB, raA)
pfUI.swingtimer.ranged.warn:Hide()
else
-- Phase 2: warning color grows from center->outside
local p = 1 - (remaining / DEADZONE) -- 0 = nothing, 1 = full
local w = sw_width * p
if w < 1 then w = 1 end
pfUI.swingtimer.ranged.left:Hide()
pfUI.swingtimer.ranged.right:Hide()
pfUI.swingtimer.ranged.warn:SetWidth(w)
pfUI.swingtimer.ranged.warn:Show()
end
else
-- Non-Hunter (Warrior Throw, Rogue): simple left->right fill like MH/OH
local progress = 1 - (remaining / swingState.ranged.speed)
local w = sw_width * progress
if w < 1 then w = 1 end if w < 1 then w = 1 end
pfUI.swingtimer.ranged.left:Show() pfUI.swingtimer.ranged.left:Show()
pfUI.swingtimer.ranged.left:SetWidth(w) pfUI.swingtimer.ranged.left:SetWidth(w)
pfUI.swingtimer.ranged.left:SetTexCoord(0, progress, 0, 1) pfUI.swingtimer.ranged.left:SetTexCoord(0, (1 - p) * 0.5, 0, 1)
pfUI.swingtimer.ranged.right:Hide() pfUI.swingtimer.ranged.left:SetVertexColor(raR, raG, raB, raA)
pfUI.swingtimer.ranged.right:Show()
pfUI.swingtimer.ranged.right:SetWidth(w)
pfUI.swingtimer.ranged.right:SetTexCoord(1 - (1 - p) * 0.5, 1, 0, 1)
pfUI.swingtimer.ranged.right:SetVertexColor(raR, raG, raB, raA)
pfUI.swingtimer.ranged.warn:Hide() pfUI.swingtimer.ranged.warn:Hide()
else
local p = 1 - (remaining / DEADZONE)
local w = sw_width * p
if w < 1 then w = 1 end
pfUI.swingtimer.ranged.left:Hide()
pfUI.swingtimer.ranged.right:Hide()
pfUI.swingtimer.ranged.warn:SetWidth(w)
pfUI.swingtimer.ranged.warn:Show()
end end
if sw_showtext then if sw_showtext then
if isHunter and remaining <= 0.5 then if remaining <= 0.5 then
pfUI.swingtimer.ranged.text:SetText(string.format("%.1f", remaining)) pfUI.swingtimer.ranged.text:SetText(string.format("%.1f", math.floor(remaining * 10) / 10))
elseif isHunter then
-- Show time until deadzone starts, not full remaining
pfUI.swingtimer.ranged.text:SetText(string.format("%.1f", remaining - 0.5))
else else
pfUI.swingtimer.ranged.text:SetText(string.format("%.1f", remaining)) pfUI.swingtimer.ranged.text:SetText(string.format("%.1f", math.floor((remaining - 0.5) * 10) / 10))
end end
end end
anyActive = true else
local progress = 1 - (remaining / raTimerMax)
local w = sw_width * progress
if w < 1 then w = 1 end
pfUI.swingtimer.ranged.left:Show()
pfUI.swingtimer.ranged.left:SetWidth(w)
pfUI.swingtimer.ranged.left:SetTexCoord(0, progress, 0, 1)
pfUI.swingtimer.ranged.right:Hide()
pfUI.swingtimer.ranged.warn:Hide()
if sw_showtext then
pfUI.swingtimer.ranged.text:SetText(string.format("%.1f", math.floor(remaining * 10) / 10))
end
end end
anyActive = true
elseif not sw_showranged then elseif not sw_showranged then
pfUI.swingtimer.ranged:Hide() pfUI.swingtimer.ranged:Hide()
end end
@@ -498,36 +518,40 @@ pfUI:RegisterModule("swingtimer", "vanilla:tbc", function ()
end end
end) end)
local events = CreateFrame("Frame") -- SPELL_START_SELF: fires only for cast-time spells, never instants
events:RegisterEvent("AUTO_ATTACK_SELF") local spellStartFrame = CreateFrame("Frame")
events:RegisterEvent("AUTO_ATTACK_OTHER") spellStartFrame:RegisterEvent("SPELL_START_SELF")
events:RegisterEvent("PLAYER_ENTERING_WORLD") spellStartFrame:SetScript("OnEvent", function()
events:RegisterEvent("UNIT_INVENTORY_CHANGED") pendingCastSpellId = arg1
events:RegisterEvent("PLAYER_REGEN_DISABLED") end)
events:RegisterEvent("PLAYER_REGEN_ENABLED")
events:RegisterEvent("ACTIONBAR_SLOT_CHANGED")
events:RegisterEvent("UNIT_DIED")
events:RegisterEvent("SPELL_QUEUE_EVENT")
-- Use libdebuff SPELL_GO_SELF hook instead of registering the event separately -- SPELL_GO hook via libdebuff
pfUI.libdebuff_spell_go_hooks = pfUI.libdebuff_spell_go_hooks or {} pfUI.libdebuff_spell_go_hooks = pfUI.libdebuff_spell_go_hooks or {}
pfUI.libdebuff_spell_go_hooks["swingtimer"] = function(spellId) pfUI.libdebuff_spell_go_hooks["swingtimer"] = function(spellId)
if RANGED_SPELLIDS[spellId] then if RANGED_SPELLIDS[spellId] then
StartRangedSwing() ResetRanged()
elseif slamSpellIDs[spellId] then elseif slamSpellIDs[spellId] then
-- Slam resets the swing timer hard from now (not chained from previous nextSwing) -- Slam delays auto-attack but does NOT reset the swing timer. Ignore.
pendingCastSpellId = nil
return
elseif hsSpellIDs[spellId] or IsOnSwingSpell(spellId) then
hsQueued = false; cleaveQueued = false hsQueued = false; cleaveQueued = false
StartSwing(false, true) ResetMH()
elseif hsSpellIDs[spellId] then
hsQueued = false; cleaveQueued = false
StartSwing(false)
elseif cleaveSpellIDs[spellId] then elseif cleaveSpellIDs[spellId] then
hsQueued = false; cleaveQueued = false hsQueued = false; cleaveQueued = false
StartSwing(false) ResetMH()
else
-- Only reset for cast-time spells (signaled by SPELL_START_SELF)
if mhActive and mhSpeed > 0 and pendingCastSpellId == spellId then
UpdateWeaponSpeeds()
mhTimerMax = mhSpeed
mhTimer = mhSpeed
end
end end
pendingCastSpellId = nil
end end
-- Use libdebuff SPELL_CAST_EVENT hook instead of registering the event separately -- SPELL_CAST_EVENT hook: HS/Cleave queue tracking
pfUI.libdebuff_spell_cast_hooks = pfUI.libdebuff_spell_cast_hooks or {} pfUI.libdebuff_spell_cast_hooks = pfUI.libdebuff_spell_cast_hooks or {}
pfUI.libdebuff_spell_cast_hooks["swingtimer"] = function(success, spellId) pfUI.libdebuff_spell_cast_hooks["swingtimer"] = function(success, spellId)
if success ~= 1 then return end if success ~= 1 then return end
@@ -540,60 +564,84 @@ pfUI:RegisterModule("swingtimer", "vanilla:tbc", function ()
local playerGUID = nil local playerGUID = nil
local events = CreateFrame("Frame")
events:RegisterEvent("AUTO_ATTACK_SELF")
events:RegisterEvent("AUTO_ATTACK_OTHER")
events:RegisterEvent("PLAYER_ENTERING_WORLD")
events:RegisterEvent("UNIT_INVENTORY_CHANGED")
events:RegisterEvent("PLAYER_REGEN_DISABLED")
events:RegisterEvent("PLAYER_REGEN_ENABLED")
events:RegisterEvent("ACTIONBAR_SLOT_CHANGED")
events:RegisterEvent("UNIT_DIED")
events:RegisterEvent("SPELL_QUEUE_EVENT")
events:RegisterEvent("START_AUTOATTACK")
events:RegisterEvent("STOP_AUTOATTACK")
events:SetScript("OnEvent", function() events:SetScript("OnEvent", function()
if event == "AUTO_ATTACK_SELF" then if event == "AUTO_ATTACK_SELF" then
local hitInfo = arg4 or 0 local hitInfo = arg4 or 0
local isOffhand = bit.band(hitInfo, HITINFO_LEFTSWING) ~= 0 local isOffhand = bit.band(hitInfo, HITINFO_LEFTSWING) ~= 0
local noAction = bit.band(hitInfo, HITINFO_NOACTION) ~= 0 local noAction = bit.band(hitInfo, HITINFO_NOACTION) ~= 0
-- HITINFO_NOACTION: server did not advance the swing clock (extra attack), ignore DEFAULT_CHAT_FRAME:AddMessage(string.format("|cff00ffff[AA] hi=%d OH=%s noAct=%s|r", hitInfo, tostring(isOffhand), tostring(noAction)))
if noAction then return end if noAction then return end
-- Fallback extra attack detection: some servers (e.g. Turtle WoW) do not set -- Extra attack detection: if timer still has >20% remaining for that hand,
-- HITINFO_NOACTION for extra attacks (Sword Specialization, Windfury, Hand of Justice). -- the server did NOT reset the swing clock -> this is an extra attack, skip.
-- An extra attack fires a second AUTO_ATTACK_SELF while the current swing timer is -- Use 20% here (SP_SwingTimer's ShouldResetTimer threshold).
-- still running with significant time remaining. In that case, the swing clock was -- Exception: if timer is already at 0 (expired), always accept.
-- NOT reset by the server, so we must not reset our timer either. if isOffhand then
local now = GetTime() local pct = ohActive and (ohTimer / ohTimerMax) or 0
if not isOffhand and swingState.mainhand.swinging then if ohActive and ohTimer > 0 and pct > 0.20 then
local remaining = swingState.mainhand.nextSwing - now DEFAULT_CHAT_FRAME:AddMessage(string.format("|cffff4444[AA] OH extra-attack skip pct=%.0f%% t=%.3f|r", pct*100, ohTimer))
-- If more than 20% of swing speed remains, this is an extra attack, not a real swing return
if remaining > swingState.mainhand.speed * 0.2 then return end end
DEFAULT_CHAT_FRAME:AddMessage(string.format("|cff00ff88[AA] OH reset (was pct=%.0f%% t=%.3f)|r", pct*100, ohTimer))
ResetOH()
else
local pct = mhActive and (mhTimer / mhTimerMax) or 0
if mhActive and mhTimer > 0 and pct > 0.20 then
DEFAULT_CHAT_FRAME:AddMessage(string.format("|cffff4444[AA] MH extra-attack skip pct=%.0f%% t=%.3f|r", pct*100, mhTimer))
return
end
DEFAULT_CHAT_FRAME:AddMessage(string.format("|cff00ff88[AA] MH reset (was pct=%.0f%% t=%.3f)|r", pct*100, mhTimer))
ResetMH()
end end
if isOffhand and swingState.offhand.swinging then
local remaining = swingState.offhand.nextSwing - now
if remaining > swingState.offhand.speed * 0.2 then return end
end
StartSwing(isOffhand)
elseif event == "AUTO_ATTACK_OTHER" then elseif event == "AUTO_ATTACK_OTHER" then
-- arg1=attackerGuid, arg2=targetGuid, arg3=totalDamage, arg4=hitInfo, arg5=victimState -- Parry haste: enemy attacked the player and player parried
-- We want: someone attacked the player (targetGuid == playerGUID) and the player parried
local targetGuid = arg2 local targetGuid = arg2
if not targetGuid or not playerGUID then return end if not targetGuid or not playerGUID then return end
if targetGuid ~= playerGUID then return end if targetGuid ~= playerGUID then return end
local victimState = arg5 or 0 local victimState = arg5 or 0
-- VICTIMSTATE_PARRY = 3 -- VICTIMSTATE_PARRY = 3
-- Vanilla parry mechanic: resets swing timer to 60% of weapon speed, -- Vanilla: parry reduces the NEXT swing timer by 40% of weapon speed,
-- but only if the remaining time is more than 40% of weapon speed (otherwise no change) -- minimum 20% of weapon speed remaining (SP_SwingTimer approach)
if victimState == 3 then if victimState == 3 then
local now = GetTime() -- Apply to whichever swing comes next (smallest % remaining = closest to firing)
local speed = swingState.mainhand.speed if ohActive and ohSpeed > 0 and (ohTimer / ohTimerMax) < (mhTimer / mhTimerMax) then
local remaining = swingState.mainhand.nextSwing - now local minimum = ohSpeed * 0.20
local parryReset = speed * 0.6 if ohTimer > minimum then
local threshold = speed * 0.4 local reduct = ohSpeed * 0.40
-- Only apply reset if it would actually reduce the remaining time local before = ohTimer
if remaining > parryReset then ohTimer = ohTimer - reduct
swingState.mainhand.nextSwing = now + parryReset if ohTimer < minimum then ohTimer = minimum end
swingState.mainhand.swinging = true DEFAULT_CHAT_FRAME:AddMessage(string.format("|cffff9900[PARRY] OH %.3fs->%.3f min=%.3f spd=%.3f pct=%.0f%%|r", before, ohTimer, minimum, ohSpeed, (ohTimer/ohTimerMax)*100))
pfUI.swingtimer.mainhand:Show() else
pfUI.swingtimer:Show() DEFAULT_CHAT_FRAME:AddMessage(string.format("|cffaaaaaa[PARRY] OH no-op already<=min %.3f|r", ohTimer))
elseif remaining <= 0 and threshold > 0 then end
-- swing already fired but parry came in: reset to parryReset elseif mhActive and mhSpeed > 0 then
swingState.mainhand.nextSwing = now + parryReset local minimum = mhSpeed * 0.20
swingState.mainhand.swinging = true if mhTimer > minimum then
pfUI.swingtimer.mainhand:Show() local reduct = mhSpeed * 0.40
pfUI.swingtimer:Show() local before = mhTimer
mhTimer = mhTimer - reduct
if mhTimer < minimum then mhTimer = minimum end
DEFAULT_CHAT_FRAME:AddMessage(string.format("|cffff9900[PARRY] MH %.3f->%.3f min=%.3f spd=%.3f pct=%.0f%%|r", before, mhTimer, minimum, mhSpeed, (mhTimer/mhTimerMax)*100))
else
DEFAULT_CHAT_FRAME:AddMessage(string.format("|cffaaaaaa[PARRY] MH no-op already<=min %.3f|r", mhTimer))
end
else
DEFAULT_CHAT_FRAME:AddMessage(string.format("|cffff0000[PARRY] skip mhA=%s ohA=%s mhT=%.3f ohT=%.3f|r", tostring(mhActive), tostring(ohActive), mhTimer, ohTimer))
end end
end end
@@ -606,30 +654,33 @@ pfUI:RegisterModule("swingtimer", "vanilla:tbc", function ()
hsQueued = true; cleaveQueued = false hsQueued = true; cleaveQueued = false
elseif cleaveSpellIDs[spellId] then elseif cleaveSpellIDs[spellId] then
cleaveQueued = true; hsQueued = false cleaveQueued = true; hsQueued = false
else
end end
elseif eventCode == ON_SWING_QUEUE_POPPED then elseif eventCode == ON_SWING_QUEUE_POPPED then
hsQueued = false; cleaveQueued = false hsQueued = false; cleaveQueued = false
else
end end
elseif event == "START_AUTOATTACK" then
autoAttackActive = true
elseif event == "STOP_AUTOATTACK" then
autoAttackActive = false
elseif event == "PLAYER_ENTERING_WORLD" then elseif event == "PLAYER_ENTERING_WORLD" then
local _, class = UnitClass("player") local _, class = UnitClass("player")
isWarrior = (class == "WARRIOR") isWarrior = (class == "WARRIOR")
local guid = GetUnitGUID("player") playerGUID = GetUnitGUID("player")
playerGUID = guid
UpdateWeaponSpeeds() UpdateWeaponSpeeds()
RebuildQueueSlotCache() RebuildQueueSlotCache()
elseif event == "UNIT_INVENTORY_CHANGED" then elseif event == "UNIT_INVENTORY_CHANGED" then
if arg1 and arg1 ~= "player" then return end if arg1 and arg1 ~= "player" then return end
UpdateWeaponSpeeds() UpdateWeaponSpeeds()
if swingState.offhand.speed == 0 then if ohSpeed == 0 then
swingState.offhand.swinging = false ohActive = false
pfUI.swingtimer.offhand:Hide() pfUI.swingtimer.offhand:Hide()
end end
if swingState.ranged.speed == 0 then if raSpeed == 0 then
swingState.ranged.swinging = false raActive = false
pfUI.swingtimer.ranged:Hide() pfUI.swingtimer.ranged:Hide()
end end
@@ -637,22 +688,17 @@ pfUI:RegisterModule("swingtimer", "vanilla:tbc", function ()
RebuildQueueSlotCache() RebuildQueueSlotCache()
elseif event == "PLAYER_REGEN_DISABLED" then elseif event == "PLAYER_REGEN_DISABLED" then
swingState.regenResetPending = nil -- cancel delayed reset, combat resumed pfUI.swingtimer.regenResetAt = nil
UpdateWeaponSpeeds() UpdateWeaponSpeeds()
elseif event == "PLAYER_REGEN_ENABLED" then elseif event == "PLAYER_REGEN_ENABLED" then
-- Delay reset to avoid false resets between mobs in raids pfUI.swingtimer.regenResetAt = GetTime() + 5.0
-- If combat resumes within 2s (PLAYER_REGEN_DISABLED), cancel the reset
swingState.regenResetPending = GetTime() + 2.0
hsQueued = false hsQueued = false
cleaveQueued = false cleaveQueued = false
elseif event == "UNIT_DIED" then elseif event == "UNIT_DIED" then
-- Only reset if the player themselves died if arg1 and arg1 == playerGUID then
local guid = arg1 ResetAll()
if not guid then return end
if guid == playerGUID then
ResetSwingTimers()
end end
end end
end) end)