mirror of
https://github.com/brues-code/pfUI.git
synced 2026-10-31 16:06:57 +00:00
C_SwingTimer checks the range for each shown bar, and PLAYER_SWING_RANGE_UPDATE turns a bar the new Out Of Range Color (grey by default, since the main-hand bar and the hunter dead zone are red) while its swing can't reach the current target. The melee check is the server's 2D reach; the ranged one covers the hunter's minimum range too. No check (no target, nothing to attack, no weapon) keeps the normal color.
807 lines
34 KiB
Lua
807 lines
34 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
|
|
|
|
-- Consolidate state into a table to avoid Lua 5.0 upvalue limit (32 max)
|
|
local S = {
|
|
mhTimer = 0, mhTimerMax = 1,
|
|
ohTimer = 0, ohTimerMax = 1,
|
|
raTimer = 0, raTimerMax = 1,
|
|
mhSpeed = 0, ohSpeed = 0, raSpeed = 0,
|
|
mhActive = false, ohActive = false, raActive = false,
|
|
lastMhMarkerX = -1, lastOhMarkerX = -1, lastRaMarkerX = -1,
|
|
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,
|
|
swingThrottle = 0,
|
|
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"
|
|
local mhDefaultR, mhDefaultG, mhDefaultB = mhR, mhG, mhB
|
|
|
|
|
|
|
|
-- Create container frame
|
|
pfUI.swingtimer = CreateFrame("Frame", "pfSwingTimer", UIParent)
|
|
pfUI.swingtimer:SetFrameStrata("MEDIUM")
|
|
pfUI.swingtimer:Hide()
|
|
|
|
-- Mainhand bar
|
|
pfUI.swingtimer.mainhand = CreateFrame("StatusBar", "pfSwingTimerMainhand", UIParent)
|
|
pfUI.swingtimer.mainhand:SetPoint("BOTTOM", UIParent, "CENTER", -189, 103)
|
|
pfUI.swingtimer.mainhand:SetWidth(sw_width)
|
|
pfUI.swingtimer.mainhand:SetHeight(sw_height)
|
|
pfUI.swingtimer.mainhand:SetMinMaxValues(0, 1)
|
|
pfUI.swingtimer.mainhand:SetValue(0)
|
|
pfUI.swingtimer.mainhand:SetStatusBarTexture(sw_texture)
|
|
pfUI.swingtimer.mainhand:SetStatusBarColor(mhR, mhG, mhB, mhA)
|
|
pfUI.swingtimer.mainhand:Hide()
|
|
|
|
pfUI.swingtimer.mainhand.text = pfUI.swingtimer.mainhand:CreateFontString("Status", "DIALOG", "GameFontNormal")
|
|
pfUI.swingtimer.mainhand.text:SetPoint("CENTER", pfUI.swingtimer.mainhand, "CENTER", 0, 0)
|
|
pfUI.swingtimer.mainhand.text:SetFont(pfUI.font_default, sw_fontsize, "OUTLINE")
|
|
pfUI.swingtimer.mainhand.text:SetTextColor(1, 1, 1, 1)
|
|
pfUI.swingtimer.mainhand.text:SetText("")
|
|
if not sw_showtext then pfUI.swingtimer.mainhand.text:Hide() end
|
|
|
|
pfUI.swingtimer.mainhand.label = pfUI.swingtimer.mainhand:CreateFontString("Status", "DIALOG", "GameFontNormal")
|
|
pfUI.swingtimer.mainhand.label:SetPoint("RIGHT", pfUI.swingtimer.mainhand, "LEFT", -4, 0)
|
|
pfUI.swingtimer.mainhand.label:SetFont(pfUI.font_default, sw_fontsize, "OUTLINE")
|
|
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.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)
|
|
|
|
|
|
pfUI.swingtimer.mainhand.marker = pfUI.swingtimer.mainhand:CreateTexture(nil, "OVERLAY")
|
|
pfUI.swingtimer.mainhand.marker:SetColorTexture(1, 1, 1, 1.0)
|
|
pfUI.swingtimer.mainhand.marker:SetWidth(2)
|
|
pfUI.swingtimer.mainhand.marker:SetHeight(sw_height)
|
|
pfUI.swingtimer.mainhand.marker:Hide()
|
|
|
|
-- Left glow: fades from transparent to white (right edge = marker)
|
|
pfUI.swingtimer.mainhand.markerGlowL = pfUI.swingtimer.mainhand:CreateTexture(nil, "OVERLAY")
|
|
pfUI.swingtimer.mainhand.markerGlowL:SetColorTexture(1, 1, 1, 1)
|
|
pfUI.swingtimer.mainhand.markerGlowL:SetGradientAlpha("HORIZONTAL", 1, 1, 1, 0, 1, 1, 1, 0.35)
|
|
pfUI.swingtimer.mainhand.markerGlowL:SetWidth(10)
|
|
pfUI.swingtimer.mainhand.markerGlowL:SetHeight(sw_height)
|
|
pfUI.swingtimer.mainhand.markerGlowL:Hide()
|
|
|
|
-- Right glow: fades from white to transparent (left edge = marker)
|
|
pfUI.swingtimer.mainhand.markerGlowR = pfUI.swingtimer.mainhand:CreateTexture(nil, "OVERLAY")
|
|
pfUI.swingtimer.mainhand.markerGlowR:SetColorTexture(1, 1, 1, 1)
|
|
pfUI.swingtimer.mainhand.markerGlowR:SetGradientAlpha("HORIZONTAL", 1, 1, 1, 0.35, 1, 1, 1, 0)
|
|
pfUI.swingtimer.mainhand.markerGlowR:SetWidth(10)
|
|
pfUI.swingtimer.mainhand.markerGlowR:SetHeight(sw_height)
|
|
pfUI.swingtimer.mainhand.markerGlowR:Hide()
|
|
|
|
CreateBackdropShadow(pfUI.swingtimer.mainhand)
|
|
|
|
-- Offhand bar
|
|
pfUI.swingtimer.offhand = CreateFrame("StatusBar", "pfSwingTimerOffhand", UIParent)
|
|
pfUI.swingtimer.offhand:SetPoint("TOP", pfUI.swingtimer.mainhand, "BOTTOM", 0, -4)
|
|
pfUI.swingtimer.offhand:SetWidth(sw_width)
|
|
pfUI.swingtimer.offhand:SetHeight(sw_height)
|
|
pfUI.swingtimer.offhand:SetMinMaxValues(0, 1)
|
|
pfUI.swingtimer.offhand:SetValue(0)
|
|
pfUI.swingtimer.offhand:SetStatusBarTexture(sw_texture)
|
|
pfUI.swingtimer.offhand:SetStatusBarColor(ohR, ohG, ohB, ohA)
|
|
pfUI.swingtimer.offhand:Hide()
|
|
|
|
pfUI.swingtimer.offhand.text = pfUI.swingtimer.offhand:CreateFontString("Status", "DIALOG", "GameFontNormal")
|
|
pfUI.swingtimer.offhand.text:SetPoint("CENTER", pfUI.swingtimer.offhand, "CENTER", 0, 0)
|
|
pfUI.swingtimer.offhand.text:SetFont(pfUI.font_default, sw_fontsize, "OUTLINE")
|
|
pfUI.swingtimer.offhand.text:SetTextColor(1, 1, 1, 1)
|
|
pfUI.swingtimer.offhand.text:SetText("")
|
|
if not sw_showtext then pfUI.swingtimer.offhand.text:Hide() end
|
|
|
|
pfUI.swingtimer.offhand.label = pfUI.swingtimer.offhand:CreateFontString("Status", "DIALOG", "GameFontNormal")
|
|
pfUI.swingtimer.offhand.label:SetPoint("RIGHT", pfUI.swingtimer.offhand, "LEFT", -4, 0)
|
|
pfUI.swingtimer.offhand.label:SetFont(pfUI.font_default, sw_fontsize, "OUTLINE")
|
|
pfUI.swingtimer.offhand.label:SetTextColor(0.8, 0.8, 0.8, 1)
|
|
pfUI.swingtimer.offhand.label:SetText(sw_showlabel and "OH" or "")
|
|
|
|
pfUI.swingtimer.offhand.speed = pfUI.swingtimer.offhand:CreateFontString("Status", "DIALOG", "GameFontNormal")
|
|
pfUI.swingtimer.offhand.speed:SetPoint("LEFT", pfUI.swingtimer.offhand, "RIGHT", 4, 0)
|
|
pfUI.swingtimer.offhand.speed:SetFont(pfUI.font_default, sw_fontsize, "OUTLINE")
|
|
pfUI.swingtimer.offhand.speed:SetTextColor(0.8, 0.8, 0.8, 1)
|
|
pfUI.swingtimer.offhand.speed:SetText("")
|
|
if not sw_showspeed then pfUI.swingtimer.offhand.speed:Hide() end
|
|
|
|
CreateBackdrop(pfUI.swingtimer.offhand)
|
|
|
|
|
|
pfUI.swingtimer.offhand.marker = pfUI.swingtimer.offhand:CreateTexture(nil, "OVERLAY")
|
|
pfUI.swingtimer.offhand.marker:SetColorTexture(1, 1, 1, 1.0)
|
|
pfUI.swingtimer.offhand.marker:SetWidth(2)
|
|
pfUI.swingtimer.offhand.marker:SetHeight(sw_height)
|
|
pfUI.swingtimer.offhand.marker:Hide()
|
|
|
|
-- Left glow: fades from transparent to white (right edge = marker)
|
|
pfUI.swingtimer.offhand.markerGlowL = pfUI.swingtimer.offhand:CreateTexture(nil, "OVERLAY")
|
|
pfUI.swingtimer.offhand.markerGlowL:SetColorTexture(1, 1, 1, 1)
|
|
pfUI.swingtimer.offhand.markerGlowL:SetGradientAlpha("HORIZONTAL", 1, 1, 1, 0, 1, 1, 1, 0.35)
|
|
pfUI.swingtimer.offhand.markerGlowL:SetWidth(10)
|
|
pfUI.swingtimer.offhand.markerGlowL:SetHeight(sw_height)
|
|
pfUI.swingtimer.offhand.markerGlowL:Hide()
|
|
|
|
-- Right glow: fades from white to transparent (left edge = marker)
|
|
pfUI.swingtimer.offhand.markerGlowR = pfUI.swingtimer.offhand:CreateTexture(nil, "OVERLAY")
|
|
pfUI.swingtimer.offhand.markerGlowR:SetColorTexture(1, 1, 1, 1)
|
|
pfUI.swingtimer.offhand.markerGlowR:SetGradientAlpha("HORIZONTAL", 1, 1, 1, 0.35, 1, 1, 1, 0)
|
|
pfUI.swingtimer.offhand.markerGlowR:SetWidth(10)
|
|
pfUI.swingtimer.offhand.markerGlowR:SetHeight(sw_height)
|
|
pfUI.swingtimer.offhand.markerGlowR:Hide()
|
|
|
|
CreateBackdropShadow(pfUI.swingtimer.offhand)
|
|
|
|
-- Ranged bar
|
|
-- 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.
|
|
pfUI.swingtimer.ranged = CreateFrame("StatusBar", "pfSwingTimerRanged", UIParent)
|
|
pfUI.swingtimer.ranged:SetPoint("BOTTOM", UIParent, "CENTER", -189, 83)
|
|
pfUI.swingtimer.ranged:SetWidth(sw_width)
|
|
pfUI.swingtimer.ranged:SetHeight(sw_height)
|
|
pfUI.swingtimer.ranged:SetMinMaxValues(0, 1)
|
|
pfUI.swingtimer.ranged:SetValue(0)
|
|
pfUI.swingtimer.ranged:SetStatusBarTexture(sw_texture)
|
|
pfUI.swingtimer.ranged:SetStatusBarColor(raR, raG, raB, raA)
|
|
pfUI.swingtimer.ranged:SetFillStyle(isHunter and "CENTER" or "STANDARD")
|
|
pfUI.swingtimer.ranged:Hide()
|
|
|
|
pfUI.swingtimer.ranged.text = pfUI.swingtimer.ranged:CreateFontString("Status", "DIALOG", "GameFontNormal")
|
|
pfUI.swingtimer.ranged.text:SetPoint("CENTER", pfUI.swingtimer.ranged, "CENTER", 0, 0)
|
|
pfUI.swingtimer.ranged.text:SetFont(pfUI.font_default, sw_fontsize, "OUTLINE")
|
|
pfUI.swingtimer.ranged.text:SetTextColor(1, 1, 1, 1)
|
|
pfUI.swingtimer.ranged.text:SetText("")
|
|
if not sw_showtext then pfUI.swingtimer.ranged.text:Hide() end
|
|
|
|
pfUI.swingtimer.ranged.label = pfUI.swingtimer.ranged:CreateFontString("Status", "DIALOG", "GameFontNormal")
|
|
pfUI.swingtimer.ranged.label:SetPoint("RIGHT", pfUI.swingtimer.ranged, "LEFT", -4, 0)
|
|
pfUI.swingtimer.ranged.label:SetFont(pfUI.font_default, sw_fontsize, "OUTLINE")
|
|
pfUI.swingtimer.ranged.label:SetTextColor(0.8, 0.8, 0.8, 1)
|
|
pfUI.swingtimer.ranged.label:SetText(sw_showlabel and "Ra" or "")
|
|
|
|
pfUI.swingtimer.ranged.speed = pfUI.swingtimer.ranged:CreateFontString("Status", "DIALOG", "GameFontNormal")
|
|
pfUI.swingtimer.ranged.speed:SetPoint("LEFT", pfUI.swingtimer.ranged, "RIGHT", 4, 0)
|
|
pfUI.swingtimer.ranged.speed:SetFont(pfUI.font_default, sw_fontsize, "OUTLINE")
|
|
pfUI.swingtimer.ranged.speed:SetTextColor(0.8, 0.8, 0.8, 1)
|
|
pfUI.swingtimer.ranged.speed:SetText("")
|
|
if not sw_showspeed then pfUI.swingtimer.ranged.speed:Hide() end
|
|
|
|
CreateBackdrop(pfUI.swingtimer.ranged)
|
|
|
|
|
|
pfUI.swingtimer.ranged.marker = pfUI.swingtimer.ranged:CreateTexture(nil, "OVERLAY")
|
|
pfUI.swingtimer.ranged.marker:SetColorTexture(1, 1, 1, 1.0)
|
|
pfUI.swingtimer.ranged.marker:SetWidth(2)
|
|
pfUI.swingtimer.ranged.marker:SetHeight(sw_height)
|
|
pfUI.swingtimer.ranged.marker:Hide()
|
|
|
|
-- Left glow: fades from transparent to white (right edge = marker)
|
|
pfUI.swingtimer.ranged.markerGlowL = pfUI.swingtimer.ranged:CreateTexture(nil, "OVERLAY")
|
|
pfUI.swingtimer.ranged.markerGlowL:SetColorTexture(1, 1, 1, 1)
|
|
pfUI.swingtimer.ranged.markerGlowL:SetGradientAlpha("HORIZONTAL", 1, 1, 1, 0, 1, 1, 1, 0.35)
|
|
pfUI.swingtimer.ranged.markerGlowL:SetWidth(10)
|
|
pfUI.swingtimer.ranged.markerGlowL:SetHeight(sw_height)
|
|
pfUI.swingtimer.ranged.markerGlowL:Hide()
|
|
|
|
-- Right glow: fades from white to transparent (left edge = marker)
|
|
pfUI.swingtimer.ranged.markerGlowR = pfUI.swingtimer.ranged:CreateTexture(nil, "OVERLAY")
|
|
pfUI.swingtimer.ranged.markerGlowR:SetColorTexture(1, 1, 1, 1)
|
|
pfUI.swingtimer.ranged.markerGlowR:SetGradientAlpha("HORIZONTAL", 1, 1, 1, 0.35, 1, 1, 1, 0)
|
|
pfUI.swingtimer.ranged.markerGlowR:SetWidth(10)
|
|
pfUI.swingtimer.ranged.markerGlowR:SetHeight(sw_height)
|
|
pfUI.swingtimer.ranged.markerGlowR:Hide()
|
|
|
|
CreateBackdropShadow(pfUI.swingtimer.ranged)
|
|
|
|
UpdateMovable(pfUI.swingtimer.mainhand)
|
|
UpdateMovable(pfUI.swingtimer.ranged)
|
|
|
|
-- OH weapon detection
|
|
-- 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
|
|
|
|
-- Start* restart a countdown 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 StartMH(remaining)
|
|
UpdateWeaponSpeeds()
|
|
pfUI.swingtimer.mhGraceAt = nil -- cancel any pending hide
|
|
S.mhTimerMax = math.max(S.mhSpeed, remaining)
|
|
S.mhTimer = remaining
|
|
S.mhActive = true
|
|
pfUI.swingtimer.mainhand:Show()
|
|
pfUI.swingtimer:Show()
|
|
end
|
|
|
|
local function StartOH(remaining)
|
|
UpdateWeaponSpeeds()
|
|
-- The off-hand attack time stays set with a shield equipped
|
|
if S.ohSpeed <= 0 then return end
|
|
pfUI.swingtimer.ohGraceAt = nil -- cancel any pending hide
|
|
S.ohTimerMax = math.max(S.ohSpeed, remaining)
|
|
S.ohTimer = remaining
|
|
S.ohActive = true
|
|
if sw_showoh then pfUI.swingtimer.offhand:Show() end
|
|
pfUI.swingtimer:Show()
|
|
end
|
|
|
|
local function StartRanged(remaining)
|
|
if not sw_showranged then return end
|
|
UpdateWeaponSpeeds()
|
|
if S.raSpeed <= 0 then return end
|
|
S.raTimerMax = math.max(S.raSpeed, remaining)
|
|
S.raTimer = remaining
|
|
S.raActive = true
|
|
|
|
pfUI.swingtimer.ranged:SetStatusBarColor(raR, raG, raB, raA)
|
|
pfUI.swingtimer.ranged:SetValue(isHunter and 1 or 0)
|
|
pfUI.swingtimer.ranged:Show()
|
|
pfUI.swingtimer:Show()
|
|
end
|
|
|
|
local function ResetAll()
|
|
S.mhActive = false
|
|
S.ohActive = false
|
|
S.raActive = false
|
|
S.mhTimer = 0
|
|
S.ohTimer = 0
|
|
S.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/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
|
|
|
|
-- OnUpdate: countdown all timers with delta, then render
|
|
pfUI.swingtimer:SetScript("OnUpdate", function()
|
|
S.swingThrottle = S.swingThrottle + arg1
|
|
local swingDelay = pfUI.throttle and pfUI.throttle:Get("swingtimer") or 0.02
|
|
if S.swingThrottle < swingDelay then return end
|
|
local delta = S.swingThrottle
|
|
S.swingThrottle = 0
|
|
|
|
-- (out-of-combat hide handled naturally when timers expire)
|
|
|
|
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 PLAYER_SWING arrives.
|
|
-- If PLAYER_SWING 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 S.mhActive then
|
|
S.mhTimer = S.mhTimer - delta
|
|
if S.mhTimer <= 0 then
|
|
S.mhTimer = 0
|
|
if not pfUI.swingtimer.mhGraceAt then
|
|
pfUI.swingtimer.mhGraceAt = GetTime() + GRACE
|
|
elseif GetTime() >= pfUI.swingtimer.mhGraceAt then
|
|
pfUI.swingtimer.mhGraceAt = nil
|
|
if not S.inCombat or not S.autoAttackActive then
|
|
S.mhActive = false
|
|
pfUI.swingtimer.mainhand:Hide()
|
|
S.lastMhMarkerX = -1
|
|
pfUI.swingtimer.mainhand.marker:Hide()
|
|
pfUI.swingtimer.mainhand.markerGlowL:Hide()
|
|
pfUI.swingtimer.mainhand.markerGlowR:Hide()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if S.ohActive then
|
|
S.ohTimer = S.ohTimer - delta
|
|
if S.ohTimer <= 0 then
|
|
S.ohTimer = 0
|
|
if not pfUI.swingtimer.ohGraceAt then
|
|
pfUI.swingtimer.ohGraceAt = GetTime() + GRACE
|
|
elseif GetTime() >= pfUI.swingtimer.ohGraceAt then
|
|
pfUI.swingtimer.ohGraceAt = nil
|
|
if not S.inCombat or not S.autoAttackActive then
|
|
S.ohActive = false
|
|
pfUI.swingtimer.offhand:Hide()
|
|
S.lastOhMarkerX = -1
|
|
pfUI.swingtimer.offhand.marker:Hide()
|
|
pfUI.swingtimer.offhand.markerGlowL:Hide()
|
|
pfUI.swingtimer.offhand.markerGlowR:Hide()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if S.raActive then
|
|
S.raTimer = S.raTimer - delta
|
|
if S.raTimer <= 0 then
|
|
S.raTimer = 0
|
|
S.raActive = false
|
|
pfUI.swingtimer.ranged:Hide()
|
|
end
|
|
end
|
|
|
|
-- HS/Cleave color
|
|
local curR, curG, curB = mhDefaultR, mhDefaultG, mhDefaultB
|
|
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
|
|
curR, curG, curB = 1.0, 0.55, 0.0 -- orange for druid maul queue
|
|
elseif S.isWarrior then
|
|
local hs, cl = IsHSOrCleaveQueued()
|
|
if cl then
|
|
curR, curG, curB = 0.2, 0.9, 0.2
|
|
elseif hs then
|
|
curR, curG, curB = 0.9, 0.9, 0.2
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Render MH
|
|
if S.outOfRange[MAINHAND] then
|
|
curR, curG, curB = orR, orG, orB
|
|
end
|
|
|
|
if S.mhActive then
|
|
local progress = 1 - (S.mhTimer / S.mhTimerMax)
|
|
pfUI.swingtimer.mainhand:SetValue(progress)
|
|
if S.mhTimer <= 0 then
|
|
-- Timer expired: hide marker (no gap at right edge)
|
|
if S.lastMhMarkerX ~= -1 then
|
|
S.lastMhMarkerX = -1
|
|
pfUI.swingtimer.mainhand.marker:Hide()
|
|
pfUI.swingtimer.mainhand.markerGlowL:Hide()
|
|
pfUI.swingtimer.mainhand.markerGlowR:Hide()
|
|
end
|
|
else
|
|
local mhMarkerX = progress * sw_width
|
|
if mhMarkerX < 1 then mhMarkerX = 1 end
|
|
if mhMarkerX > sw_width - 2 then mhMarkerX = sw_width - 2 end
|
|
if mhMarkerX ~= S.lastMhMarkerX then
|
|
S.lastMhMarkerX = mhMarkerX
|
|
pfUI.swingtimer.mainhand.marker:SetPoint("LEFT", pfUI.swingtimer.mainhand, "LEFT", mhMarkerX - 1, 0)
|
|
pfUI.swingtimer.mainhand.markerGlowL:SetPoint("RIGHT", pfUI.swingtimer.mainhand.marker, "LEFT", 0, 0)
|
|
pfUI.swingtimer.mainhand.markerGlowR:SetPoint("LEFT", pfUI.swingtimer.mainhand.marker, "RIGHT", 0, 0)
|
|
pfUI.swingtimer.mainhand.marker:Show()
|
|
pfUI.swingtimer.mainhand.markerGlowL:Show()
|
|
pfUI.swingtimer.mainhand.markerGlowR:Show()
|
|
end
|
|
end
|
|
pfUI.swingtimer.mainhand:SetStatusBarColor(curR, curG, curB, mhA)
|
|
if sw_showtext then
|
|
pfUI.swingtimer.mainhand.text:SetFormattedText("%.1f", math.floor(S.mhTimer * 10) / 10)
|
|
end
|
|
if sw_showspeed and S.mhSpeed > 0 then
|
|
pfUI.swingtimer.mainhand.speed:SetFormattedText("%.2f", S.mhSpeed)
|
|
end
|
|
anyActive = true
|
|
end
|
|
|
|
-- Render OH
|
|
if sw_showoh and S.ohActive then
|
|
local progress = 1 - (S.ohTimer / S.ohTimerMax)
|
|
pfUI.swingtimer.offhand:SetValue(progress)
|
|
if S.outOfRange[OFFHAND] then
|
|
pfUI.swingtimer.offhand:SetStatusBarColor(orR, orG, orB, ohA)
|
|
else
|
|
pfUI.swingtimer.offhand:SetStatusBarColor(ohR, ohG, ohB, ohA)
|
|
end
|
|
if S.ohTimer <= 0 then
|
|
if S.lastOhMarkerX ~= -1 then
|
|
S.lastOhMarkerX = -1
|
|
pfUI.swingtimer.offhand.marker:Hide()
|
|
pfUI.swingtimer.offhand.markerGlowL:Hide()
|
|
pfUI.swingtimer.offhand.markerGlowR:Hide()
|
|
end
|
|
else
|
|
local ohMarkerX = progress * sw_width
|
|
if ohMarkerX < 1 then ohMarkerX = 1 end
|
|
if ohMarkerX > sw_width - 2 then ohMarkerX = sw_width - 2 end
|
|
if ohMarkerX ~= S.lastOhMarkerX then
|
|
S.lastOhMarkerX = ohMarkerX
|
|
pfUI.swingtimer.offhand.marker:SetPoint("LEFT", pfUI.swingtimer.offhand, "LEFT", ohMarkerX - 1, 0)
|
|
pfUI.swingtimer.offhand.markerGlowL:SetPoint("RIGHT", pfUI.swingtimer.offhand.marker, "LEFT", 0, 0)
|
|
pfUI.swingtimer.offhand.markerGlowR:SetPoint("LEFT", pfUI.swingtimer.offhand.marker, "RIGHT", 0, 0)
|
|
pfUI.swingtimer.offhand.marker:Show()
|
|
pfUI.swingtimer.offhand.markerGlowL:Show()
|
|
pfUI.swingtimer.offhand.markerGlowR:Show()
|
|
end
|
|
end
|
|
if sw_showtext then
|
|
pfUI.swingtimer.offhand.text:SetFormattedText("%.1f", math.floor(S.ohTimer * 10) / 10)
|
|
end
|
|
if sw_showspeed and S.ohSpeed > 0 then
|
|
pfUI.swingtimer.offhand.speed:SetFormattedText("%.2f", S.ohSpeed)
|
|
end
|
|
anyActive = true
|
|
elseif not sw_showoh then
|
|
pfUI.swingtimer.offhand:Hide()
|
|
end
|
|
|
|
-- Render Ranged
|
|
if sw_showranged and S.raActive then
|
|
local remaining = S.raTimer
|
|
local r, g, b, a = raR, raG, raB, raA
|
|
if isHunter then
|
|
local DEADZONE = 0.5
|
|
if remaining > DEADZONE then
|
|
pfUI.swingtimer.ranged:SetValue((remaining - DEADZONE) / (S.raTimerMax - DEADZONE))
|
|
else
|
|
r, g, b, a = rwR, rwG, rwB, rwA
|
|
pfUI.swingtimer.ranged:SetValue(1 - (remaining / DEADZONE))
|
|
end
|
|
if sw_showtext then
|
|
if remaining <= 0.5 then
|
|
pfUI.swingtimer.ranged.text:SetFormattedText("%.1f", math.floor(remaining * 10) / 10)
|
|
else
|
|
pfUI.swingtimer.ranged.text:SetFormattedText("%.1f", math.floor((remaining - 0.5) * 10) / 10)
|
|
end
|
|
end
|
|
else
|
|
pfUI.swingtimer.ranged:SetValue(1 - (remaining / S.raTimerMax))
|
|
if sw_showtext then
|
|
pfUI.swingtimer.ranged.text:SetFormattedText("%.1f", math.floor(remaining * 10) / 10)
|
|
end
|
|
end
|
|
if S.outOfRange[RANGED] then
|
|
r, g, b = orR, orG, orB
|
|
end
|
|
pfUI.swingtimer.ranged:SetStatusBarColor(r, g, b, a)
|
|
if sw_showspeed and S.raSpeed > 0 then
|
|
pfUI.swingtimer.ranged.speed:SetFormattedText("%.2f", S.raSpeed)
|
|
end
|
|
local raProgress = 1 - (S.raTimer / S.raTimerMax)
|
|
local raMarkerX = raProgress * sw_width
|
|
if raMarkerX < 1 then raMarkerX = 1 end
|
|
if raMarkerX > sw_width - 2 then raMarkerX = sw_width - 2 end
|
|
if raMarkerX ~= S.lastRaMarkerX then
|
|
S.lastRaMarkerX = raMarkerX
|
|
if not isHunter then
|
|
pfUI.swingtimer.ranged.marker:SetPoint("LEFT", pfUI.swingtimer.ranged, "LEFT", raMarkerX - 1, 0)
|
|
pfUI.swingtimer.ranged.marker:Show()
|
|
pfUI.swingtimer.ranged.markerGlowL:SetPoint("RIGHT", pfUI.swingtimer.ranged.marker, "LEFT", 0, 0)
|
|
pfUI.swingtimer.ranged.markerGlowR:SetPoint("LEFT", pfUI.swingtimer.ranged.marker, "RIGHT", 0, 0)
|
|
pfUI.swingtimer.ranged.markerGlowL:Show()
|
|
pfUI.swingtimer.ranged.markerGlowR:Show()
|
|
else
|
|
pfUI.swingtimer.ranged.marker:Hide()
|
|
pfUI.swingtimer.ranged.markerGlowL:Hide()
|
|
pfUI.swingtimer.ranged.markerGlowR:Hide()
|
|
end
|
|
end
|
|
anyActive = true
|
|
elseif not sw_showranged then
|
|
pfUI.swingtimer.ranged:Hide()
|
|
end
|
|
|
|
if not anyActive then
|
|
if not pfUI.swingtimer.mainhand:IsShown()
|
|
and not pfUI.swingtimer.offhand:IsShown()
|
|
and not pfUI.swingtimer.ranged:IsShown() then
|
|
this:Hide()
|
|
end
|
|
end
|
|
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("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
|
|
|
|
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)
|
|
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
|
|
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
|
|
|
|
elseif event == "START_AUTOATTACK" then
|
|
S.autoAttackActive = true
|
|
|
|
elseif event == "STOP_AUTOATTACK" then
|
|
S.autoAttackActive = false
|
|
|
|
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
|
|
S.ohActive = false
|
|
pfUI.swingtimer.offhand:Hide()
|
|
end
|
|
if S.raSpeed == 0 then
|
|
S.raActive = false
|
|
pfUI.swingtimer.ranged:Hide()
|
|
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
|
|
|
|
elseif event == "PLAYER_DEAD" then
|
|
ResetAll()
|
|
end
|
|
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 S.mhTimer end,
|
|
GetOHTimer = function() return S.ohTimer end,
|
|
GetRangedTimer = function() return S.raTimer 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 S.mhActive end,
|
|
IsOHActive = function() return S.ohActive end,
|
|
IsRangedActive = function() return S.raActive end,
|
|
|
|
-- Swing progress 0.0 (just reset) to 1.0 (about to swing)
|
|
GetMHProgress = function()
|
|
return S.mhActive and S.mhTimerMax > 0 and (1 - S.mhTimer / S.mhTimerMax) or 0
|
|
end,
|
|
GetOHProgress = function()
|
|
return S.ohActive and S.ohTimerMax > 0 and (1 - S.ohTimer / S.ohTimerMax) or 0
|
|
end,
|
|
GetRangedProgress = function()
|
|
return S.raActive and S.raTimerMax > 0 and (1 - S.raTimer / S.raTimerMax) or 0
|
|
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()
|
|
end) |