From fe93577825d262ad04a81dc3dd70cdf34ead21f0 Mon Sep 17 00:00:00 2001 From: Meow <30401521+me0wg4ming@users.noreply.github.com> Date: Thu, 19 Feb 2026 17:16:48 +0100 Subject: [PATCH] working swingtimer re-written - no errors on load working swingtimer re-written - no errors on load >.< --- api/config.lua | 8 ++ modules/gui.lua | 8 ++ modules/swingtimer.lua | 230 ++++++++++++++++++++++++++++++++++++----- 3 files changed, 219 insertions(+), 27 deletions(-) diff --git a/api/config.lua b/api/config.lua index 44799d9e..8cc13778 100644 --- a/api/config.lua +++ b/api/config.lua @@ -230,6 +230,14 @@ function pfUI:LoadConfig() pfUI:UpdateConfig("unitframes", nil, "comboheight", "6") pfUI:UpdateConfig("unitframes", nil, "swingtimerwidth", "200") pfUI:UpdateConfig("unitframes", nil, "swingtimerheight", "12") + pfUI:UpdateConfig("unitframes", nil, "swingtimertexture", "Interface\\AddOns\\pfUI\\img\\bar") + pfUI:UpdateConfig("unitframes", nil, "swingtimertext", "1") + pfUI:UpdateConfig("unitframes", nil, "swingtimerlabel", "1") + pfUI:UpdateConfig("unitframes", nil, "swingtimeroffhand","1") + pfUI:UpdateConfig("unitframes", nil, "swingtimerfontsize","12") + pfUI:UpdateConfig("unitframes", nil, "swingtimermhcolor",".8,.3,.3,1") + pfUI:UpdateConfig("unitframes", nil, "swingtimerohcolor",".3,.8,.3,1") + pfUI:UpdateConfig("unitframes", nil, "swingtimerhsqueue","1") pfUI:UpdateConfig("unitframes", nil, "abbrevnum", "1") pfUI:UpdateConfig("unitframes", nil, "castbardecimals", "2") pfUI:UpdateConfig("unitframes", nil, "abbrevname", "1") diff --git a/modules/gui.lua b/modules/gui.lua index ae49c601..71f9cc5f 100644 --- a/modules/gui.lua +++ b/modules/gui.lua @@ -1957,6 +1957,14 @@ pfUI:RegisterModule("gui", "vanilla:tbc", function () CreateConfig(nil, T["Swing Timer"], nil, nil, "header") CreateConfig(nil, T["Swing Timer Width"], C.unitframes, "swingtimerwidth") CreateConfig(nil, T["Swing Timer Height"], C.unitframes, "swingtimerheight") + CreateConfig(nil, T["Swing Timer Texture"], C.unitframes, "swingtimertexture", "dropdown", pfUI.gui.dropdowns.uf_bartexture) + CreateConfig(nil, T["Swing Timer Font Size"], C.unitframes, "swingtimerfontsize") + CreateConfig(nil, T["Show Timer Text"], C.unitframes, "swingtimertext", "checkbox") + CreateConfig(nil, T["Show MH/OH Labels"], C.unitframes, "swingtimerlabel", "checkbox") + CreateConfig(nil, T["Show Offhand Bar"], C.unitframes, "swingtimeroffhand", "checkbox") + CreateConfig(nil, T["Mainhand Bar Color"], C.unitframes, "swingtimermhcolor", "color") + CreateConfig(nil, T["Offhand Bar Color"], C.unitframes, "swingtimerohcolor", "color") + CreateConfig(nil, T["Show HS/Cleave Queue Color (Warrior)"], C.unitframes, "swingtimerhsqueue", "checkbox") CreateConfig(U[c], T["Font Options"], nil, nil, "header") CreateConfig(nil, T["Unit Frame Text Font"], C.global, "font_unit", "dropdown", pfUI.gui.dropdowns.fonts) diff --git a/modules/swingtimer.lua b/modules/swingtimer.lua index f043178e..f3e3bdd7 100644 --- a/modules/swingtimer.lua +++ b/modules/swingtimer.lua @@ -1,16 +1,13 @@ pfUI:RegisterModule("swingtimer", "vanilla:tbc", function () local rawborder, border = GetBorderSize() - -- Enable Nampower auto attack events - if GetCVar and SetCVar then - local success, value = pcall(GetCVar, "NP_EnableAutoAttackEvents") - if success and value == "0" then - pcall(SetCVar, "NP_EnableAutoAttackEvents", "1") - end - end + -- HitInfo flags (EVENTS.md) + local HITINFO_LEFTSWING = 4 -- 0x4: Off-hand attack + local HITINFO_NOACTION = 65536 -- 0x10000: server did not advance the swing clock - -- HitInfo flags - local HITINFO_LEFTSWING = 4 + -- SPELL_QUEUE_EVENT codes (EVENTS.md) + local ON_SWING_QUEUED = 0 + local ON_SWING_QUEUE_POPPED = 1 -- Swing state local swingState = { @@ -23,8 +20,31 @@ pfUI:RegisterModule("swingtimer", "vanilla:tbc", function () pfUI.swingtimer:SetFrameStrata("MEDIUM") pfUI.swingtimer:Hide() - local sw_width = tonumber(C.unitframes.swingtimerwidth) or 200 - local sw_height = tonumber(C.unitframes.swingtimerheight) or 12 + -- Read config once at load into locals + 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_fontsize = tonumber(C.unitframes.swingtimerfontsize) or 12 + local sw_hsqueue = C.unitframes.swingtimerhsqueue ~= "0" + + -- Parse color strings "r,g,b,a" into components + 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) + + -- Store default MH color for HS/Cleave restore + local mhDefaultR, mhDefaultG, mhDefaultB = mhR, mhG, mhB -- Mainhand bar pfUI.swingtimer.mainhand = CreateFrame("StatusBar", "pfSwingTimerMainhand", UIParent) @@ -33,21 +53,22 @@ pfUI:RegisterModule("swingtimer", "vanilla:tbc", function () pfUI.swingtimer.mainhand:SetHeight(sw_height) pfUI.swingtimer.mainhand:SetMinMaxValues(0, 1) pfUI.swingtimer.mainhand:SetValue(0) - pfUI.swingtimer.mainhand:SetStatusBarTexture(pfUI.media["img:bar"]) - pfUI.swingtimer.mainhand:SetStatusBarColor(0.8, 0.3, 0.3, 1) + 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, C.global.font_size, "OUTLINE") + 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, C.global.font_size, "OUTLINE") + 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("MH") + pfUI.swingtimer.mainhand.label:SetText(sw_showlabel and "MH" or "") CreateBackdrop(pfUI.swingtimer.mainhand) CreateBackdropShadow(pfUI.swingtimer.mainhand) @@ -59,27 +80,97 @@ pfUI:RegisterModule("swingtimer", "vanilla:tbc", function () pfUI.swingtimer.offhand:SetHeight(sw_height) pfUI.swingtimer.offhand:SetMinMaxValues(0, 1) pfUI.swingtimer.offhand:SetValue(0) - pfUI.swingtimer.offhand:SetStatusBarTexture(pfUI.media["img:bar"]) - pfUI.swingtimer.offhand:SetStatusBarColor(0.3, 0.8, 0.3, 1) + 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, C.global.font_size, "OUTLINE") + 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, C.global.font_size, "OUTLINE") + 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("OH") + pfUI.swingtimer.offhand.label:SetText(sw_showlabel and "OH" or "") CreateBackdrop(pfUI.swingtimer.offhand) CreateBackdropShadow(pfUI.swingtimer.offhand) + -- HS/Cleave queue state + local hsQueued = false + local cleaveQueued = false + local isWarrior = false + local cachedHSSlots = {} + local cachedCleaveSlots = {} + local useSpellQueueEvent = false + + -- Heroic Strike spell IDs (all ranks) + local hsSpellIDs = { + [78] = true, [284] = true, [285] = true, [1608] = true, + [11564] = true, [11565] = true, [11566] = true, [11567] = true, + [25286] = true, + } + -- Cleave spell IDs (all ranks) + local cleaveSpellIDs = { + [845] = true, [7369] = true, [11608] = true, [11609] = true, + [20569] = true, + } + + local function RebuildQueueSlotCache() + if not isWarrior or not sw_hsqueue or useSpellQueueEvent then return end + + cachedHSSlots = {} + cachedCleaveSlots = {} + + for slot = 1, 120 do + local tex = GetActionTexture(slot) + local name = GetActionText(slot) + + if tex then + if string.find(tex, "Ability_Rogue_Ambush") then + table.insert(cachedHSSlots, slot) + elseif string.find(tex, "Ability_Warrior_Cleave") then + table.insert(cachedCleaveSlots, slot) + end + end + + if name then + local lower = string.lower(name) + if lower == "heroic strike" or lower == "heroicstrike" or lower == "hs" then + table.insert(cachedHSSlots, slot) + elseif lower == "cleave" then + table.insert(cachedCleaveSlots, slot) + end + 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 + + local function IsHSOrCleaveQueued() + if not sw_hsqueue or not isWarrior then return false, false end + if useSpellQueueEvent then + return hsQueued, cleaveQueued + end + return CheckQueuedAction(cachedHSSlots), CheckQueuedAction(cachedCleaveSlots) + end + UpdateMovable(pfUI.swingtimer.mainhand) + -- 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. local function UpdateWeaponSpeeds() if not GetUnitField then return end @@ -115,7 +206,7 @@ pfUI:RegisterModule("swingtimer", "vanilla:tbc", function () if isOffhand and swingState.offhand.speed > 0 then swingState.offhand.nextSwing = now + swingState.offhand.speed swingState.offhand.swinging = true - pfUI.swingtimer.offhand:Show() + if sw_showoh then pfUI.swingtimer.offhand:Show() end else swingState.mainhand.nextSwing = now + swingState.mainhand.speed swingState.mainhand.swinging = true @@ -129,6 +220,16 @@ pfUI:RegisterModule("swingtimer", "vanilla:tbc", function () local now = GetTime() local anyActive = false + local curR, curG, curB = mhDefaultR, mhDefaultG, mhDefaultB + if sw_hsqueue and 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 + if swingState.mainhand.swinging then local remaining = swingState.mainhand.nextSwing - now @@ -138,12 +239,15 @@ pfUI:RegisterModule("swingtimer", "vanilla:tbc", function () else local progress = 1 - (remaining / swingState.mainhand.speed) pfUI.swingtimer.mainhand:SetValue(progress) - pfUI.swingtimer.mainhand.text:SetText(string.format("%.1f", remaining)) + 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 swingState.offhand.swinging then + if sw_showoh and swingState.offhand.swinging then local remaining = swingState.offhand.nextSwing - now if remaining <= 0 then @@ -152,9 +256,13 @@ pfUI:RegisterModule("swingtimer", "vanilla:tbc", function () else local progress = 1 - (remaining / swingState.offhand.speed) pfUI.swingtimer.offhand:SetValue(progress) - pfUI.swingtimer.offhand.text:SetText(string.format("%.1f", remaining)) + if sw_showtext then + pfUI.swingtimer.offhand.text:SetText(string.format("%.1f", remaining)) + end anyActive = true end + elseif not sw_showoh then + pfUI.swingtimer.offhand:Hide() end if not anyActive then @@ -164,11 +272,15 @@ pfUI:RegisterModule("swingtimer", "vanilla:tbc", function () 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("PLAYER_TARGET_CHANGED") + events:RegisterEvent("ACTIONBAR_SLOT_CHANGED") + events:RegisterEvent("UNIT_DIED") + events:RegisterEvent("SPELL_QUEUE_EVENT") local function ResetSwingTimers() swingState.mainhand.swinging = false @@ -178,24 +290,88 @@ pfUI:RegisterModule("swingtimer", "vanilla:tbc", function () pfUI.swingtimer:Hide() end + local playerGUID = nil + events:SetScript("OnEvent", function() if event == "AUTO_ATTACK_SELF" then local hitInfo = arg4 or 0 + -- HITINFO_NOACTION: server did not advance the swing clock, ignore + if bit.band(hitInfo, HITINFO_NOACTION) ~= 0 then return end local isOffhand = bit.band(hitInfo, HITINFO_LEFTSWING) ~= 0 StartSwing(isOffhand) - elseif event == "PLAYER_ENTERING_WORLD" or event == "UNIT_INVENTORY_CHANGED" then + + elseif event == "AUTO_ATTACK_OTHER" then + if not swingState.mainhand.swinging then return end + local targetGuid = arg2 + if not targetGuid or not playerGUID then return end + if targetGuid ~= playerGUID then return end + local victimState = arg5 or 0 + if victimState == 3 then + local now = GetTime() + local remaining = swingState.mainhand.nextSwing - now + local reduction = swingState.mainhand.speed * 0.4 + local minRemaining = swingState.mainhand.speed * 0.2 + local newRemaining = remaining - reduction + if newRemaining < minRemaining then newRemaining = minRemaining end + if newRemaining < remaining then + swingState.mainhand.nextSwing = now + newRemaining + end + end + + elseif event == "SPELL_QUEUE_EVENT" then + local eventCode = arg1 or -1 + local spellId = arg2 or 0 + if eventCode == ON_SWING_QUEUED then + useSpellQueueEvent = true + if hsSpellIDs[spellId] then + hsQueued = true; cleaveQueued = false + elseif cleaveSpellIDs[spellId] then + cleaveQueued = true; hsQueued = false + end + elseif eventCode == ON_SWING_QUEUE_POPPED then + hsQueued = false; cleaveQueued = false + end + + elseif event == "PLAYER_ENTERING_WORLD" then + local _, class = UnitClass("player") + isWarrior = (class == "WARRIOR") + playerGUID = UnitGUID and UnitGUID("player") or nil + UpdateWeaponSpeeds() + RebuildQueueSlotCache() + + elseif event == "UNIT_INVENTORY_CHANGED" then if arg1 and arg1 ~= "player" then return end UpdateWeaponSpeeds() + if swingState.offhand.speed == 0 then + swingState.offhand.swinging = false + pfUI.swingtimer.offhand:Hide() + end + + elseif event == "ACTIONBAR_SLOT_CHANGED" then + RebuildQueueSlotCache() + elseif event == "PLAYER_REGEN_DISABLED" then UpdateWeaponSpeeds() + elseif event == "PLAYER_REGEN_ENABLED" then ResetSwingTimers() + hsQueued = false + cleaveQueued = false + elseif event == "PLAYER_TARGET_CHANGED" then if not UnitExists("target") or UnitIsDead("target") then ResetSwingTimers() end + + elseif event == "UNIT_DIED" then + local guid = arg1 + if not guid then return end + local targetGUID = UnitExists("target") and UnitGUID and UnitGUID("target") or nil + if guid == targetGUID or guid == playerGUID then + ResetSwingTimers() + end end end) UpdateWeaponSpeeds() -end) +end) \ No newline at end of file