diff --git a/api/config.lua b/api/config.lua index 44799d9e..cca51ebd 100644 --- a/api/config.lua +++ b/api/config.lua @@ -230,6 +230,12 @@ 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, "swingtimerlabel", "1") + pfUI:UpdateConfig("unitframes", nil, "swingtimeroh", "1") + pfUI:UpdateConfig("unitframes", nil, "swingtimerhsqueue","1") + pfUI:UpdateConfig("unitframes", nil, "swingtimertexture","img:bar") + pfUI:UpdateConfig("unitframes", nil, "swingtimertext", "1") + pfUI:UpdateConfig("unitframes", nil, "swingtimerfontsize","12") pfUI:UpdateConfig("unitframes", nil, "abbrevnum", "1") pfUI:UpdateConfig("unitframes", nil, "castbardecimals", "2") pfUI:UpdateConfig("unitframes", nil, "abbrevname", "1") diff --git a/api/unitframes.lua b/api/unitframes.lua index ed9c8f13..6ac3456c 100644 --- a/api/unitframes.lua +++ b/api/unitframes.lua @@ -3224,6 +3224,42 @@ function pfUI.uf:GetStatusValue(unit, pos) end elseif config == "powerminmax" then return unit:GetColor("power") .. pfUI.api.Abbreviate(mp) .. "/" .. pfUI.api.Abbreviate(mpmax) + elseif config == "attackspeed" then + local mainSpeed = UnitAttackSpeed(unitstr) + return unit:GetColor("unit") .. format("Spd: %.2f", mainSpeed) + elseif config == "attackdmg" then + local lowDmg, hiDmg = UnitDamage(unitstr) + return unit:GetColor("unit") .. format("Dmg: %.0f - %.0f", lowDmg, hiDmg) + elseif config == "unit-left" then + local name = unit:GetColor("unit") .. pfUI.uf:GetNameString(unitstr) + local level = unit:GetColor("level") .. pfUI.uf:GetLevelString(unitstr) + return level .. "|cffffffff | " .. name + elseif config == "unit-right" then + local name = unit:GetColor("unit") .. pfUI.uf:GetNameString(unitstr) + local level = unit:GetColor("level") .. pfUI.uf:GetLevelString(unitstr) + return name .. "|cffffffff | " .. level + elseif config == "unit-white-left" then + local name = "|cffffffff" .. pfUI.uf:GetNameString(unitstr) + local level = "|cffffffff" .. pfUI.uf:GetLevelString(unitstr) + return level .. " |cffffffff | " .. name + elseif config == "unit-white-right" then + local name = "|cffffffff" .. pfUI.uf:GetNameString(unitstr) + local level = "|cffffffff" .. pfUI.uf:GetLevelString(unitstr) + return name .. " |cffffffff | " .. level + elseif config == "hp-right" then + return unit:GetColor("health") .. ceil(rhp / rhpmax * 100) .. "%" .. " | " .. pfUI.api.Abbreviate(rhp) + elseif config == "hp-left" then + return unit:GetColor("health") .. pfUI.api.Abbreviate(rhp) .. " | " .. ceil(rhp / rhpmax * 100) .. "%" + elseif config == "mana-auto-deli" then + if mp ~= mpmax and UnitPowerType(unitstr) == 0 then + return "|cffffffff" .. pfUI.api.Abbreviate(mp) .. " - " .. ceil(mp / mpmax * 100) .. "%" + else + return "|cffffffff" .. pfUI.api.Abbreviate(mp) + end + elseif config == "mana-current-deli" then + return "|cffffffff" .. pfUI.api.Abbreviate(mp) + elseif config == "petname" then + return "|cffffffff" .. (UnitName(unitstr) or "") else return "" end diff --git a/modules/gui.lua b/modules/gui.lua index ae49c601..c0f6ba25 100644 --- a/modules/gui.lua +++ b/modules/gui.lua @@ -1060,6 +1060,17 @@ pfUI:RegisterModule("gui", "vanilla:tbc", function () "powerperc:" .. T["Mana - Percentage"], "powermiss:" .. T["Mana - Missing"], "powerminmax:" .. T["Mana - Min/Max"], + "attackspeed:" .. T["Attack Speed"], + "attackdmg:" .. T["Min/Max Damage"], + "unit-left:" .. T["Unit String Left"], + "unit-right:" .. T["Unit String Right"], + "unit-white-left:" .. T["Unit String Left White"], + "unit-white-right:" .. T["Unit String Right White"], + "hp-right:" .. T["HP Current | Percent"], + "hp-left:" .. T["HP Percent | Current"], + "mana-auto-deli:" .. T["Mana Auto"], + "mana-current-deli:" .. T["Mana Current"], + "petname:" .. T["Pet Name"], }, ["hpformat"] = { "percent:" .. T["Percent"], @@ -1957,6 +1968,12 @@ 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["Show Timer Text"], C.unitframes, "swingtimertext", "checkbox") + CreateConfig(nil, T["Timer Text Font Size"], C.unitframes, "swingtimerfontsize") + CreateConfig(nil, T["Show MH/OH Labels"], C.unitframes, "swingtimerlabel", "checkbox") + CreateConfig(nil, T["Show Offhand Bar"], C.unitframes, "swingtimeroh", "checkbox") + CreateConfig(nil, T["Show HS/Cleave Queue Color"], 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 4df5c5ee..8b86fbf8 100644 --- a/modules/swingtimer.lua +++ b/modules/swingtimer.lua @@ -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) \ No newline at end of file + UpdateHSCleaveSlots() +end)