mirror of
https://github.com/brues-code/pfUI.git
synced 2026-09-28 10:26:02 +00:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cc8b8b476b | |||
| bd2b6c3105 | |||
| 5bede33f00 | |||
| a286843b18 | |||
| 39840c10fd | |||
| ab98bb7ab5 | |||
| 73f4999004 | |||
| b7c3fe5333 |
+1
-1
@@ -13,7 +13,7 @@ do
|
||||
-- on. There pfUI does load, and will throw wherever it reaches for something
|
||||
-- the installed DLL doesn't have yet -- the popup names the cause so those
|
||||
-- errors aren't a mystery.
|
||||
local PFUI_CLASSIC_API_MIN = 11510 -- (X*10000 + Y*100 + Z)
|
||||
local PFUI_CLASSIC_API_MIN = 11500 -- (X*10000 + Y*100 + Z)
|
||||
local PFUI_CLASSIC_API_LATEST = PFUI_CLASSIC_API_MIN
|
||||
local PFUI_CLASSIC_API_WEBSITE = "https://github.com/brues-code/ClassicAPI"
|
||||
local PFUI_CLASSIC_API_LATEST_URL = PFUI_CLASSIC_API_WEBSITE .. "/releases/latest"
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
# pfUI - ClassicAPI Edition
|
||||
|
||||
[](https://octowow.st/)
|
||||
[](https://github.com/brues-code/ClassicAPI)
|
||||
[](https://github.com/brues-code/nampower)
|
||||
[](https://github.com/balakethelock/SuperWoW)
|
||||
[](https://github.com/brues-code/UnitXP_SP3)
|
||||
|
||||
**A pfUI fork specifically optimized for ClassicAPI on [Octo WoW](https://octowow.st/)**
|
||||
|
||||
This version includes significant performance improvements and DLL-enhanced features.
|
||||
|
||||
## Installation
|
||||
@@ -15,7 +18,7 @@ This version includes significant performance improvements and DLL-enhanced feat
|
||||
|
||||
## DLL Enhancements
|
||||
|
||||
Since pfUI 6.0.0 includes integrations with client-side DLLs for enhanced functionality:
|
||||
Since pfUI 6.0.0 includes integrations with client-side DLLs for enhanced functionality. These DLLs are permitted on Octo WoW:
|
||||
|
||||
### [ClassicAPI](https://github.com/brues-code/ClassicAPI)
|
||||
|
||||
|
||||
-26
@@ -1333,32 +1333,6 @@ function pfUI.api.CreateBackdropShadow(f)
|
||||
f.backdrop_shadow:SetBackdropBorderColor(0, 0, 0, tonumber(pfUI_config.appearance.border.shadow_intensity))
|
||||
end
|
||||
|
||||
-- [ Set Castbar Shield ]
|
||||
-- Colors a castbar for a cast that none of the player's own interrupts or
|
||||
-- silences can stop. The bar and the icon's border both take the shield color,
|
||||
-- so the state still reads on a bar configured without an icon. Not shielded
|
||||
-- restores the normal cast/channel color and the configured border.
|
||||
-- 'bar' [frame] the castbar statusbar
|
||||
-- 'icon' [frame] the icon frame, or nil for a bar without one
|
||||
-- 'shielded' [bool] whether the cast is uninterruptible
|
||||
-- 'isChannel' [bool] selects the unshielded color
|
||||
function pfUI.api.SetCastbarShield(bar, icon, shielded, isChannel)
|
||||
if shielded then
|
||||
bar:SetStatusBarColor(pfUI.api.GetStringColor(C.appearance.castbar.shieldcolor))
|
||||
else
|
||||
bar:SetStatusBarColor(pfUI.api.GetStringColor(C.appearance.castbar[isChannel and "channelcolor" or "castbarcolor"]))
|
||||
end
|
||||
|
||||
local iconborder = icon and icon.backdrop
|
||||
if not iconborder then return end
|
||||
|
||||
if shielded then
|
||||
iconborder:SetBackdropBorderColor(pfUI.api.GetStringColor(C.appearance.castbar.shieldcolor))
|
||||
else
|
||||
iconborder:SetBackdropBorderColor(pfUI.api.GetStringColor(C.appearance.border.color))
|
||||
end
|
||||
end
|
||||
|
||||
-- [ Bar Layout Options ] --
|
||||
-- 'barsize' size of bar in number of buttons
|
||||
-- returns: array of options as strings for pfUI.gui.bar
|
||||
|
||||
@@ -156,8 +156,6 @@ function pfUI:LoadConfig()
|
||||
pfUI:UpdateConfig("appearance", "castbar", "castbarcolor", ".7,.7,.9,.8")
|
||||
pfUI:UpdateConfig("appearance", "castbar", "channelcolor", ".9,.9,.7,.8")
|
||||
pfUI:UpdateConfig("appearance", "castbar", "failcolor", "1,.2,.2,.8")
|
||||
pfUI:UpdateConfig("appearance", "castbar", "shieldcolor", ".8,.45,.45,.8")
|
||||
pfUI:UpdateConfig("appearance", "castbar", "fsrcolor", ".9,.6,.2,.8")
|
||||
pfUI:UpdateConfig("appearance", "castbar", "texture", "Interface\\AddOns\\pfUI\\img\\bar")
|
||||
pfUI:UpdateConfig("appearance", "infight", "screen", "0")
|
||||
pfUI:UpdateConfig("appearance", "infight", "aggro", "0")
|
||||
@@ -326,9 +324,6 @@ function pfUI:LoadConfig()
|
||||
pfUI:UpdateConfig("unitframes", "player", "showRest", "0")
|
||||
pfUI:UpdateConfig("unitframes", "player", "energy", "1")
|
||||
pfUI:UpdateConfig("unitframes", "player", "manatick", "0")
|
||||
pfUI:UpdateConfig("unitframes", "player", "showfsr", "1")
|
||||
pfUI:UpdateConfig("unitframes", "player", "fsrheight", "4")
|
||||
pfUI:UpdateConfig("unitframes", "player", "fsrstyle", "0")
|
||||
pfUI:UpdateConfig("unitframes", "player", "display_haste", "0")
|
||||
pfUI:UpdateConfig("unitframes", "player", "display_haste_color", "1,1,1,1")
|
||||
pfUI:UpdateConfig("unitframes", "player", "display_spellpower", "1")
|
||||
|
||||
+14
-37
@@ -18,7 +18,12 @@ local ItemQuality = Enum.ItemQuality
|
||||
local function SortCategoryPrefix(itemId, classID, quality)
|
||||
if itemId == HEARTHSTONE_ITEM_ID then return "00" end
|
||||
if quality == ItemQuality.Poor then return "13" end -- gray always last
|
||||
if classID == ItemClass.Weapon or classID == ItemClass.Armor then return "01" end
|
||||
if classID == ItemClass.Weapon or classID == ItemClass.Armor then
|
||||
if quality and quality >= ItemQuality.Epic then return "01" end -- Epic+ gear
|
||||
if quality == ItemQuality.Rare then return "02" end -- Rare gear
|
||||
if quality == ItemQuality.Uncommon then return "03" end -- Uncommon gear
|
||||
return "04" -- Common/poor gear
|
||||
end
|
||||
if classID == ItemClass.Consumable then return "05" end
|
||||
if classID == ItemClass.Reagent then return "06" end
|
||||
if classID == ItemClass.Tradegoods then return "07" end
|
||||
@@ -37,40 +42,12 @@ local function SortCountSuffix(count)
|
||||
return string.sub(s, -6)
|
||||
end
|
||||
|
||||
-- Gear's slot order, keyed by the equipLoc token (INVTYPE_*). Not the
|
||||
-- token's alphabetical order, which puts chest before head, and not its
|
||||
-- INVTYPE number, which parts robe (20) from chest (5). Same order as
|
||||
-- ClassicAPI's C_Container.SortBags, taken from Baganator: weapons by hand,
|
||||
-- ranged and ammo, then armor with shields and off-hands first, head to
|
||||
-- feet, then jewelry, shirt and tabard.
|
||||
local SLOT_ORDER = {
|
||||
"INVTYPE_2HWEAPON", "INVTYPE_WEAPON", "INVTYPE_WEAPONMAINHAND",
|
||||
"INVTYPE_SHIELD", "INVTYPE_HOLDABLE", "INVTYPE_RANGEDRIGHT",
|
||||
"INVTYPE_WEAPONOFFHAND", "INVTYPE_RANGED", "INVTYPE_THROWN",
|
||||
"INVTYPE_AMMO", "INVTYPE_QUIVER", "INVTYPE_RELIC",
|
||||
"INVTYPE_HEAD", "INVTYPE_SHOULDER", "INVTYPE_CLOAK", "INVTYPE_CHEST",
|
||||
"INVTYPE_ROBE", "INVTYPE_WRIST", "INVTYPE_HAND", "INVTYPE_WAIST",
|
||||
"INVTYPE_LEGS", "INVTYPE_FEET",
|
||||
"INVTYPE_NECK", "INVTYPE_FINGER", "INVTYPE_TRINKET", "INVTYPE_BODY",
|
||||
"INVTYPE_TABARD", "INVTYPE_BAG",
|
||||
}
|
||||
local SLOT_RANK = {}
|
||||
for i, token in ipairs(SLOT_ORDER) do SLOT_RANK[token] = i end
|
||||
|
||||
local function SortKey(itemId, name, classID, subClassID, quality, count, equipLoc)
|
||||
-- Zero-pad the numeric fields so they sort numerically as strings ("10"
|
||||
-- would otherwise precede "2").
|
||||
local key = SortCategoryPrefix(itemId, classID, quality)
|
||||
.. string.format("%02d|", classID or 99)
|
||||
if classID == ItemClass.Weapon or classID == ItemClass.Armor then
|
||||
-- Gear: slot, then subtype, then best quality first, so all helms sit
|
||||
-- together, then all shoulders.
|
||||
key = key .. string.format("%02d|%02d|%d|",
|
||||
SLOT_RANK[equipLoc] or 99, subClassID or 99, 9 - (quality or 0))
|
||||
else
|
||||
key = key .. string.format("%02d|", subClassID or 99)
|
||||
end
|
||||
return key .. (name or "zzz") .. "|" .. SortCountSuffix(count)
|
||||
local function SortKey(itemId, name, classID, subClassID, quality, count)
|
||||
-- Zero-pad the class/subclass so the secondary grouping sorts numerically
|
||||
-- (as a string, "10" would otherwise precede "2").
|
||||
return SortCategoryPrefix(itemId, classID, quality)
|
||||
.. string.format("%02d|%02d|", classID or 99, subClassID or 99)
|
||||
.. (name or "zzz") .. "|" .. SortCountSuffix(count)
|
||||
end
|
||||
|
||||
local function ClearSortData()
|
||||
@@ -191,10 +168,10 @@ local function BuildSortGrid()
|
||||
-- sit at positions 12/13. We categorize on those numeric class IDs
|
||||
-- rather than the localized itemType/itemSubType strings. (pfUI's
|
||||
-- shimmed global GetItemInfo is only 10 fields and lacks them.)
|
||||
local name, _, quality, _, _, _, _, _, equipLoc, _, _, classID, subClassID = C_Item.GetItemInfo(itemId)
|
||||
local name, _, quality, _, _, _, _, _, _, _, _, classID, subClassID = C_Item.GetItemInfo(itemId)
|
||||
local _, count = GetContainerItemInfo(bag, slot)
|
||||
local item = {
|
||||
key = SortKey(itemId, name, classID, subClassID, quality, count, equipLoc),
|
||||
key = SortKey(itemId, name, classID, subClassID, quality, count),
|
||||
-- vanilla items carry at most one family bit, so equality
|
||||
-- against a bag family suffices (no bit.band needed).
|
||||
family = C_Item.GetItemFamily(itemId) or 0,
|
||||
|
||||
+1
-1
@@ -937,7 +937,7 @@ if hasNampower then
|
||||
local numMissed = arg7 or 0
|
||||
|
||||
-- Fire registered SPELL_GO_SELF hooks BEFORE miss guard
|
||||
-- (Swingtimer needs to see ALL casts, even misses, to clear its queue color)
|
||||
-- (Swingtimer needs to see ALL casts, even misses, for swing reset)
|
||||
if event == "SPELL_GO_SELF" and pfUI.libdebuff_spell_go_hooks then
|
||||
for _, fn in pairs(pfUI.libdebuff_spell_go_hooks) do
|
||||
fn(spellId, arg1, arg2, arg3, arg4, arg5, arg6, arg7)
|
||||
|
||||
+5
-8
@@ -86,7 +86,7 @@ pfUI:RegisterModule("castbar", function ()
|
||||
|
||||
-- Stamp the bar with cast data and render text/icon/lag once. OnUpdate
|
||||
-- then animates the fill from this state without touching C_Spell.
|
||||
local function StampBar(cb, name, tex, startMs, endMs, spellID, isChannel, delayMs, isTradeskill, rank, noInterrupt)
|
||||
local function StampBar(cb, name, tex, startMs, endMs, spellID, isChannel, delayMs, isTradeskill, rank)
|
||||
cb.startTime = startMs
|
||||
cb.endTime = endMs
|
||||
cb.isChannel = isChannel
|
||||
@@ -97,10 +97,7 @@ pfUI:RegisterModule("castbar", function ()
|
||||
cb:SetAlpha(1)
|
||||
cb.fadeout = nil
|
||||
|
||||
-- Shield an uninterruptible cast. Not on the player's own bar: the flag is
|
||||
-- measured against the reader's own interrupts and silences, so on your own
|
||||
-- cast it answers a question nobody asked.
|
||||
SetCastbarShield(cb.bar, cb.icon, noInterrupt and cb.unitstr ~= "player", isChannel)
|
||||
cb.bar:SetStatusBarColor(GetStringColor(C.appearance.castbar[isChannel and "channelcolor" or "castbarcolor"]))
|
||||
|
||||
-- Rank: prefer the value the UNIT_SPELLCAST_* event delivered (arg5, passed
|
||||
-- through by RefreshBar). Only the retarget re-poll has no event in hand, so
|
||||
@@ -155,10 +152,10 @@ pfUI:RegisterModule("castbar", function ()
|
||||
ClearBar(cb)
|
||||
return
|
||||
end
|
||||
local name, _, tex, startMs, endMs, isTradeskill, _, noInterrupt, spellID, _, delayMs = C_Spell.UnitCastingInfo(query)
|
||||
local name, _, tex, startMs, endMs, isTradeskill, _, _, spellID, _, delayMs = C_Spell.UnitCastingInfo(query)
|
||||
local isChan
|
||||
if not name then
|
||||
name, _, tex, startMs, endMs, _, noInterrupt, spellID = C_Spell.UnitChannelInfo(query)
|
||||
name, _, tex, startMs, endMs, _, _, spellID = C_Spell.UnitChannelInfo(query)
|
||||
isChan = true
|
||||
end
|
||||
-- Synthetic fallback for abilities the engine treats as instant-cast but
|
||||
@@ -172,7 +169,7 @@ pfUI:RegisterModule("castbar", function ()
|
||||
end
|
||||
end
|
||||
if name and startMs and endMs then
|
||||
StampBar(cb, name, tex, startMs, endMs, spellID, isChan, delayMs, isTradeskill, rank, noInterrupt)
|
||||
StampBar(cb, name, tex, startMs, endMs, spellID, isChan, delayMs, isTradeskill, rank)
|
||||
else
|
||||
ClearBar(cb)
|
||||
end
|
||||
|
||||
+7
-44
@@ -89,13 +89,6 @@ pfUI:RegisterModule("energytick", function()
|
||||
return C.unitframes.player.pwidth ~= "-1" and C.unitframes.player.pwidth or C.unitframes.player.width
|
||||
end
|
||||
|
||||
local rawborder, default_border = GetBorderSize("unitframes")
|
||||
local cbtexture = pfUI.media[C.appearance.castbar.texture]
|
||||
|
||||
-- style is read once: switching it in the GUI needs a /reload, same as the
|
||||
-- other structural (not cosmetic) toggles in this file
|
||||
local fsrOverlay = C.unitframes.player.fsrstyle == "1"
|
||||
|
||||
-- was this gain the regen tick? if so, re-anchor the sweep on it
|
||||
local function lockTick(frame)
|
||||
local now, period = GetTime(), getAdjustedTickTimer()
|
||||
@@ -206,21 +199,11 @@ pfUI:RegisterModule("energytick", function()
|
||||
end
|
||||
this.tick = GetTime() + 0.020 -- ~50 FPS
|
||||
|
||||
-- five-second rule drains to nothing. The display is cosmetic and can be
|
||||
-- hidden on its own; the window itself (fsrEnd/fsrGain) keeps running
|
||||
-- either way -- it still dims the sweep spark below.
|
||||
-- five-second rule drains to nothing
|
||||
local remaining = this.fsrEnd and (this.fsrEnd - GetTime()) or 0
|
||||
if this.mode == "MANA" and remaining > 0 then
|
||||
if C.unitframes.player.showfsr == "1" then
|
||||
if fsrOverlay then
|
||||
this.fsrbar:SetWidth(getBarWidth() * remaining / FIVE_SECOND_RULE)
|
||||
else
|
||||
this.fsrbar:SetValue(remaining)
|
||||
end
|
||||
this.fsrbar:Show()
|
||||
else
|
||||
this.fsrbar:Hide()
|
||||
end
|
||||
this.fsrbar:SetWidth(getBarWidth() * remaining / FIVE_SECOND_RULE)
|
||||
this.fsrbar:Show()
|
||||
else
|
||||
this.fsrSpell, this.fsrEnd, this.fsrGain = nil, nil, nil
|
||||
this.fsrbar:Hide()
|
||||
@@ -257,26 +240,10 @@ pfUI:RegisterModule("energytick", function()
|
||||
this.spark:SetPoint("LEFT", pos - ((C.unitframes.player.pheight + 5) / 2), 0)
|
||||
end)
|
||||
|
||||
if fsrOverlay then
|
||||
-- classic style: a translucent shade on top of the power bar's own fill
|
||||
energytick.fsrbar = energytick:CreateTexture(nil, "ARTWORK")
|
||||
energytick.fsrbar:SetTexture(1, 1, 1, .15)
|
||||
energytick.fsrbar:SetPoint("TOPLEFT", 0, 0)
|
||||
energytick.fsrbar:SetPoint("BOTTOMLEFT", 0, 0)
|
||||
else
|
||||
-- a real bar below the power bar, not an overlay on top of it -- the same
|
||||
-- treatment nameplate castbars get relative to the health bar, so the
|
||||
-- window can't be mistaken for missing mana
|
||||
energytick.fsrbar = CreateFrame("StatusBar", nil, pfUI.uf.player)
|
||||
energytick.fsrbar:SetStatusBarTexture(cbtexture)
|
||||
energytick.fsrbar:SetMinMaxValues(0, FIVE_SECOND_RULE)
|
||||
energytick.fsrbar:SetPoint("TOPLEFT", pfUI.uf.player.power.bar, "BOTTOMLEFT", 0, -default_border * 2)
|
||||
energytick.fsrbar:SetPoint("TOPRIGHT", pfUI.uf.player.power.bar, "BOTTOMRIGHT", 0, -default_border * 2)
|
||||
energytick.fsrbar:SetHeight(tonumber(C.unitframes.player.fsrheight) or 4)
|
||||
energytick.fsrbar:SetStatusBarColor(GetStringColor(C.appearance.castbar.fsrcolor))
|
||||
CreateBackdrop(energytick.fsrbar, default_border)
|
||||
CreateBackdropShadow(energytick.fsrbar)
|
||||
end
|
||||
energytick.fsrbar = energytick:CreateTexture(nil, "ARTWORK")
|
||||
energytick.fsrbar:SetTexture(1, 1, 1, .15)
|
||||
energytick.fsrbar:SetPoint("TOPLEFT", 0, 0)
|
||||
energytick.fsrbar:SetPoint("BOTTOMLEFT", 0, 0)
|
||||
energytick.fsrbar:Hide()
|
||||
|
||||
energytick.spark = energytick:CreateTexture(nil, "OVERLAY")
|
||||
@@ -289,10 +256,6 @@ pfUI:RegisterModule("energytick", function()
|
||||
function pfUI.uf.player.UpdateConfig()
|
||||
energytick.spark:SetHeight(C.unitframes.player.pheight + 15)
|
||||
energytick.spark:SetWidth(C.unitframes.player.pheight + 5)
|
||||
if not fsrOverlay then
|
||||
energytick.fsrbar:SetHeight(tonumber(C.unitframes.player.fsrheight) or 4)
|
||||
energytick.fsrbar:SetStatusBarColor(GetStringColor(C.appearance.castbar.fsrcolor))
|
||||
end
|
||||
hookUpdateConfig(pfUI.uf.player)
|
||||
end
|
||||
end)
|
||||
|
||||
@@ -301,16 +301,6 @@ pfUI:RegisterModule("equipmentmanager", function()
|
||||
pfUI.equipmentmanager.Refresh()
|
||||
end)
|
||||
|
||||
-- Drag a set onto an action bar. PickupEquipmentSet is newer than
|
||||
-- the rest of C_EquipmentSet, so dragging stays inert on ClassicAPI
|
||||
-- builds that don't ship it yet.
|
||||
if C_EquipmentSet.PickupEquipmentSet then
|
||||
row:RegisterForDrag("LeftButton")
|
||||
row:SetScript("OnDragStart", function()
|
||||
if row.setID then C_EquipmentSet.PickupEquipmentSet(row.setID) end
|
||||
end)
|
||||
end
|
||||
|
||||
-- Show the gear and tooltip while the cursor is over the row or its
|
||||
-- gear child. The gear sits inside the row's rectangle, so a single
|
||||
-- MouseIsOver(row) check covers both. OnLeave on the row and the gear
|
||||
|
||||
+2
-11
@@ -2072,6 +2072,8 @@ pfUI:RegisterModule("gui", function ()
|
||||
CreateConfig(nil, T["Combopoint Width"], C.unitframes, "combowidth")
|
||||
CreateConfig(nil, T["Combopoint Height"], C.unitframes, "comboheight")
|
||||
CreateConfig(nil, T["Show Resting"], C.unitframes.player, "showRest", "checkbox")
|
||||
CreateConfig(nil, T["Enable Energy Ticks"], C.unitframes.player, "energy", "checkbox")
|
||||
CreateConfig(nil, T["Enable Mana Ticks"], C.unitframes.player, "manatick", "checkbox")
|
||||
CreateConfig(nil, T["Detect Enemy Buffs"], C.unitframes, "buffdetect", "checkbox", nil, nil, nil, nil)
|
||||
CreateConfig(nil, T["Raid Mark Icon Style"], C.unitframes, "blizzard_raidicons", "dropdown", function() return {"1:Original Blizzard", "0:pfUI Design"} end)
|
||||
|
||||
@@ -2318,15 +2320,6 @@ pfUI:RegisterModule("gui", function ()
|
||||
CreateConfig(U[c], T["Right Text Y Offset"], C.unitframes[c], "txtpowerrightoffy")
|
||||
CreateConfig(U[c], T["Power Bar Anchor"], C.unitframes[c], "panchor", "dropdown", pfUI.gui.dropdowns.uf_powerbar_position)
|
||||
|
||||
if c == "player" then
|
||||
CreateConfig(nil, T["Energy/Mana Ticks"], nil, nil, "header")
|
||||
CreateConfig(nil, T["Enable Energy Ticks"], C.unitframes[c], "energy", "checkbox")
|
||||
CreateConfig(nil, T["Enable Mana Ticks"], C.unitframes[c], "manatick", "checkbox")
|
||||
CreateConfig(nil, T["Show Five Second Rule"], C.unitframes[c], "showfsr", "checkbox")
|
||||
CreateConfig(nil, T["Five Second Rule Style"], C.unitframes[c], "fsrstyle", "dropdown", function() return {"0:Separate Bar", "1:Overlay"} end)
|
||||
CreateConfig(nil, T["Five Second Rule Height"], C.unitframes[c], "fsrheight")
|
||||
end
|
||||
|
||||
CreateConfig(U[c], T["Combat Text"], nil, nil, "header")
|
||||
CreateConfig(U[c], T["Show Combat Text"], C.unitframes[c], "hitindicator", "checkbox")
|
||||
CreateConfig(U[c], T["Combat Text Font"], C.unitframes[c], "hitindicatorfont", "dropdown", pfUI.gui.dropdowns.fonts)
|
||||
@@ -2799,8 +2792,6 @@ pfUI:RegisterModule("gui", function ()
|
||||
CreateConfig(nil, T["Use Unit Fonts"], C.castbar, "use_unitfonts", "checkbox")
|
||||
CreateConfig(nil, T["Casting Color"], C.appearance.castbar, "castbarcolor", "color")
|
||||
CreateConfig(nil, T["Channeling Color"], C.appearance.castbar, "channelcolor", "color")
|
||||
CreateConfig(nil, T["Uninterruptible Color"], C.appearance.castbar, "shieldcolor", "color")
|
||||
CreateConfig(nil, T["Five Second Rule Color"], C.appearance.castbar, "fsrcolor", "color")
|
||||
CreateConfig(nil, T["Castbar Texture"], C.appearance.castbar, "texture", "dropdown", pfUI.gui.dropdowns.uf_bartexture)
|
||||
CreateConfig(nil, T["Disable Blizzard Castbar"], C.castbar.player, "hide_blizz", "checkbox")
|
||||
end)
|
||||
|
||||
@@ -77,10 +77,10 @@ pfUI:RegisterModule("nameplates", function ()
|
||||
-- vs channel itself. Never called per frame.
|
||||
local function PollCastInfo(unit)
|
||||
if not unit then return nil end
|
||||
local name, _, texture, startMs, endMs, _, _, noInterrupt, spellID = C_Spell.UnitCastingInfo(unit)
|
||||
local name, _, texture, startMs, endMs, _, _, _, spellID = C_Spell.UnitCastingInfo(unit)
|
||||
local isChannel
|
||||
if not name then
|
||||
name, _, texture, startMs, endMs, _, noInterrupt, spellID = C_Spell.UnitChannelInfo(unit)
|
||||
name, _, texture, startMs, endMs, _, _, spellID = C_Spell.UnitChannelInfo(unit)
|
||||
isChannel = true
|
||||
end
|
||||
if not name or not startMs or not endMs then return nil end
|
||||
@@ -92,7 +92,6 @@ pfUI:RegisterModule("nameplates", function ()
|
||||
endTime = endMs / 1000,
|
||||
duration = (endMs - startMs) / 1000,
|
||||
isChannel = isChannel,
|
||||
noInterrupt = noInterrupt,
|
||||
}
|
||||
end
|
||||
|
||||
@@ -1752,7 +1751,7 @@ nameplates:RegisterEvent("PLAYER_GUILD_UPDATE")
|
||||
-- Relative 0..duration range to avoid float precision loss with large
|
||||
-- absolute timestamps.
|
||||
nameplate.castbar:SetMinMaxValues(0, duration)
|
||||
SetCastbarShield(nameplate.castbar, nameplate.castbar.icon, castInfo.noInterrupt, isChannel)
|
||||
nameplate.castbar:SetStatusBarColor(GetStringColor(C.appearance.castbar[(isChannel and "channelcolor" or "castbarcolor")]))
|
||||
if castInfo.icon then
|
||||
nameplate.castbar.icon.tex:SetTexture(castInfo.icon)
|
||||
nameplate.castbar.icon.tex:SetTexCoord(.1,.9,.1,.9)
|
||||
|
||||
+174
-31
@@ -1,6 +1,10 @@
|
||||
pfUI:RegisterModule("swingtimer", function ()
|
||||
local rawborder, border = GetBorderSize()
|
||||
|
||||
-- HitInfo flags (EVENTS.md)
|
||||
local HITINFO_LEFTSWING = 4 -- 4: Off-hand attack
|
||||
local HITINFO_NOACTION = 65536 -- 65536: server did not advance the swing clock
|
||||
|
||||
-- SPELL_QUEUE_EVENT codes (EVENTS.md)
|
||||
local ON_SWING_QUEUED = 0
|
||||
local ON_SWING_QUEUE_POPPED = 1
|
||||
@@ -15,6 +19,8 @@ pfUI:RegisterModule("swingtimer", function ()
|
||||
lastMhMarkerX = -1, lastOhMarkerX = -1, lastRaMarkerX = -1,
|
||||
autoAttackActive = false,
|
||||
inCombat = false,
|
||||
pendingCastSpellId = nil,
|
||||
mhFrozenAt = nil,
|
||||
hsQueued = false, cleaveQueued = false, maulQueued = false,
|
||||
hsSeenCurrent = false, cleaveSeenCurrent = false, maulSeenCurrent = false,
|
||||
isWarrior = false,
|
||||
@@ -25,6 +31,13 @@ pfUI:RegisterModule("swingtimer", function ()
|
||||
onSwingCache = {},
|
||||
}
|
||||
|
||||
-- Wand "Shoot" runs on the ranged bar but is INDEPENDENT of the melee
|
||||
-- swing clock — casters melee-weave between mainhand swings and wand
|
||||
-- fires, both timers tick concurrently. Every other ranged-auto-attack
|
||||
-- (Hunter Auto Shot, any future auto-repeat ranged spell) replaces MH.
|
||||
local WAND_SHOOT_SPELLID = 5019
|
||||
local THROW_SPELLID = 2764 -- one-shot ranged, not auto-repeat
|
||||
|
||||
-- On-next-swing spells replace the next auto-attack swing.
|
||||
-- Covers Raptor Strike, Maul, Mongoose Bite, Holy Strike, etc. automatically.
|
||||
local function IsOnSwingSpell(spellId)
|
||||
@@ -291,37 +304,45 @@ pfUI:RegisterModule("swingtimer", function ()
|
||||
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)
|
||||
-- Reset MH countdown to full speed (server confirmed swing)
|
||||
local function ResetMH()
|
||||
if S.mhFrozenAt then
|
||||
S.mhFrozenAt = nil
|
||||
end
|
||||
UpdateWeaponSpeeds()
|
||||
pfUI.swingtimer.mhGraceAt = nil -- cancel any pending hide
|
||||
S.mhTimerMax = math.max(S.mhSpeed, remaining)
|
||||
S.mhTimer = remaining
|
||||
S.mhTimerMax = S.mhSpeed
|
||||
S.mhTimer = S.mhSpeed
|
||||
S.mhActive = true
|
||||
pfUI.swingtimer.mainhand:Show()
|
||||
pfUI.swingtimer:Show()
|
||||
end
|
||||
|
||||
local function StartOH(remaining)
|
||||
-- Reset OH countdown to full speed
|
||||
local function ResetOH()
|
||||
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.ohTimerMax = S.ohSpeed
|
||||
S.ohTimer = S.ohSpeed
|
||||
S.ohActive = true
|
||||
if sw_showoh then pfUI.swingtimer.offhand:Show() end
|
||||
pfUI.swingtimer:Show()
|
||||
end
|
||||
|
||||
local function StartRanged(remaining)
|
||||
-- Reset ranged countdown. replaceMH=true (Hunter Auto Shot, Throw) stops
|
||||
-- the melee swing clock while ranged ticks; replaceMH=false (wand Shoot)
|
||||
-- leaves it running for melee weaving.
|
||||
local function ResetRanged(replaceMH)
|
||||
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
|
||||
if replaceMH then
|
||||
S.mhActive = false
|
||||
pfUI.swingtimer.mainhand:Hide()
|
||||
end
|
||||
S.raTimerMax = S.raSpeed
|
||||
S.raTimer = S.raSpeed
|
||||
S.raActive = true
|
||||
|
||||
if isHunter then
|
||||
@@ -465,17 +486,23 @@ pfUI:RegisterModule("swingtimer", function ()
|
||||
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
|
||||
-- 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 S.mhActive then
|
||||
S.mhTimer = S.mhTimer - delta
|
||||
-- Don't tick while frozen (swing-delay spell like Slam is being cast)
|
||||
if not S.mhFrozenAt then
|
||||
S.mhTimer = S.mhTimer - delta
|
||||
end
|
||||
if S.mhTimer <= 0 then
|
||||
S.mhTimer = 0
|
||||
if not pfUI.swingtimer.mhGraceAt then
|
||||
if S.mhFrozenAt then
|
||||
-- Swing-delay spell cast in progress: bar holds at 0, skip grace/hide.
|
||||
-- SPELL_GO handler will unfreeze and add cast duration to timer.
|
||||
elseif not pfUI.swingtimer.mhGraceAt then
|
||||
pfUI.swingtimer.mhGraceAt = GetTime() + GRACE
|
||||
elseif GetTime() >= pfUI.swingtimer.mhGraceAt then
|
||||
pfUI.swingtimer.mhGraceAt = nil
|
||||
@@ -695,13 +722,80 @@ pfUI:RegisterModule("swingtimer", function ()
|
||||
end
|
||||
end)
|
||||
|
||||
-- SPELL_GO hook via libdebuff: an on-next-swing ability (HS / Cleave /
|
||||
-- Maul) resolving consumes the queue, so drop the queued color.
|
||||
-- SPELL_START_SELF: fires only for cast-time spells, never instants
|
||||
local spellStartFrame = CreateFrame("Frame")
|
||||
spellStartFrame:RegisterEvent("SPELL_START_SELF")
|
||||
spellStartFrame:SetScript("OnEvent", function()
|
||||
if not (arg1 and arg1 > 0) then return end
|
||||
S.pendingCastSpellId = arg1
|
||||
-- Freeze the swing timer for cast-time spells that DON'T reset auto-
|
||||
-- attack on completion (Slam, Hammer of Wrath on Turtle, etc.) — those
|
||||
-- let the swing resume from where it paused. C_Spell.ResetsMeleeSwing
|
||||
-- mirrors the server rule; spells that reset don't need freezing (they
|
||||
-- reset on SPELL_GO_SELF). Subsumes the old hardcoded swingDelaySpells
|
||||
-- list (no list maintenance for new Slam-style spells).
|
||||
if S.mhActive then
|
||||
if not C_Spell.ResetsMeleeSwing(arg1) then
|
||||
S.mhFrozenAt = GetTime()
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- Cast cancel/fail/interrupt: unfreeze swing timer if a delay spell was interrupted
|
||||
local spellFailFrame = CreateFrame("Frame")
|
||||
spellFailFrame:RegisterEvent("SPELLCAST_FAILED")
|
||||
spellFailFrame:RegisterEvent("SPELLCAST_INTERRUPTED")
|
||||
spellFailFrame:SetScript("OnEvent", function()
|
||||
if S.mhFrozenAt then
|
||||
S.mhFrozenAt = nil
|
||||
end
|
||||
S.pendingCastSpellId = nil
|
||||
end)
|
||||
|
||||
-- SPELL_GO hook via libdebuff
|
||||
pfUI.libdebuff_spell_go_hooks = pfUI.libdebuff_spell_go_hooks or {}
|
||||
pfUI.libdebuff_spell_go_hooks["swingtimer"] = function(spellId)
|
||||
if IsOnSwingSpell(spellId) then
|
||||
-- C_Spell.IsRangedAutoAttackSpell catches both Auto Shot (75) and
|
||||
-- wand Shoot (5019) via Spell.dbc's AUTO_REPEAT attribute (covers
|
||||
-- any future auto-repeat ranged spell automatically). Wand is the
|
||||
-- one independent of the MH swing — everything else replaces it.
|
||||
-- Throw isn't auto-repeat (single-shot) so it's handled explicitly.
|
||||
if C_Spell.IsRangedAutoAttackSpell(spellId) then
|
||||
ResetRanged(spellId ~= WAND_SHOOT_SPELLID)
|
||||
return
|
||||
elseif spellId == THROW_SPELLID then
|
||||
ResetRanged(true)
|
||||
return
|
||||
elseif IsOnSwingSpell(spellId) then
|
||||
-- On-next-swing ability (HS / Cleave / Maul / Raptor Strike / etc.)
|
||||
-- — the swing fires as the spell consumes it. Drop the queued color.
|
||||
S.hsQueued = false; S.cleaveQueued = false; S.maulQueued = false
|
||||
ResetMH()
|
||||
else
|
||||
-- C_Spell.ResetsMeleeSwing mirrors the server rule (Turtle's
|
||||
-- Spell::IsMeleeAttackResetSpell): InterruptFlags has AUTOATTACK and
|
||||
-- AttributesEx2 lacks NOT_RESET_AUTO_ACTIONS. When the spell resets the
|
||||
-- swing, snap the timers to full. Otherwise (elseif) a frozen-swing-
|
||||
-- during-cast is a Slam-style cast — push the timer forward by the cast
|
||||
-- duration so the bar resumes from where it paused.
|
||||
if C_Spell.ResetsMeleeSwing(spellId) then
|
||||
if S.mhActive and S.mhSpeed > 0 then
|
||||
UpdateWeaponSpeeds()
|
||||
S.mhTimerMax = S.mhSpeed
|
||||
S.mhTimer = S.mhSpeed
|
||||
end
|
||||
if S.ohActive and S.ohSpeed > 0 then
|
||||
S.ohTimerMax = S.ohSpeed
|
||||
S.ohTimer = S.ohSpeed
|
||||
end
|
||||
elseif S.mhFrozenAt then
|
||||
local castDuration = GetTime() - S.mhFrozenAt
|
||||
S.mhTimer = S.mhTimer + castDuration
|
||||
S.mhTimerMax = S.mhTimerMax + castDuration
|
||||
S.mhFrozenAt = nil
|
||||
end
|
||||
end
|
||||
S.pendingCastSpellId = nil
|
||||
end
|
||||
|
||||
-- SPELL_CAST_EVENT hook: HS/Cleave/Maul queue tracking
|
||||
@@ -715,7 +809,8 @@ pfUI:RegisterModule("swingtimer", function ()
|
||||
|
||||
|
||||
local events = CreateFrame("Frame")
|
||||
events:RegisterEvent("PLAYER_SWING")
|
||||
events:RegisterEvent("AUTO_ATTACK_SELF")
|
||||
events:RegisterEvent("AUTO_ATTACK_OTHER")
|
||||
events:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||||
events:RegisterEvent("PLAYER_EQUIPMENT_CHANGED") -- ClassicAPI: per-slot, equipment-only
|
||||
events:RegisterEvent("PLAYER_REGEN_DISABLED")
|
||||
@@ -727,16 +822,64 @@ pfUI:RegisterModule("swingtimer", function ()
|
||||
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 == Enum.PlayerSwingType.MainHand then
|
||||
StartMH(arg1)
|
||||
elseif arg2 == Enum.PlayerSwingType.OffHand then
|
||||
StartOH(arg1)
|
||||
elseif arg2 == Enum.PlayerSwingType.Ranged then
|
||||
StartRanged(arg1)
|
||||
if event == "AUTO_ATTACK_SELF" then
|
||||
local hitInfo = arg4 or 0
|
||||
local isOffhand = bit.band(hitInfo, HITINFO_LEFTSWING) ~= 0
|
||||
local noAction = bit.band(hitInfo, HITINFO_NOACTION) ~= 0
|
||||
-- NOACTION means server did not advance swing clock (dodge/parry/miss).
|
||||
-- Only skip if the timer is already running - if it's not active yet
|
||||
-- (first swing ever), we still want to start it so the bar appears.
|
||||
if noAction then
|
||||
if isOffhand and S.ohActive then return end
|
||||
if not isOffhand and S.mhActive then return end
|
||||
end
|
||||
|
||||
-- Extra attack detection: if timer still has >20% remaining for that hand,
|
||||
-- the server did NOT reset the swing clock -> this is an extra attack, skip.
|
||||
-- Use 20% here (SP_SwingTimer's ShouldResetTimer threshold).
|
||||
-- Exception: if timer is already at 0 (expired), always accept.
|
||||
if isOffhand then
|
||||
local pct = S.ohActive and (S.ohTimer / S.ohTimerMax) or 0
|
||||
if S.ohActive and S.ohTimer > 0 and pct > 0.20 then
|
||||
return
|
||||
end
|
||||
ResetOH()
|
||||
else
|
||||
local pct = S.mhActive and (S.mhTimer / S.mhTimerMax) or 0
|
||||
if S.mhActive and S.mhTimer > 0 and pct > 0.20 then
|
||||
return
|
||||
end
|
||||
ResetMH()
|
||||
end
|
||||
|
||||
elseif event == "AUTO_ATTACK_OTHER" then
|
||||
-- Parry haste: enemy attacked the player and player parried
|
||||
local targetGuid = arg2
|
||||
if not targetGuid or not IsPlayerGuid(targetGuid) then return end
|
||||
local victimState = arg5 or 0
|
||||
-- VICTIMSTATE_PARRY = 3
|
||||
-- Vanilla: parry reduces the NEXT swing timer by 40% of weapon speed,
|
||||
-- minimum 20% of weapon speed remaining (SP_SwingTimer approach)
|
||||
if victimState == 3 then
|
||||
-- Apply to whichever swing comes next (smallest % remaining = closest to firing)
|
||||
if S.ohActive and S.ohSpeed > 0 and (S.ohTimer / S.ohTimerMax) < (S.mhTimer / S.mhTimerMax) then
|
||||
local minimum = S.ohSpeed * 0.20
|
||||
if S.ohTimer > minimum then
|
||||
local reduct = S.ohSpeed * 0.40
|
||||
local before = S.ohTimer
|
||||
S.ohTimer = S.ohTimer - reduct
|
||||
if S.ohTimer < minimum then S.ohTimer = minimum end
|
||||
end
|
||||
elseif S.mhActive and S.mhSpeed > 0 then
|
||||
local minimum = S.mhSpeed * 0.20
|
||||
if S.mhTimer > minimum then
|
||||
local reduct = S.mhSpeed * 0.40
|
||||
local before = S.mhTimer
|
||||
S.mhTimer = S.mhTimer - reduct
|
||||
if S.mhTimer < minimum then S.mhTimer = minimum end
|
||||
end
|
||||
else
|
||||
end
|
||||
end
|
||||
|
||||
elseif event == "SPELL_QUEUE_EVENT" then
|
||||
|
||||
Reference in New Issue
Block a user