mirror of
https://github.com/brues-code/pfUI.git
synced 2026-09-29 18:56:02 +00:00
swing timer fixes and additions
* fixed offhand timer incorrectly showing for 2H weapon users * fixed sw_texture resolving pfUI.media key correctly instead of passing raw file path * added proper offhand weapon detection via GetEquippedItem + GetItemStatsField (Nampower), with vanilla API fallback * added UNIT_DIED event to reset timer when target dies * added ACTIONBAR_SLOT_CHANGED event to keep HS/Cleave slot cache up to date * added immediate offhand bar hide when OH weapon is unequipped mid-combat * added player GUID caching for UNIT_DIED comparison * added GUI options: texture, font size, show/hide timer text, MH/OH labels, offhand bar, HS/Cleave queue color * added HS/Cleave queue color coding for Warriors (green = Cleave, yellow = Heroic Strike) * added unit frame text options: attack speed, min/max damage, unit string, HP/mana display variants, pet name * removed unreliable dual-wield heuristic guard (mhAge < 0.1) Co-authored-by: Zedris
This commit is contained in:
+163
-39
@@ -9,13 +9,13 @@ pfUI:RegisterModule("swingtimer", "vanilla:tbc", function ()
|
||||
end
|
||||
end
|
||||
|
||||
-- HitInfo flags
|
||||
local HITINFO_LEFTSWING = 4
|
||||
-- HitInfo flags (from Nampower EVENTS.md)
|
||||
local HITINFO_LEFTSWING = 4 -- 0x4: Off-hand attack
|
||||
|
||||
-- Swing state
|
||||
local swingState = {
|
||||
mainhand = { speed = 0, nextSwing = 0, swinging = false },
|
||||
offhand = { speed = 0, nextSwing = 0, swinging = false }
|
||||
offhand = { speed = 0, nextSwing = 0, swinging = false }
|
||||
}
|
||||
|
||||
-- Create container frame
|
||||
@@ -23,8 +23,16 @@ 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
|
||||
-- Config locals (read once at load, no per-frame lookups)
|
||||
local sw_width = tonumber(C.unitframes.swingtimerwidth) or 200
|
||||
local sw_height = tonumber(C.unitframes.swingtimerheight) or 12
|
||||
local sw_label = C.unitframes.swingtimerlabel ~= "0"
|
||||
local sw_oh = C.unitframes.swingtimeroh ~= "0"
|
||||
local sw_hs = C.unitframes.swingtimerhsqueue ~= "0"
|
||||
local sw_text = C.unitframes.swingtimertext ~= "0"
|
||||
local sw_fontsize = tonumber(C.unitframes.swingtimerfontsize) or 12
|
||||
-- swingtimertexture stores a pfUI.media key (e.g. "img:bar"), not a raw path
|
||||
local sw_texture = pfUI.media[C.unitframes.swingtimertexture] or pfUI.media["img:bar"]
|
||||
|
||||
-- Mainhand bar
|
||||
pfUI.swingtimer.mainhand = CreateFrame("StatusBar", "pfSwingTimerMainhand", UIParent)
|
||||
@@ -33,21 +41,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:SetStatusBarTexture(sw_texture)
|
||||
pfUI.swingtimer.mainhand:SetStatusBarColor(0.8, 0.3, 0.3, 1)
|
||||
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_text 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_label and "MH" or "")
|
||||
|
||||
CreateBackdrop(pfUI.swingtimer.mainhand)
|
||||
CreateBackdropShadow(pfUI.swingtimer.mainhand)
|
||||
@@ -59,39 +68,114 @@ 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:SetStatusBarTexture(sw_texture)
|
||||
pfUI.swingtimer.offhand:SetStatusBarColor(0.3, 0.8, 0.3, 1)
|
||||
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_text 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_label and "OH" or "")
|
||||
|
||||
CreateBackdrop(pfUI.swingtimer.offhand)
|
||||
CreateBackdropShadow(pfUI.swingtimer.offhand)
|
||||
|
||||
-- HS/Cleave queue detection (Warrior only)
|
||||
-- Slots are cached on load and on actionbar changes to avoid per-frame texture lookups.
|
||||
local hsTrackedSlots = {}
|
||||
local cleaveTrackedSlots = {}
|
||||
|
||||
local function UpdateHSCleaveSlots()
|
||||
local _, class = UnitClass("player")
|
||||
if class ~= "WARRIOR" or not sw_hs then return end
|
||||
hsTrackedSlots = {}
|
||||
cleaveTrackedSlots = {}
|
||||
for slot = 1, 120 do
|
||||
local texture = GetActionTexture(slot)
|
||||
local text = GetActionText(slot)
|
||||
if texture then
|
||||
-- Heroic Strike and Ambush share the same icon in 1.12.1
|
||||
if texture == "Interface\\Icons\\Ability_Rogue_Ambush" then
|
||||
tinsert(hsTrackedSlots, slot)
|
||||
elseif texture == "Interface\\Icons\\Ability_Warrior_Cleave" then
|
||||
tinsert(cleaveTrackedSlots, slot)
|
||||
elseif text then
|
||||
text = string.lower(text)
|
||||
if text == "heroic strike" or text == "heroicstrike" or text == "hs" then
|
||||
tinsert(hsTrackedSlots, slot)
|
||||
elseif text == "cleave" then
|
||||
tinsert(cleaveTrackedSlots, slot)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function IsHSQueued()
|
||||
if not sw_hs then return false end
|
||||
for _, slot in ipairs(hsTrackedSlots) do
|
||||
if IsCurrentAction(slot) then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function IsCleaveQueued()
|
||||
if not sw_hs then return false end
|
||||
for _, slot in ipairs(cleaveTrackedSlots) do
|
||||
if IsCurrentAction(slot) then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
UpdateMovable(pfUI.swingtimer.mainhand)
|
||||
|
||||
-- inventoryType numbers from ItemStats DBC:
|
||||
-- 17 = INVTYPE_WEAPON (one-hand, can go in either hand)
|
||||
-- 21 = INVTYPE_WEAPONOFFHAND
|
||||
-- Shields (14), Ranged (15/26), Relics (28) etc. must NOT trigger OH timer.
|
||||
local OH_SWINGABLE = { [17] = true, [21] = true }
|
||||
|
||||
local function HasOffhandWeapon()
|
||||
if GetEquippedItem then
|
||||
local item = GetEquippedItem("player", 17)
|
||||
if not item or not item.itemId or item.itemId == 0 then return false end
|
||||
local invType = GetItemStatsField and GetItemStatsField(item.itemId, "inventoryType")
|
||||
if invType then
|
||||
return OH_SWINGABLE[invType] == true
|
||||
end
|
||||
end
|
||||
-- Fallback: vanilla API
|
||||
local link = GetInventoryItemLink("player", 17)
|
||||
if not link then return false end
|
||||
local _, _, _, _, _, _, _, _, invTypeStr = GetItemInfo(link)
|
||||
return invTypeStr == "INVTYPE_WEAPON" or invTypeStr == "INVTYPE_WEAPONOFFHAND"
|
||||
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 ohSpeed and ohSpeed > 0 then
|
||||
swingState.offhand.speed = ohSpeed / 1000
|
||||
-- offhandAttackTime is populated by the server even for 2H / empty OH slot.
|
||||
-- Gate it on an actual swingable weapon in the offhand equipment slot.
|
||||
if HasOffhandWeapon() then
|
||||
local ohSpeed = GetUnitField("player", "offhandAttackTime")
|
||||
if ohSpeed and ohSpeed > 0 then
|
||||
swingState.offhand.speed = ohSpeed / 1000
|
||||
else
|
||||
swingState.offhand.speed = 0
|
||||
end
|
||||
else
|
||||
swingState.offhand.speed = 0
|
||||
end
|
||||
@@ -100,25 +184,16 @@ pfUI:RegisterModule("swingtimer", "vanilla:tbc", function ()
|
||||
local function StartSwing(isOffhand)
|
||||
local now = GetTime()
|
||||
|
||||
-- always refresh speeds to catch haste buffs/debuffs
|
||||
-- Refresh speeds on every swing to catch haste changes mid-combat.
|
||||
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
|
||||
swingState.offhand.nextSwing = now + swingState.offhand.speed
|
||||
swingState.offhand.swinging = true
|
||||
pfUI.swingtimer.offhand:Show()
|
||||
swingState.offhand.swinging = true
|
||||
if sw_oh then pfUI.swingtimer.offhand:Show() end
|
||||
else
|
||||
swingState.mainhand.nextSwing = now + swingState.mainhand.speed
|
||||
swingState.mainhand.swinging = true
|
||||
swingState.mainhand.swinging = true
|
||||
pfUI.swingtimer.mainhand:Show()
|
||||
end
|
||||
|
||||
@@ -129,32 +204,47 @@ pfUI:RegisterModule("swingtimer", "vanilla:tbc", function ()
|
||||
local now = GetTime()
|
||||
local anyActive = false
|
||||
|
||||
-- HS/Cleave colour coding (only recalculated when MH is active)
|
||||
local mhR, mhG, mhB = 0.8, 0.3, 0.3
|
||||
if sw_hs then
|
||||
if IsCleaveQueued() then
|
||||
mhR, mhG, mhB = 0.2, 0.9, 0.2
|
||||
elseif IsHSQueued() then
|
||||
mhR, mhG, mhB = 0.9, 0.9, 0.2
|
||||
end
|
||||
end
|
||||
|
||||
if swingState.mainhand.swinging then
|
||||
local remaining = swingState.mainhand.nextSwing - now
|
||||
|
||||
if remaining <= 0 then
|
||||
swingState.mainhand.swinging = false
|
||||
pfUI.swingtimer.mainhand:Hide()
|
||||
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(mhR, mhG, mhB, 1)
|
||||
if sw_text then
|
||||
pfUI.swingtimer.mainhand.text:SetText(string.format("%.1f", remaining))
|
||||
end
|
||||
anyActive = true
|
||||
end
|
||||
end
|
||||
|
||||
if swingState.offhand.swinging then
|
||||
if sw_oh and swingState.offhand.swinging then
|
||||
local remaining = swingState.offhand.nextSwing - now
|
||||
|
||||
if remaining <= 0 then
|
||||
swingState.offhand.swinging = false
|
||||
pfUI.swingtimer.offhand:Hide()
|
||||
else
|
||||
local progress = 1 - (remaining / swingState.offhand.speed)
|
||||
pfUI.swingtimer.offhand:SetValue(progress)
|
||||
pfUI.swingtimer.offhand.text:SetText(string.format("%.1f", remaining))
|
||||
if sw_text then
|
||||
pfUI.swingtimer.offhand.text:SetText(string.format("%.1f", remaining))
|
||||
end
|
||||
anyActive = true
|
||||
end
|
||||
elseif not sw_oh then
|
||||
pfUI.swingtimer.offhand:Hide()
|
||||
end
|
||||
|
||||
if not anyActive then
|
||||
@@ -169,33 +259,67 @@ pfUI:RegisterModule("swingtimer", "vanilla:tbc", function ()
|
||||
events:RegisterEvent("PLAYER_REGEN_DISABLED")
|
||||
events:RegisterEvent("PLAYER_REGEN_ENABLED")
|
||||
events:RegisterEvent("PLAYER_TARGET_CHANGED")
|
||||
events:RegisterEvent("UNIT_DIED")
|
||||
events:RegisterEvent("ACTIONBAR_SLOT_CHANGED")
|
||||
|
||||
local function ResetSwingTimers()
|
||||
swingState.mainhand.swinging = false
|
||||
swingState.offhand.swinging = false
|
||||
swingState.offhand.swinging = false
|
||||
pfUI.swingtimer.mainhand:Hide()
|
||||
pfUI.swingtimer.offhand:Hide()
|
||||
pfUI.swingtimer:Hide()
|
||||
end
|
||||
|
||||
-- Cache player GUID for UNIT_DIED comparison
|
||||
local playerGUID = nil
|
||||
|
||||
events:SetScript("OnEvent", function()
|
||||
if event == "AUTO_ATTACK_SELF" then
|
||||
local hitInfo = arg4 or 0
|
||||
-- arg4 = hitInfo (Nampower EVENTS.md)
|
||||
local hitInfo = arg4 or 0
|
||||
local isOffhand = bit.band(hitInfo, HITINFO_LEFTSWING) ~= 0
|
||||
StartSwing(isOffhand)
|
||||
elseif event == "PLAYER_ENTERING_WORLD" or event == "UNIT_INVENTORY_CHANGED" then
|
||||
|
||||
elseif event == "PLAYER_ENTERING_WORLD" then
|
||||
playerGUID = UnitExists and UnitGUID and UnitGUID("player") or nil
|
||||
UpdateWeaponSpeeds()
|
||||
UpdateHSCleaveSlots()
|
||||
|
||||
elseif event == "UNIT_INVENTORY_CHANGED" then
|
||||
if arg1 and arg1 ~= "player" then return end
|
||||
UpdateWeaponSpeeds()
|
||||
UpdateHSCleaveSlots()
|
||||
-- Hide OH bar immediately if player unequipped their OH weapon
|
||||
if swingState.offhand.speed == 0 then
|
||||
swingState.offhand.swinging = false
|
||||
pfUI.swingtimer.offhand:Hide()
|
||||
end
|
||||
|
||||
elseif event == "ACTIONBAR_SLOT_CHANGED" then
|
||||
UpdateHSCleaveSlots()
|
||||
|
||||
elseif event == "PLAYER_REGEN_DISABLED" then
|
||||
UpdateWeaponSpeeds()
|
||||
|
||||
elseif event == "PLAYER_REGEN_ENABLED" then
|
||||
ResetSwingTimers()
|
||||
|
||||
elseif event == "PLAYER_TARGET_CHANGED" then
|
||||
if not UnitExists("target") or UnitIsDead("target") then
|
||||
ResetSwingTimers()
|
||||
end
|
||||
|
||||
elseif event == "UNIT_DIED" then
|
||||
-- arg1 = guid of the unit that died (Nampower EVENTS.md)
|
||||
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)
|
||||
UpdateHSCleaveSlots()
|
||||
end)
|
||||
|
||||
Reference in New Issue
Block a user