Files
pfUI/modules/actionbar.lua
T
Brues 740940dece actionbar: defer to SuperCleveRoidMacros for managed macros
In ButtonMacroScan, when SuperCleveRoidMacros manages the slot
(CleveRoids.IsManagedAction), leave spellslot/booktype unset and return
early. This lets the button's icon, cooldown, and tooltip flow through
the addon's hooked GetActionTexture / GetActionCooldown /
GameTooltip:SetAction so they follow the active conditional dynamically,
instead of being frozen to the first statically-scanned spell.
2026-06-21 00:11:29 -05:00

1619 lines
50 KiB
Lua
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
pfUI:RegisterModule("actionbar", function ()
local _, class = UnitClass("player")
local _, cr, cg, cb = GetUnitColor('player')
local er, eg, eb, ea = GetStringColor(pfUI_config.appearance.border.color)
local backdrop_highlight = { edgeFile = pfUI.media["img:glow"], edgeSize = 8 }
local showgrid = 0
local showgrid_pet = 0
local rawborder, border = GetBorderSize("actionbars")
local bpad = rawborder > 1 and border - GetPerfectPixel() or GetPerfectPixel()
local eventcache = { } -- contains a list of events that shall be processed later -> [event] = true
local updatecache = { } -- contains a list of buttons slots that shall be refreshed later -> [slot] = true
local buttoncache = { } -- contains a list of all buttons ever created -> [slot] = frame
-- try to assume based on the current mouse positions if a button drag
-- should happen even if action-on-key-down is used. By that replace the
-- cast events by reverting the active buttons to the old mouse-up state.
local drag_await
local drag_active
local function AssumeButtonDrag()
-- skip if keydown press is not enabled
if C.bars.keydown ~= "1" then return end
-- skip if always shift-drag is not enabled
if C.bars.shiftdrag ~= "1" then return end
if drag_await and not drag_active and IsShiftKeyDown() then
drag_active = true
-- set all buttons to regular on release clicks
for id, button in pairs(buttoncache) do
button:RegisterForClicks("LeftButtonUp", "RightButtonUp")
end
elseif drag_active and not IsShiftKeyDown() then
drag_active = nil
-- set all buttons back to their defaults
for id, button in pairs(buttoncache) do
button:RegisterForClicks("LeftButtonDown", "RightButtonDown")
end
end
end
-- hide blizzard bars
local function kill(f, killshow)
if f.Show and killshow then f.Show = function() return end end
if f.UnregisterAllEvents then f:UnregisterAllEvents() end
if f.Hide then f:Hide() end
end
-- also abbreviate mouse buttons
local OrigGetBindingText = GetBindingText
local function GetBindingText(msg, mod, abbrev)
local txt = OrigGetBindingText(msg, mod, abbrev)
if abbrev then
txt = string.gsub(txt, _G[string.format("%s%s", mod, "BUTTON3")], "MB3")
txt = string.gsub(txt, _G[string.format("%s%s", mod, "BUTTON4")], "MB4")
txt = string.gsub(txt, _G[string.format("%s%s", mod, "BUTTON5")], "MB5")
txt = string.gsub(txt, _G[string.format("%s%s", mod, "MOUSEWHEELDOWN")], "MWD")
txt = string.gsub(txt, _G[string.format("%s%s", mod, "MOUSEWHEELUP")], "MWU")
end
return txt
end
kill(MainMenuBar)
local blizzard_elements = { MultiBarBottomLeft,
MultiBarBottomRight, MultiBarLeft, MultiBarRight }
for _, f in pairs(blizzard_elements) do
kill(f, true)
end
-- enable all possible actionbar pages
SetActionBarToggles(0, 0, 0, 0)
_G.SHOW_MULTI_ACTIONBAR_1 = nil
_G.SHOW_MULTI_ACTIONBAR_2 = nil
_G.SHOW_MULTI_ACTIONBAR_3 = nil
_G.SHOW_MULTI_ACTIONBAR_4 = nil
-- events that provide special updater functions
local special_events = {
["ACTIONBAR_UPDATE_COOLDOWN"] = true,
["ACTIONBAR_UPDATE_USABLE"] = true,
["ACTIONBAR_UPDATE_STATE"] = true,
}
-- events that are shared across all buttons
local global_events = {
-- slot/button updates
["PLAYER_ENTERING_WORLD"] = true,
["ACTIONBAR_SLOT_CHANGED"] = true,
["UPDATE_BINDINGS"] = true,
["ACTIONBAR_PAGE_CHANGED"] = true,
["UPDATE_BONUS_ACTIONBAR"] = true,
["CRAFT_SHOW"] = true,
["CRAFT_CLOSE"] = true,
["TRADE_SKILL_SHOW"] = true,
["TRADE_SKILL_CLOSE"] = true,
["PLAYER_ENTER_COMBAT"] = true,
["PLAYER_LEAVE_COMBAT"] = true,
-- cooldown updates
["UNIT_INVENTORY_CHANGED"] = true,
-- auto repeat action
["START_AUTOREPEAT_SPELL"] = true,
["STOP_AUTOREPEAT_SPELL"] = true,
}
-- events that are used for the aura/shapeshift bar
local aura_events = {
["UPDATE_SHAPESHIFT_FORMS"] = true,
["PLAYER_AURAS_CHANGED"] = true,
}
-- events that are used for the pet bar
local pet_events = {
["PLAYER_CONTROL_LOST"] = true,
["PLAYER_CONTROL_GAINED"] = true,
["PLAYER_FARSIGHT_FOCUS_CHANGED"] = true,
["UNIT_PET"] = true,
["PET_BAR_UPDATE"] = true,
["PET_BAR_UPDATE_COOLDOWN"] = true,
}
local buttontypes = {
-- blizzard keybinds
[3] = "MULTIACTIONBAR3BUTTON",
[4] = "MULTIACTIONBAR4BUTTON",
[5] = "MULTIACTIONBAR2BUTTON",
[6] = "MULTIACTIONBAR1BUTTON",
[11] = "SHAPESHIFTBUTTON",
[12] = "BONUSACTIONBUTTON",
-- additional keybinds
[2] = "PFPAGING",
[7] = "PFSTANCEONE",
[8] = "PFSTANCETWO",
[9] = "PFSTANCETHREE",
[10] = "PFSTANCEFOUR",
}
local blizzbarmapping = {
["MultiBarRight"] = 3,
["MultiBarLeft"] = 4,
["MultiBarBottomRight"] = 5,
["MultiBarBottomLeft"] = 6,
["ShapeShiftBar"] = 11,
["BonusActionBar"] = 12,
}
local barnames = {
[1] = "Main",
[2] = "Paging",
[3] = "Right",
[4] = "Vertical",
[5] = "Left",
[6] = "Top",
[7] = "StanceBar1",
[8] = "StanceBar2",
[9] = "StanceBar3",
[10] = "StanceBar4",
[11] = "Stances",
[12] = "Pet",
}
local button_animations = {
["none"] = function()
this.active = nil
this:Hide()
end,
["zoomfade"] = function()
if this.active == 0 then
-- init animation
this:SetWidth(this.parent:GetWidth())
this:SetHeight(this.parent:GetHeight())
this:SetScale(this.parent:GetScale())
this.tex:SetTexture(this.parent.icon:GetTexture())
this.tex:SetVertexColor(this.parent.icon:GetVertexColor())
this:SetAlpha(1)
this.active = 1
return
elseif this.active == 1 then
-- run animation
local fade = 30/GetFramerate()*0.05
this:SetAlpha(this:GetAlpha() - fade)
this:SetScale(this:GetScale() + fade)
if this:GetAlpha() > 0 then return end
end
-- stop animation
this.active = nil
this:Hide()
end,
["shrinkreturn"] = function()
if this.active == 0 then
-- init animation
this:SetWidth(this.parent:GetWidth())
this:SetHeight(this.parent:GetHeight())
this:SetScale(this.parent:GetScale())
this.tex:SetTexture(this.parent.icon:GetTexture())
this.tex:SetVertexColor(this.parent.icon:GetVertexColor())
this:SetAlpha(1)
this.parent.icon:Hide()
this.active = 1
this.time = 0
return
elseif this.active == 1 then
-- run animation
this.time = this.time + 30/GetFramerate()*0.1
local fade = 1 -math.exp(-this.time)*math.sin(this.time*math.pi)
this:SetAlpha(fade)
this:SetScale(fade)
if this.time < 1 then return end
end
-- stop animation
this.active = nil
this:Hide()
this.parent.icon:Show()
end,
["elasticzoom"] = function()
if this.active == 0 then
-- init animation
this:SetWidth(this.parent:GetWidth())
this:SetHeight(this.parent:GetHeight())
this:SetScale(this.parent:GetScale())
this.tex:SetTexture(this.parent.icon:GetTexture())
this.tex:SetVertexColor(this.parent.icon:GetVertexColor())
this:SetAlpha(1)
this.parent.icon:Hide()
this.active = 1
this.time = 0
return
elseif this.active == 1 then
-- run animation
this.time = this.time + 30/GetFramerate()*0.03
local fade = 1 -math.exp(-this.time*6)*math.sin(this.time*4*math.pi)
this:SetAlpha(fade)
this:SetScale(fade)
if this.time < 1 then return end
end
-- stop animation
this.active = nil
this:Hide()
this.parent.icon:Show()
end,
["wobblezoom"] = function()
if this.active == 0 then
-- init animation
this:SetWidth(this.parent:GetWidth())
this:SetHeight(this.parent:GetHeight())
this:SetScale(this.parent:GetScale())
this.tex:SetTexture(this.parent.icon:GetTexture())
this.tex:SetVertexColor(this.parent.icon:GetVertexColor())
this:SetAlpha(1)
this.parent.icon:Hide()
this.active = 1
this.time = 0
return
elseif this.active == 1 then
-- run animation
this.time = this.time + 30/GetFramerate()*0.02
local fade = 1 -math.exp(-this.time*6)*math.sin(this.time*10*math.pi)
this:SetAlpha(fade)
this:SetScale(fade)
if this.time < 1 then return end
end
-- stop animation
this.active = nil
this:Hide()
this.parent.icon:Show()
end,
}
local function GetActiveBar()
if CURRENT_ACTIONBAR_PAGE == 1 and GetBonusBarOffset() ~= 0 then
return NUM_ACTIONBAR_PAGES + GetBonusBarOffset()
else
return CURRENT_ACTIONBAR_PAGE
end
end
local function ButtonDrag(self)
local self = self or this
if _G.LOCK_ACTIONBAR == "1" and not (pfUI_config.bars.shiftdrag == "1" and IsShiftKeyDown()) then return end
if self.bar == 12 then
PickupPetAction(self.id)
else
PickupAction(self.id)
end
end
local function ButtonDragStop(self)
local self = self or this
if MacroFrame_SaveMacro then
MacroFrame_SaveMacro()
end
if self.bar == 12 then
PickupPetAction(self.id)
else
PlaceAction(self.id)
end
end
local mouse
local function ButtonAnimate(self)
local self = self or this
mouse = arg1 and not keystate
-- trigger action animation
if ( pfUI_config.bars.keydown == "1" and keystate == "down" ) or (pfUI_config.bars.keydown == "0" and keystate == "up" ) or self.bar == 11 or mouse then
if C.bars.animmode == "keypress" and ( self:GetAlpha() > .1 or C.bars.animalways == "1" ) then
self.animation.active = 0
self.animation:Show()
end
end
-- handle button highlight
if keystate == "down" then
self.highlight:Show()
elseif not MouseIsOver(self) then
self.highlight:Hide()
end
end
local function ButtonClick(self)
local self = self or this
local grid = self.bar == 12 and showgrid_pet or showgrid
local mouse = arg1 and not keystate
local keystate = keystate
local slfcast = C.bars.altself == "1" and IsAltKeyDown() and true or self.slfcast
slfcast = C.bars.rightself == "1" and arg1 and arg1 == "RightButton" and true or slfcast
self.slfcast = nil
if ( pfUI_config.bars.keydown == "1" and keystate == "down" and not drag_active ) or (pfUI_config.bars.keydown == "0" and keystate == "up" or drag_active ) or self.bar == 11 or mouse then
if self.bar == 11 then
CastShapeshiftForm(self.id)
elseif grid == 1 then
if self.bar == 12 then
PickupPetAction(self.id)
else
PickupAction(self.id)
end
elseif self.bar == 12 then
if arg1 == "LeftButton" then
if IsPetAttackActive(self.id) then
PetStopAttack()
else
CastPetAction(self.id)
end
else
TogglePetAutocast(self.id)
end
else
if MacroFrame_SaveMacro then
MacroFrame_SaveMacro()
end
UseAction(self.id, nil, slfcast)
end
end
end
local function ButtonMacroScan(self)
if self.bar > 10 then return end
if not self.scanmacro then return end
if pfUI.bars.skip_macro then return end
-- SuperCleveRoidMacros: for macros it manages, leave spellslot/booktype unset
-- so the button's icon, cooldown, and tooltip flow through the hooked
-- GetActionTexture / GetActionCooldown / GameTooltip:SetAction and follow the
-- active conditional dynamically, instead of being frozen to the first
-- statically-scanned spell.
if CleveRoids and CleveRoids.IsManagedAction and CleveRoids.IsManagedAction(self.id) then
self.spellslot, self.booktype = nil, nil
return
end
local kind, slot = GetActionInfo(self.id)
self.spellslot, self.booktype = nil, nil
if kind == 'macro' then
local name, _, body = GetMacroInfo(slot)
if name and body then
local match
for line in gfind(body, "[^%\n]+") do
_, _, match = string.find(line, '^#showtooltip (.+)')
-- allow the user to disable the scan
if match and strfind(match, "disable") then
return
end
if not match then
-- add support to specify custom tooltips via:
-- /run --showtooltip SPELLNAME
_, _, match = string.find(line, '%-%-showtooltip (.+)')
end
if not match then
_, _, match = string.find(line, '^/cast (.+)')
end
if not match then
_, _, match = string.find(line, '^/pfcast (.+)')
end
if not match then
_, _, match = string.find(line, '^/pfmouse (.+)')
end
if not match then
_, _, match = string.find(line, 'CastSpellByName%(%"(.+)%"%)')
end
if match then
local _, _, spell, rank = string.find(match, '(.+)%((.+)%)')
spell = spell or match
self.spellslot, self.booktype = libspell.GetSpellIndex(spell, rank)
if self.spellslot and self.spellslot > 0 then return end
end
end
end
end
end
local function ButtonEnter(self)
local self = self or this
-- indicate that dragging could get enabled
drag_await = true
GameTooltip:ClearLines()
GameTooltip_SetDefaultAnchor(GameTooltip, self)
if self.bar == 11 then
GameTooltip:SetShapeshift(self.id)
elseif self.bar == 12 then
local name, _, _, token = GetPetActionInfo(self.id)
if token then
GameTooltip:AddLine(_G[name])
GameTooltip:Show()
else
GameTooltip:SetPetAction(self.id)
end
elseif self.spellslot and self.booktype then
GameTooltip:SetSpell(self.spellslot, self.booktype)
else
GameTooltip:SetAction(self.id)
end
self.highlight:Show()
end
local function ButtonLeave(self)
local self = self or this
-- no longer wait for a drag event
drag_await = nil
self.highlight:Hide()
GameTooltip:Hide()
end
local start, duration, enable, castable, autocast, token
local grid, sid, id, bar, active, texture, _
local function ButtonSlotUpdate(self)
if not self then return end
local self = self or this
sid = self.id -- 1 to 120
-- reset shared variables
castable, autocast, token = nil, nil, nil
-- set the own ID for compatibility to some vanilla addons
self:SetID(self.id)
grid = self.bar == 12 and showgrid_pet or showgrid
if self.bar == 11 then
-- stance button
bar = self.bar
id = sid
texture, _, active = GetShapeshiftFormInfo(id)
elseif self.bar == 12 then
-- pet button
bar = self.bar
id = sid
_, _, texture, token, active, castable, autocast = GetPetActionInfo(id)
texture = token and _G[texture] or texture
else
active = IsCurrentAction(sid) or IsAutoRepeatAction(sid)
texture = GetActionTexture(sid)
bar = GetActiveBar()
id = self.bar == 1 and self.slot or sid-((self.bar)-1)*12
end
-- overwrite with spell macro texture where possible
if self.spellslot and self.booktype then
texture = GetSpellTexture(self.spellslot, self.booktype)
end
if not self.showempty and self.backdrop and not texture and grid == 0 then
self.backdrop:Hide()
self.hide = true
else
self.backdrop:Show()
self.hide = nil
end
-- active border
if active then
if C.bars.animmode == "statechange" and not self.active:IsShown() then
self.animation.active = 0
self.animation:Show()
end
self.backdrop:SetBackdropBorderColor(cr,cg,cb,1)
self.active:Show()
else
self.backdrop:SetBackdropBorderColor(er,eg,eb,ea)
self.active:Hide()
end
if self.bar ~= 11 and self.bar ~= 12 then
-- update consumables
if IsConsumableAction(sid) then
self.count:SetText(GetActionCount(sid))
elseif IsReagentAction and IsReagentAction(sid) then
self.count:SetText(GetReagentCount(sid))
else
self.count:SetText(nil)
end
-- equipped item
if IsEquippedAction(sid) and C.bars.showequipped == "1" then
self.equipped:Show()
else
self.equipped:Hide()
end
-- update macro text
if C.bars["bar"..self.bar] and C.bars["bar"..self.bar].showmacro == "1" then
self.macro:SetText(GetActionText(sid))
else
self.macro:SetText("")
end
end
-- icon
if texture ~= self.texture then
self.icon:SetTexture(texture)
self.texture = texture
end
-- handle pet bar quirks
if self.bar == 12 then
-- desaturate disabled petbar
self.icon:SetDesaturated(not GetPetActionsUsable())
-- display autocast
if autocast then
self.autocast:Show()
self.autocast:SetAlpha(self:GetAlpha() * 0.10)
else
self.autocast:Hide()
end
if castable and C.bars.showcastable == "1" then
self.autocastable:Show()
else
self.autocastable:Hide()
end
end
-- keybinds
if not self.hide and self.bar == 1 and self.bar ~= 11 and self.bar ~= 12 then
self.keybind:SetText(GetBindingText(GetBindingKey("ACTIONBUTTON"..id), "KEY_", 1))
elseif not self.hide and buttontypes[self.bar] then
self.keybind:SetText(GetBindingText(GetBindingKey(buttontypes[self.bar]..id), "KEY_", 1))
else
self.keybind:SetText("")
end
end
local sid, usable, oom, _
local function ButtonUsableUpdate(self)
local self = self or this
sid = self.id -- 1 to 120
if self.bar == 11 then
_, _, _, usable = GetShapeshiftFormInfo(sid)
elseif self.bar == 12 then
usable = true
else
usable, oom = IsUsableAction(sid)
end
-- update usable [out-of-range = 1, oom = 2, not-usable = 3, default = 0]
if self.outofrange and C.bars.glowrange == "1" then
if self.vertexstate ~= 1 then
self.icon:SetVertexColor(self.rangeColor[1], self.rangeColor[2], self.rangeColor[3], self.rangeColor[4])
self.vertexstate = 1
end
elseif oom and C.bars.showoom == "1" then
if self.vertexstate ~= 2 then
self.icon:SetVertexColor(self.oomColor[1], self.oomColor[2], self.oomColor[3], self.oomColor[4])
self.vertexstate = 2
end
elseif not usable and C.bars.showna == "1" then
if self.vertexstate ~= 3 then
self.icon:SetVertexColor(self.naColor[1], self.naColor[2], self.naColor[3], self.naColor[4])
self.vertexstate = 3
end
else
if self.vertexstate ~= 0 then
self.icon:SetVertexColor(1, 1, 1, 1)
self.vertexstate = 0
end
end
end
local function ButtonRangeUpdate(self)
local self = self or this
-- update range display
if C.bars.glowrange == "1" and self.bar ~= 11 and self.bar ~= 12 and HasAction(self.id) and ActionHasRange(self.id) and IsActionInRange(self.id) == 0 then
if not self.outofrange then
self.outofrange = true
ButtonUsableUpdate(self)
end
elseif self.outofrange then
self.outofrange = nil
ButtonUsableUpdate(self)
end
end
local start, duration, enable
local function ButtonCooldownUpdate(button)
if not button then return end
if button.bar == 11 then
start, duration, enable = GetShapeshiftFormCooldown(button.id)
elseif button.bar == 12 then
start, duration, enable = GetPetActionCooldown(button.id)
elseif button.spellslot and button.booktype then
start, duration = GetSpellCooldown(button.spellslot, button.booktype)
enable = 1
else
start, duration, enable = GetActionCooldown(button.id)
end
-- Nil-protect: GetActionCooldown can return nil during macro parsing/indexing
if start and duration then
CooldownFrame_SetTimer(button.cd, start, duration, enable or 1)
end
end
local _, active
local function ButtonIsActiveUpdate(button)
if not button then return end
if button.bar == 11 then
_, _, active, _ = GetShapeshiftFormInfo(button.id)
elseif button.bar == 12 then
_, _, _, _, active, _, _ = GetPetActionInfo(button.id)
else
active = IsCurrentAction(button.id) or IsAutoRepeatAction(button.id)
end
-- active border
if active then
if not button.border_active then
button.backdrop:SetBackdropBorderColor(cr,cg,cb,1)
button.border_active = true
end
button.active:Show()
else
if button.border_active then
button.backdrop:SetBackdropBorderColor(er,eg,eb,ea)
button.border_active = nil
end
button.active:Hide()
end
end
local function ButtonFullUpdate(button)
if not button then return end
ButtonMacroScan(button)
ButtonSlotUpdate(button)
ButtonRangeUpdate(button)
ButtonUsableUpdate(button)
ButtonCooldownUpdate(button)
ButtonIsActiveUpdate(button)
end
local function BarsEvent(self)
local self = self or this
-- refresh only specific slots
if event == "ACTIONBAR_SLOT_CHANGED" and arg1 and arg1 ~= 0 then
updatecache[arg1] = true
return
end
-- run special refresh functions on next update
if special_events[event] then
eventcache[event] = true
return
end
-- handle aura events
if aura_events[event] then
for j=1,12 do
if self[11] and self[11][j] then
updatecache[self[11][j].slot] = true
end
end
return
end
-- handle pet events
if pet_events[event] then
for j=1,12 do
if self[12] and self[12][j] then
updatecache[self[12][j].slot] = true
end
end
return
end
-- handle global events
for id in pairs(buttoncache) do
updatecache[id] = true
end
end
local self, button, unlock
-- Main update loop with throttle for performance optimization
local function BarsUpdate(self)
self = self or this
-- Throttle for performance
if (this.tick_main or 0) > GetTime() then return end
this.tick_main = GetTime() + 0.025
-- update buttons whenever a button drag is assumed
AssumeButtonDrag()
if pfUI.unlock then
-- update all bars when entering unlock
if pfUI.unlock:IsShown() ~= unlock then
pfUI.bars:UpdateConfig()
unlock = pfUI.unlock:IsShown()
end
end
-- run cached usable usable actions
if eventcache["ACTIONBAR_UPDATE_USABLE"] then
eventcache["ACTIONBAR_UPDATE_USABLE"] = nil
for id, button in pairs(buttoncache) do
ButtonUsableUpdate(button)
end
end
-- run cached cooldown events
if eventcache["ACTIONBAR_UPDATE_COOLDOWN"] then
eventcache["ACTIONBAR_UPDATE_COOLDOWN"] = nil
for id, button in pairs(buttoncache) do
ButtonCooldownUpdate(button)
end
end
-- run cached action state events
if eventcache["ACTIONBAR_UPDATE_STATE"] then
eventcache["ACTIONBAR_UPDATE_STATE"] = nil
for id, button in pairs(buttoncache) do
ButtonIsActiveUpdate(button)
end
end
for id in pairs(updatecache) do
-- run updates based on slot
pfUI.bars.ButtonFullUpdate(buttoncache[id])
-- run updates on paging actionbar if required
for i=1,12 do
if pfUI.bars[1][i].id == id then
pfUI.bars.ButtonFullUpdate(pfUI.bars[1][i])
end
end
-- clear update cache
updatecache[id] = nil
end
if ( this.tick or .2) > GetTime() then return else this.tick = GetTime() + .2 end
for id, button in pairs(buttoncache) do
if button:IsShown() then ButtonRangeUpdate(button) end
end
end
-- create the main event and update handler for pfUI actionbars
local bars = CreateFrame("Frame", "pfActionBar", UIParent)
for event in pairs(special_events) do bars:RegisterEvent(event) end
for event in pairs(global_events) do bars:RegisterEvent(event) end
for event in pairs(aura_events) do bars:RegisterEvent(event) end
for event in pairs(pet_events) do bars:RegisterEvent(event) end
-- refresh actionbar buttons on event
bars:SetScript("OnEvent", BarsEvent)
-- update actionbar buttons
bars:SetScript("OnUpdate", BarsUpdate)
-- enable bar paging via secure functions
local function EnablePaging(bar)
if not bar.pager then
bar.pager = CreateFrame("Frame")
bar.pager:RegisterEvent("PLAYER_ENTERING_WORLD")
bar.pager:RegisterEvent("UPDATE_BONUS_ACTIONBAR")
bar.pager:RegisterEvent("ACTIONBAR_PAGE_CHANGED")
bar.pager:SetScript("OnEvent", function()
for i=1, 10 do -- reload pageable bars
local pageable = C.bars["bar"..i] and C.bars["bar"..i].pageable == "1" and true or nil
_G.VIEWABLE_ACTION_BAR_PAGES[i] = pageable
end
local active = GetActiveBar()
for i=1,12 do
local id = i + (active-1)*12
bar[i].id = id
updatecache[i] = true
end
end)
end
end
local function SwitchBar(bar)
if _G.CURRENT_ACTIONBAR_PAGE ~= bar then
_G.CURRENT_ACTIONBAR_PAGE = bar
ChangeActionBarPage(bar)
end
end
local cat, stealth
local inCatForm = nil
local prowlActive = nil
-- Cat Form ID per vanilla 1.12 SpellShapeshiftForm.dbc.
local CAT_FORM = 1
local function HasCatForm()
return GetShapeshiftFormID() == CAT_FORM and true or nil
end
local function FullScan()
if class ~= "DRUID" then return nil end
inCatForm = HasCatForm()
prowlActive = inCatForm and IsStealthed() or nil
return prowlActive
end
-- pagemaster / meta page switch
do
local prowl, shift, ctrl, alt, default = 8, 6, 5, 3, 1
-- set temporary pagemaster bindings keybinds
if C.bars.pagemaster == "1" then
local modifier = { "ALT", "SHIFT", "CTRL" }
local buttons = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "+", "=", "´" }
local current = CURRENT_ACTIONBAR_PAGE
bars.pagemaster = bars.pagemaster or CreateFrame("Frame", "pfPageMaster", UIParent)
bars.pagemaster:RegisterEvent("PLAYER_ENTERING_WORLD")
bars.pagemaster:SetScript("OnEvent", function()
for _,mod in pairs(modifier) do
for _,but in pairs(buttons) do
SetBinding(mod.."-"..but)
end
end
end)
end
-- setup page switch frame
local prowling = nil
local pageswitch = CreateFrame("Frame", "pfActionBarPageSwitch", UIParent)
pageswitch:RegisterEvent("PLAYER_ENTERING_WORLD")
pageswitch:RegisterEvent("UPDATE_SHAPESHIFT_FORM")
pageswitch:RegisterEvent("PLAYER_AURAS_CHANGED")
pageswitch:RegisterEvent("PLAYER_LOGOUT")
pageswitch:SetScript("OnEvent", function()
if event == "PLAYER_LOGOUT" then
this:UnregisterAllEvents()
this:SetScript("OnEvent", nil)
this:SetScript("OnUpdate", nil)
return
end
if class ~= "DRUID" then return end
if event == "PLAYER_ENTERING_WORLD" then
prowling = FullScan()
return
end
if event == "UPDATE_SHAPESHIFT_FORM" then
-- Authoritative form-change signal — recompute and short-circuit.
inCatForm = HasCatForm()
if not inCatForm then
prowlActive = nil
prowling = nil
end
return
end
-- PLAYER_AURAS_CHANGED: form changes already went through
-- UPDATE_SHAPESHIFT_FORM above; this handler only watches prowl on/off.
if inCatForm then
if IsStealthed() then
prowlActive = true
prowling = true
else
prowlActive = nil
prowling = nil
end
end
end)
-- Eager prowl detection via Nampower SPELL_GO_SELF — zero-latency vs.
-- PLAYER_AURAS_CHANGED, which can land a frame or two behind the cast.
-- Form changes are no longer hooked here; UPDATE_SHAPESHIFT_FORM covers
-- those without needing a per-spellID watchlist.
local PROWL_IDS = { [5215] = true, [6783] = true, [9913] = true }
pfUI.libdebuff_spell_go_hooks = pfUI.libdebuff_spell_go_hooks or {}
pfUI.libdebuff_spell_go_hooks["actionbar_prowl"] = function(spellId)
if class ~= "DRUID" then return end
if PROWL_IDS[spellId] then
inCatForm = true
prowlActive = true
prowling = true
end
end
pageswitch:SetScript("OnUpdate", function()
-- switch actionbar page depending on meta key that is pressed
if C.bars.pagemastershift == "1" and IsShiftKeyDown() then
SwitchBar(shift)
return
elseif C.bars.pagemasterctrl == "1" and IsControlKeyDown() then
SwitchBar(ctrl)
return
elseif C.bars.pagemasteralt == "1" and IsAltKeyDown() then
SwitchBar(alt)
return
elseif C.bars.pagemasteralt == "1" or C.bars.pagemasterctrl == "1" or C.bars.pagemastershift == "1" then
SwitchBar(default)
end
-- switch actionbar page if druid stealth is detected
if C.bars.druidstealth == "1" then
if prowling and _G.CURRENT_ACTIONBAR_PAGE == 1 then
SwitchBar(prowl)
elseif not prowling and _G.CURRENT_ACTIONBAR_PAGE == 8 then
SwitchBar(default)
end
end
end)
end
local function CreateActionButton(parent, bar, button)
-- load config
local size = C.bars["bar"..bar].icon_size
local font = pfUI.media[C.bars.font]
local font_offset = tonumber(C.bars.font_offset)
local macro_size = tonumber(C.bars.macro_size)
local macro_color = { strsplit(",", C.bars.macro_color) }
local count_size = tonumber(C.bars.count_size)
local count_color = { strsplit(",", C.bars.count_color) }
local bind_size = tonumber(C.bars.bind_size)
local bind_color = { strsplit(",", C.bars.bind_color) }
local cd_size = tonumber(C.bars.cd_size)
local showempty = C.bars["bar"..bar].showempty
local showmacro = C.bars["bar"..bar].showmacro
local showkybind = C.bars["bar"..bar].showkeybind
local showcount = C.bars["bar"..bar].showcount
-- sanitize font sizes
if macro_size == 0 then macro_size = 1 end
if count_size == 0 then macro_size = 1 end
if bind_size == 0 then macro_size = 1 end
if cd_size == 0 then cd_size = nil end
local button_name = "pfActionBar" .. barnames[bar] .. "Button" .. button
local id = (bar-1)*12+button
local exists = _G[button_name] and true or nil
local f = _G[button_name] or CreateFrame("Button", button_name, parent, ACTIONBAR_SECURE_TEMPLATE_BUTTON)
-- no button available, create a new one
if not exists then
-- prepare the button for vanilla
if not f.HookScript then
f.HookScript = HookScript
f:SetScript("OnClick", ButtonClick)
end
if bar ~= 11 then
f:RegisterForDrag("LeftButton", "RightButton")
f:SetScript("OnDragStart", ButtonDrag)
f:SetScript("OnReceiveDrag", ButtonDragStop)
end
-- add mouseovers
f:SetScript("OnEnter", ButtonEnter)
f:SetScript("OnLeave", ButtonLeave)
-- add click animation handler
f:HookScript("OnClick", ButtonAnimate)
f.id = id
-- set a static slot
f.slot = id
-- cooldown
f.cd = CreateFrame(COOLDOWN_FRAME_TYPE, f:GetName() .. "Cooldown", f, "CooldownFrameTemplate")
f.cd.pfCooldownStyleAnimation = 1
f.cd.pfCooldownType = "NOGCD"
f.cd.pfCooldownSize = cd_size
-- icon
f.icon = f:CreateTexture(button_name .. "Icon", "BACKGROUND")
f.icon:SetTexCoord(.08, .92, .08, .92)
f.icon:SetAllPoints()
-- animation
f.animation = CreateFrame("Frame", button_name .. "Animation", f)
f.animation.parent = f
f.animation:SetPoint("CENTER", 0, 0)
f.animation:Hide()
f.animation.tex = f.animation:CreateTexture(button_name .. "AnimationTexture", "BACKGROUND")
f.animation.tex:SetTexCoord(.08, .92, .08, .92)
f.animation.tex:SetAllPoints()
if bar ~= 11 and bar ~= 12 then
-- equipped item
f.equipped = f:CreateTexture(nil, "BORDER")
f.equipped:SetAllPoints()
f.equipped:Hide()
elseif bar == 12 then
f.autocast = CreateFrame("Model", nil, f)
f.autocast:SetAllPoints()
f.autocast:SetModel("Interface\\Buttons\\UI-AutoCastButton.mdx")
f.autocast:SetSequence(0)
f.autocast:SetSequenceTime(0, 0)
f.autocast:Hide()
f.autocastable = f:CreateTexture(nil, "BORDER")
f.autocastable:SetAllPoints()
f.autocastable:SetTexture("Interface\\Buttons\\UI-AutoCastableOverlay")
f.autocastable:SetTexCoord(.25, .75, .25, .75)
end
-- macro
f.macro = f:CreateFontString(button_name .. "Name", "LOW", "GameFontNormal")
-- keybind
f.keybind = f:CreateFontString(nil, "LOW", "GameFontNormal")
-- itemcount
f.count = f:CreateFontString(button_name .. "Count", "LOW", "GameFontNormal")
-- highlight
f.highlight = CreateFrame("Frame", nil, f)
f.highlight:SetBackdrop(backdrop_highlight)
f.highlight:SetBackdropBorderColor(1,1,1,.8)
f.highlight:SetAllPoints()
f.highlight:Hide()
-- active
f.active = CreateFrame("Frame", nil, f)
f.active:SetBackdrop(backdrop_highlight)
f.active:SetBackdropBorderColor(1,1,.5,1)
f.active:SetAllPoints()
f.active:Hide()
-- add to buttoncache
buttoncache[id] = f
end
-- set keydown option
if C.bars.keydown == "1" then
f:RegisterForClicks("LeftButtonDown", "RightButtonDown")
else
f:RegisterForClicks("LeftButtonUp", "RightButtonUp")
end
-- set animation
f.animation:SetScript("OnUpdate", button_animations[C.bars.animation])
-- pet autocast
if bar == 12 then
f.autocast:SetScale(C.bars["bar"..bar].icon_size / 25)
f.autocast:SetAlpha(.10)
end
-- macro options
if showmacro == "1" then f.macro:Show() else f.macro:Hide() end
SetAllPointsOffset(f.macro, f, font_offset, -font_offset)
f.macro:SetFont(font, macro_size, "OUTLINE")
f.macro:SetTextColor(unpack(macro_color))
f.macro:SetJustifyH("LEFT")
f.macro:SetJustifyV("BOTTOM")
-- keybind options
if showkybind == "1" then f.keybind:Show() else f.keybind:Hide() end
SetAllPointsOffset(f.keybind, f, font_offset, -font_offset)
f.keybind:SetFont(font, bind_size, "OUTLINE")
f.keybind:SetTextColor(unpack(bind_color))
f.keybind:SetJustifyH("RIGHT")
f.keybind:SetJustifyV("TOP")
f.keybind:SetNonSpaceWrap(false)
-- item count options
if showcount == "1" then f.count:Show() else f.count:Hide() end
SetAllPointsOffset(f.count, f, font_offset, -font_offset)
f.count:SetFont(font, count_size, "OUTLINE")
f.count:SetTextColor(unpack(count_color))
f.count:SetJustifyH("RIGHT")
f.count:SetJustifyV("BOTTOM")
-- macro spell scan (disabled when macro addons are loaded)
if C.bars.macroscan == "0" or pfUI:MacroAddonsLoaded() then
f.scanmacro, f.spellslot, f.booktype = nil, nil, nil
else
f.scanmacro = true
end
-- range glow color
f.rangeColor = { strsplit(",", C.bars.rangecolor) }
-- out of mana color
f.oomColor = { strsplit(",", C.bars.oomcolor) }
-- not usable color
f.naColor = { strsplit(",", C.bars.nacolor) }
-- equipped color
if f.equipped then
f.equipped:SetTexture(strsplit(",", C.bars.eqcolor))
end
-- general appearance
f.showempty = showempty == "1" and true or nil
f:SetHeight(size)
f:SetWidth(size)
CreateBackdrop(f, border)
return f
end
local function CreateActionBar(i)
-- load config
local buttonbasename = "pfActionBar" .. barnames[i] .. "Button"
local enable = C.bars["bar"..i].enable
local size = C.bars["bar"..i].icon_size
local spacing = C.bars["bar"..i].spacing
local background = C.bars["bar"..i].background
local formfactor = C.bars["bar"..i].formfactor
local uneven = C.bars["bar"..i].uneven or "Down"
local fillmode = C.bars["bar"..i].fillmode or "auto"
-- validate uneven against bar shape and reset if incompatible
local _, _, fcols2, frows2 = string.find(tostring(formfactor or ""), "(%d+)%s*x%s*(%d+)")
fcols2, frows2 = tonumber(fcols2), tonumber(frows2)
local f_is_square = fcols2 == 3 and frows2 == 3
local f_is_vertical = fcols2 and fcols2 <= 3 and not f_is_square
local f_is_horizontal = frows2 and frows2 <= 3 and not f_is_square
if f_is_vertical then
if uneven ~= "Up" and uneven ~= "Down" then uneven = "Down" end
elseif f_is_horizontal then
if uneven ~= "Left" and uneven ~= "Right" then uneven = "Down" end
end
C.bars["bar"..i].uneven = uneven
local autohide = C.bars["bar"..i].autohide
local hide_time = C.bars["bar"..i].hide_time
local hide_combat = C.bars["bar"..i].hide_combat == "1" and true or nil
local buttons = tonumber(C.bars["bar"..i].buttons) or 12
if i == 11 and bars[i] then -- shapeshift buttons
buttons = GetNumShapeshiftForms()
elseif i == 12 and bars[i] then -- pet buttons
buttons = NUM_PET_ACTION_SLOTS
elseif i == 12 or i == 11 then
-- Fallback to 10 buttons on shapeshift and petbar during initialization.
-- This lets us load bars that aren't yet available for your class or level.
buttons = 10
end
-- don't process empty bars
if buttons == 0 then
bars[i]:Hide()
return
end
-- the stored layout is invalid, temporary fallback
local _, _, fcols, frows = string.find(tostring(formfactor or ""), "(%d+)%s*x%s*(%d+)")
if not ((fcols and frows) or pfGridmath[buttons][BarLayoutFormfactor(formfactor)]) then
formfactor = BarLayoutOptions(buttons)[1]
end
local font = pfUI.font_unit
local font_size = C.global.font_unit_size
local realsize = size+border*2
-- create frame
local init = not bars[i]
bars[i] = bars[i] or CreateFrame("Frame", "pfActionBar" .. barnames[i], UIParent, ACTIONBAR_SECURE_TEMPLATE_BAR)
bars[i]:SetID(i)
-- autohide
if autohide == "1" then
EnableAutohide(bars[i], tonumber(hide_time), hide_combat)
else
DisableAutohide(bars[i])
end
-- apply visible settings
if enable == "1" then
-- handle pet bar
if i == 12 then
-- only show when pet actions exists
if PetHasActionBar() or pfUI.unlock and pfUI.unlock:IsShown() then
bars[i]:Show()
else
bars[i]:Hide()
end
-- show/hide petbar on petbar updates
if init then
bars[i]:RegisterEvent("PET_BAR_UPDATE")
bars[i]:SetScript("OnEvent", function()
-- hide obsolete buttons
for i=1, NUM_PET_ACTION_SLOTS do
if not PetHasActionBar() and bars[12][i] then
bars[12][i]:Hide()
end
end
-- refresh layout
CreateActionBar(12)
end)
end
-- handle shapeshift bar
elseif i == 11 then
-- only show when shapeshifts exist
if GetNumShapeshiftForms() > 0 then
bars[i]:Show()
else
bars[i]:Hide()
end
-- update shapeshift bar when amount of spells changes
if init then
bars[i]:RegisterEvent("PLAYER_ENTERING_WORLD")
bars[i]:RegisterEvent("UPDATE_BONUS_ACTIONBAR")
bars[i]:RegisterEvent("UPDATE_SHAPESHIFT_FORMS")
bars[i]:SetScript("OnEvent", function()
local count = GetNumShapeshiftForms()
if count ~= this.lastCount then
this.lastCount = count
-- hide obsolete buttons
for i=1, NUM_SHAPESHIFT_SLOTS do
if i > GetNumShapeshiftForms() and bars[11][i] then
bars[11][i]:Hide()
end
end
-- create new buttons and refresh layout
CreateActionBar(11)
end
end)
end
-- regular bars
else
bars[i]:Show()
end
elseif enable == "0" then
bars[i]:UnregisterAllEvents()
bars[i]:Hide()
end
-- create action buttons
local maxbuttons = (i == 11 or i == 12) and 10 or 12
for j=1,maxbuttons do
bars[i][j] = CreateActionButton(bars[i], i, j)
bars[i][j].bar = i
BarButtonAnchor(bars[i][j], buttonbasename, j, buttons, formfactor, size, border, spacing, uneven, fillmode)
bars[i][j]:ClearAllPoints()
bars[i][j]:SetPoint(unpack(bars[i][j]._anchor))
bars[i][j]:Show()
if i > 10 then
bars[i][j].id = j
end
-- refresh button
updatecache[bars[i][j].slot] = true
end
for j=buttons+1,12 do
if bars[i][j] then
bars[i][j]:Hide()
end
end
-- enable paging for the first actionbar
if i == 1 then
EnablePaging(bars[i])
end
-- adjust actionbar size
BarLayoutSize(bars[i], buttons, formfactor, size, border, spacing, uneven, fillmode)
bars[i]:SetWidth(bars[i]._size[1])
bars[i]:SetHeight(bars[i]._size[2])
bars[i]:ClearAllPoints()
if i == 1 then -- main
bars[i]:SetPoint("BOTTOM", 0, 2*border)
elseif i == 3 then -- left
bars[i]:SetPoint("BOTTOMLEFT", bars[1], "BOTTOMRIGHT", 3*border, 0)
elseif i == 4 then -- vertical
bars[i]:SetPoint("RIGHT", -2*border, 0)
elseif i == 5 then -- right
bars[i]:SetPoint("BOTTOMRIGHT", bars[1], "BOTTOMLEFT", -3*border, 0)
elseif i == 6 then -- top
bars[i]:SetPoint("BOTTOM", bars[1], "TOP", 0, -spacing)
elseif i == 11 then -- stances
bars[i]:SetPoint("BOTTOM", bars[6], "TOP", 0, 3*border)
elseif i == 12 then -- pet
bars[i]:SetPoint("BOTTOM", bars[6], "TOP", 0, 3*border)
-- make stance bar dodge by default
bars[i]:SetScript("OnShow", function()
if bars[11] and bars[11]:IsShown() then
bars[11]:ClearAllPoints()
bars[11]:SetPoint("BOTTOM", bars[12], "TOP", 0, 3*border)
UpdateMovable(bars[11], true)
end
end)
-- restore old stance bar position
bars[i]:SetScript("OnHide", function()
if bars[11] and bars[11]:IsShown() then
bars[11]:ClearAllPoints()
bars[11]:SetPoint("BOTTOM", bars[6], "TOP", 0, 3*border)
UpdateMovable(bars[11], true)
end
end)
else -- others
bars[i]:SetPoint("TOP", 0, -i*50)
end
UpdateMovable(bars[i])
-- apply backdrop settings
if background == "1" then
CreateBackdrop(bars[i], border)
CreateBackdropShadow(bars[i])
bars[i].backdrop:Show()
elseif bars[i].backdrop then
bars[i].backdrop:Hide()
end
-- share backdrop of main and top actionbar
if bars[6] and bars[1] then
bars[6].OnMove = bars[6].OnMove or function()
bars[1].mergedBackdrop = bars[1].mergedBackdrop or CreateFrame("Frame", nil, UIParent)
bars[1].mergedBackdrop:SetPoint("TOPLEFT", bars[6], "TOPLEFT", 0, 0)
bars[1].mergedBackdrop:SetPoint("BOTTOMRIGHT", bars[1], "BOTTOMRIGHT", 0, 0)
CreateBackdrop(bars[1].mergedBackdrop)
local _, anchor, _ = bars[6]:GetPoint()
if anchor == bars[1] and C.bars.bar1.enable == "1"
and C.bars.bar1.enable == "1" and C.bars.bar6.enable == "1"
and C.bars.bar1.background == "1" and C.bars.bar6.background == "1"
and C.bars.bar1.autohide == "0" and C.bars.bar6.autohide == "0"
and C.bars.bar1.icon_size == C.bars.bar6.icon_size
and C.bars.bar1.spacing == C.bars.bar6.spacing
and C.bars.bar1.formfactor == C.bars.bar6.formfactor
and C.bars.bar1.buttons == C.bars.bar6.buttons
then
bars[1].mergedBackdrop:Show()
if C.bars.bar1.background == "1" and bars[1].backdrop then
bars[1].backdrop:Hide()
end
if C.bars.bar6.background == "1" and bars[6].backdrop then
bars[6].backdrop:Hide()
end
else
bars[1].mergedBackdrop:Hide()
if C.bars.bar1.background == "1" and bars[1].backdrop then
bars[1].backdrop:Show()
end
if C.bars.bar6.background == "1" and bars[6].backdrop then
bars[6].backdrop:Show()
end
end
end
bars[6].OnMove()
end
end
-- create actionbars
pfUI.bars = bars
pfUI.bars.update = updatecache
pfUI.bars.buttons = buttoncache
pfUI.bars.ButtonFullUpdate = ButtonFullUpdate
pfUI.bars.ButtonEnter = ButtonEnter
pfUI.bars.ButtonLeave = ButtonLeave
pfUI.bars.UpdateGrid = function(self, state, typ)
if not typ then
showgrid = state
for id in pairs(buttoncache) do
updatecache[id] = true
end
elseif typ == "PET" then
showgrid_pet = state
for slot=133,142 do
updatecache[slot] = true
end
end
end
pfUI.bars.UpdateConfig = function(self)
for i=1,12 do
CreateActionBar(i)
end
end
pfUI.bars:UpdateConfig()
-- Localize custom keybinds for additional actionbars (see Bindings.xml)
local names = {
["PAGING"] = T["Paging Actionbar"],
["STANCEONE"] = T["Stance Bar 1"],
["STANCETWO"] = T["Stance Bar 2"],
["STANCETHREE"] = T["Stance Bar 3"],
["STANCEFOUR"] = T["Stance Bar 4"],
}
for name, loc in pairs(names) do
_G["BINDING_HEADER_PFBAR"..name] = T["Action Bar"] .. " " .. loc
for i=1,12 do
_G["BINDING_NAME_PF" .. name .. i] = loc .. " " .. T["Button"] .. " " .. i
end
end
-- Map Keybinds to button clicks
function _G.pfActionButton(slot, slfcast, opt)
if ChatFrameEditBox:IsShown() then return end
local bar, button = 1, slot
-- determine the proper bar and button
if opt and blizzbarmapping[opt] then
bar = blizzbarmapping[opt]
elseif slot > 12 then
bar, button = ceil(slot/12), mod(slot, 12)
button = button == 0 and 12 or button
end
local frame = bars[bar][button]
if frame then
frame.slfcast = slfcast
frame:Click()
end
end
-- Set keybinds to all actionbuttons
-- In order to be able to reuse already defined keybinds, we need to remap
-- existing button functions to pfUI. We need to get rid of the blizzard calls
-- to avoid having them call texture changes and errors due to missing buttons
_G.ActionButtonDown = pfActionButton
_G.ActionButtonUp = pfActionButton
_G.BonusActionButtonDown = function(slot) pfActionButton(slot, nil, "BonusActionBar") end
_G.BonusActionButtonUp = function(slot) pfActionButton(slot, nil, "BonusActionBar") end
_G.MultiActionButtonDown = function(bar, slot, slf) pfActionButton(slot, slf, bar) end
_G.MultiActionButtonUp = function(bar, slot, slf) pfActionButton(slot, slf, bar) end
_G.ShapeshiftBar_ChangeForm = function(slot) pfActionButton(slot, nil, "ShapeShiftBar") end
-- handle drag-drop grid
local grid = CreateFrame("Frame")
grid:RegisterEvent("ACTIONBAR_SHOWGRID")
grid:RegisterEvent("ACTIONBAR_HIDEGRID")
grid:RegisterEvent("PET_BAR_SHOWGRID")
grid:RegisterEvent("PET_BAR_HIDEGRID")
grid:SetScript("OnEvent", function()
if event == "ACTIONBAR_SHOWGRID" then
pfUI.bars:UpdateGrid(1)
elseif event == "ACTIONBAR_HIDEGRID" then
pfUI.bars:UpdateGrid(0)
elseif event == "PET_BAR_SHOWGRID" then
pfUI.bars:UpdateGrid(1, "PET")
elseif event == "PET_BAR_HIDEGRID" then
pfUI.bars:UpdateGrid(0, "PET")
end
end)
-- reagent counter
if C.bars.reagents == "1" then
local reagent_slots = { } -- action slot -> first reagent itemID
local reagent_counts = { } -- itemID -> count in player's inventory
-- For a given action slot, resolve the spell's first reagent itemID
-- via GetActionInfo + C_Spell.GetSpellReagents. Macro actions and
-- bag-item actions are skipped (their reagent resolution would need a
-- macro-body parse / item-effect lookup that we don't bother with).
local UpdateSlot = function(slot)
local newID = nil
if HasAction(slot) then
local kind, spellID = GetActionInfo(slot)
if kind == "spell" and spellID then
local reagents = C_Spell.GetSpellReagents(spellID)
if reagents and reagents[1] then
newID = reagents[1].itemID
end
end
end
if reagent_slots[slot] ~= newID then
reagent_slots[slot] = newID
if newID then
reagent_counts[newID] = reagent_counts[newID] or 0
end
updatecache[slot] = true
end
end
local reagentcounter = CreateFrame("Frame", "pfReagentCounter", UIParent)
reagentcounter:RegisterEvent("PLAYER_ENTERING_WORLD")
reagentcounter:RegisterEvent("ACTIONBAR_SLOT_CHANGED")
reagentcounter:RegisterEvent("BAG_UPDATE_DELAYED")
reagentcounter:SetScript("OnEvent", function()
if event == "BAG_UPDATE_DELAYED" then
this.event = true
else
this.scan = 1
end
end)
-- Reagent counter update with throttle for performance optimization
reagentcounter:SetScript("OnUpdate", function()
-- Throttle entire function to 10 FPS for smooth scanning
if (this.tick_update or 0) > GetTime() then return end
this.tick_update = GetTime() + 0.1
-- scan one action slot per update
if this.scan and this.scan <= 120 then
UpdateSlot(this.scan)
this.scan = this.scan + 1
end
-- trigger reagent count updates after action scans
if this.scan and this.scan >= 120 then
this.event = true
this.scan = nil
end
-- queue events to fire only once per second
if not this.event then return end
if ( this.tick or 1) > GetTime() then return else this.tick = GetTime() + 1 end
-- scan for all reagent item counts
for itemID in pairs(reagent_counts) do
reagent_counts[itemID] = C_Item.GetItemCount(itemID)
end
-- update all actionbar buttons
for slot in pairs(reagent_slots) do
updatecache[slot] = true
end
-- remove event trigger
this.event = nil
end)
function IsReagentAction(slot)
return reagent_slots[slot] and true or nil
end
function GetReagentCount(slot)
return reagent_counts[reagent_slots[slot]]
end
end
end)