Files
pfUI/modules/actionbar.lua
T
Brues 627ab8b437 Use ClassicAPI's native Cooldown frame for cooldown sweeps
ClassicAPI 1.17.1 draws the Cooldown sweep natively, as 4.3.4's
CGCooldown, so pfUI's cooldowns no longer need the indicator Model.

- Action buttons, bank slots, totems, unit frame auras and icons, and
  nameplate debuffs are Cooldown frames with CooldownFrameTemplate.
- The plain-Frame fallbacks for disabled animations are gone; the hook
  already hides the sweep with alpha 0.
- The 36px scale-and-inset realign only runs on stock indicator Models.
2026-10-22 22:47:18 -05:00

1570 lines
48 KiB
Lua

pfUI:RegisterModule("actionbar", function ()
local class = UnitClassBase("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:SetSize(this.parent:GetSize())
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:SetSize(this.parent:GetSize())
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:SetSize(this.parent:GetSize())
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:SetSize(this.parent:GetSize())
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 MouseIsOverHitRect(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 ( C.bars.keydown == "1" and keystate == "down" and not drag_active ) or (C.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 ButtonEnter(self)
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.spellID then
GameTooltip:SetSpellByID(self.spellID)
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)
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
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)
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:GetRGBA())
self.vertexstate = 1
end
elseif oom and C.bars.showoom == "1" then
if self.vertexstate ~= 2 then
self.icon:SetVertexColor(self.oomColor:GetRGBA())
self.vertexstate = 2
end
elseif not usable and C.bars.showna == "1" then
if self.vertexstate ~= 3 then
self.icon:SetVertexColor(self.naColor:GetRGBA())
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)
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
ButtonSlotUpdate(button)
ButtonRangeUpdate(button)
ButtonUsableUpdate(button)
ButtonCooldownUpdate(button)
ButtonIsActiveUpdate(button)
end
local function BarsEvent(self)
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)
-- The only unit events in the tables above; both concern the player alone.
-- A registration keeps its kind, so these have to go in unit-filtered from
-- the start -- RegisterUnitEvent over a plain registration stays plain.
local event_units = {
["UNIT_INVENTORY_CHANGED"] = "player",
["UNIT_PET"] = "player",
}
local function RegisterBarEvent(event)
local unit = event_units[event]
if unit then
bars:RegisterUnitEvent(event, unit)
else
bars:RegisterEvent(event)
end
end
for event in pairs(special_events) do RegisterBarEvent(event) end
for event in pairs(global_events) do RegisterBarEvent(event) end
for event in pairs(aura_events) do RegisterBarEvent(event) end
for event in pairs(pet_events) do RegisterBarEvent(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 inCatForm = nil
local prowlActive = nil
-- Form IDs per vanilla 1.12 SpellShapeshiftForm.dbc.
local CAT_FORM = 1
local SHADOWFORM = 28
local function HasCatForm()
return GetShapeshiftFormID() == CAT_FORM and true or nil
end
local function InShadowform()
return GetShapeshiftFormID() == SHADOWFORM 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 formpage, shift, ctrl, alt, default = 8, 6, 5, 3, 1
-- while a page modifier is held, MOD-key must run the main bar binding on
-- that key so the switched page fires. Overrides do this without touching
-- the saved binding set.
local pagemods = {}
if C.bars.pagemasteralt == "1" then table.insert(pagemods, "ALT") end
if C.bars.pagemasterctrl == "1" then table.insert(pagemods, "CTRL") end
if C.bars.pagemastershift == "1" then table.insert(pagemods, "SHIFT") end
if table.getn(pagemods) > 0 then
local pagemaster = CreateFrame("Frame", "pfPageMaster", UIParent)
pagemaster:RegisterEvent("PLAYER_ENTERING_WORLD")
pagemaster:RegisterEvent("UPDATE_BINDINGS")
pagemaster:SetScript("OnEvent", function()
ClearOverrideBindings(this)
for i = 1, 12 do
for _, key in ipairs({ GetBindingKey("ACTIONBUTTON"..i) }) do
-- skip keys that already carry a modifier, e.g. "SHIFT-Q"
if not key:find("^%u+%-.") then
for _, mod in ipairs(pagemods) do
SetOverrideBinding(this, false, mod.."-"..key, "ACTIONBUTTON"..i)
end
end
end
end
end)
end
-- setup page switch frame. `formpaging` is the shared "auto-page is
-- active" flag; druid prowl and priest shadowform each drive it, and
-- no character is ever both, so one flag covers both features.
local formpaging = 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 == "PRIEST" then
if event == "UPDATE_SHAPESHIFT_FORM" or event == "PLAYER_ENTERING_WORLD" then
formpaging = InShadowform()
end
return
end
if class ~= "DRUID" then return end
if event == "PLAYER_ENTERING_WORLD" then
formpaging = 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
formpaging = 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
formpaging = true
else
prowlActive = nil
formpaging = 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
formpaging = 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 while druid stealth / priest shadowform is active
if (class == "DRUID" and C.bars.druidstealth == "1")
or (class == "PRIEST" and C.bars.priestshadow == "1") then
if formpaging and _G.CURRENT_ACTIONBAR_PAGE == 1 then
SwitchBar(formpage)
elseif not formpaging 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 = { GetStringColor(C.bars.macro_color) }
local count_size = tonumber(C.bars.count_size)
local count_color = { GetStringColor(C.bars.count_color) }
local bind_size = tonumber(C.bars.bind_size)
local bind_color = { GetStringColor(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)
-- no button available, create a new one
if not exists then
-- prepare the button for vanilla
if not f.HookScript then f.HookScript = HookScript end
f:SetScript("OnClick", ButtonClick)
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", 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")
f.scanmacro, f.spellslot, f.booktype = nil, nil, nil
-- range glow color
f.rangeColor = GetStringColorObject(C.bars.rangecolor)
-- out of mana color
f.oomColor = GetStringColorObject(C.bars.oomcolor)
-- not usable color
f.naColor = GetStringColorObject(C.bars.nacolor)
-- equipped color
if f.equipped then
f.equipped:SetTexture(GetStringColor(C.bars.eqcolor))
end
-- general appearance
f.showempty = showempty == "1" and true or nil
f:SetSize(size, 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)
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]:SetSize(bars[i]._size[1], 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 pfUI.unlock and pfUI.unlock:IsShown() then return end
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 pfUI.unlock and pfUI.unlock:IsShown() then return end
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 function UpdateSlot(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 C_Item.GetItemCount(newID)
end
updatecache[slot] = true
end
end
-- Recount every tracked reagent and flag its buttons for a refresh.
local RecountReagents = function()
for itemID in pairs(reagent_counts) do
reagent_counts[itemID] = C_Item.GetItemCount(itemID)
end
for slot in pairs(reagent_slots) do
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 == "ACTIONBAR_SLOT_CHANGED" then
-- arg1 is the changed slot; 0 (or nil) means "all slots"
if arg1 and arg1 > 0 then
UpdateSlot(arg1)
else
for slot = 1, 120 do UpdateSlot(slot) end
end
elseif event == "BAG_UPDATE_DELAYED" then
-- inventory changed: refresh the counts we already track
RecountReagents()
else
-- PLAYER_ENTERING_WORLD: seed the full reagent map (UpdateSlot seeds
-- each new reagent's count; a BAG_UPDATE_DELAYED follows during login)
for slot = 1, 120 do UpdateSlot(slot) end
end
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)