diff --git a/modules/actionbar.lua b/modules/actionbar.lua index eabebc55..df1eae1a 100644 --- a/modules/actionbar.lua +++ b/modules/actionbar.lua @@ -912,24 +912,40 @@ pfUI:RegisterModule("actionbar", "vanilla:tbc", function () end local cat, stealth - local function IsCatStealth() + local inCatForm = nil -- cached from buff scan + local prowlActive = nil -- tracks if prowl is active + + -- Full scan for cat form and prowl (only on login/reload) + local function FullScan() if class ~= "DRUID" then return nil end - cat, stealth = nil, nil - + + local foundCat, foundStealth = nil, nil + for i = 0, 31 do local texture = GetPlayerBuffTexture(i) if not texture then break end - -- catform icon detected if strfind(texture, "Ability_Druid_CatForm") then - if stealth then return true end - cat = true + foundCat = true end - -- stealth icon detected if strfind(texture, "Ability_Ambush") then - if cat then return true end - stealth = true + foundStealth = true + end + end + + inCatForm = foundCat + prowlActive = foundCat and foundStealth + return prowlActive + end + + -- Quick scan only for prowl (when we know we're in cat form) + local function HasProwlBuff() + for i = 0, 31 do + local texture = GetPlayerBuffTexture(i) + if not texture then break end + if strfind(texture, "Ability_Ambush") then + return true end end return nil @@ -956,7 +972,59 @@ pfUI:RegisterModule("actionbar", "vanilla:tbc", function () end -- setup page switch frame + local prowling = nil local pageswitch = CreateFrame("Frame", "pfActionBarPageSwitch", UIParent) + pageswitch:RegisterEvent("PLAYER_AURAS_CHANGED") + pageswitch:RegisterEvent("PLAYER_ENTERING_WORLD") + pageswitch:RegisterEvent("UNIT_CASTEVENT") + pageswitch:SetScript("OnEvent", function() + if class ~= "DRUID" then return end + + -- UNIT_CASTEVENT: detect Cat Form and Prowl cast instantly + -- Cat Form: 768 | Prowl: 5215 (R1), 6783 (R2), 9913 (R3) + if event == "UNIT_CASTEVENT" then + local guid, target, cEvent, spellId = arg1, arg2, arg3, arg4 + local _, playerGuid = UnitExists("player") + if guid == playerGuid and cEvent == "CAST" then + if spellId == 5215 or spellId == 6783 or spellId == 9913 then + inCatForm = true + prowlActive = true + prowling = true + elseif spellId == 768 then + inCatForm = true + end + end + return + end + + -- PLAYER_AURAS_CHANGED: smart scanning + if event == "PLAYER_AURAS_CHANGED" then + if prowlActive then + -- We were prowling, check if still prowling + if HasProwlBuff() then + prowling = true + else + -- Prowl ended + prowlActive = nil + prowling = nil + -- Also check if still in cat form + inCatForm = nil + for i = 0, 31 do + local texture = GetPlayerBuffTexture(i) + if not texture then break end + if strfind(texture, "Ability_Druid_CatForm") then + inCatForm = true + break + end + end + end + elseif not inCatForm then + -- Not in cat form, do a full scan (might have just shifted) + prowling = FullScan() + end + -- If inCatForm but not prowlActive, no scan needed (wait for UNIT_CASTEVENT) + end + end) pageswitch:SetScript("OnUpdate", function() -- switch actionbar page depending on meta key that is pressed if C.bars.pagemastershift == "1" and IsShiftKeyDown() then @@ -974,10 +1042,9 @@ pfUI:RegisterModule("actionbar", "vanilla:tbc", function () -- switch actionbar page if druid stealth is detected if C.bars.druidstealth == "1" then - local stealth = IsCatStealth() - if stealth and _G.CURRENT_ACTIONBAR_PAGE == 1 then + if prowling and _G.CURRENT_ACTIONBAR_PAGE == 1 then SwitchBar(prowl) - elseif not stealth and _G.CURRENT_ACTIONBAR_PAGE == 8 then + elseif not prowling and _G.CURRENT_ACTIONBAR_PAGE == 8 then SwitchBar(default) end end diff --git a/modules/actionbar_nodebug.lua b/modules/actionbar_nodebug.lua new file mode 100644 index 00000000..ca9a91ba --- /dev/null +++ b/modules/actionbar_nodebug.lua @@ -0,0 +1,1771 @@ +pfUI:RegisterModule("actionbar", "vanilla:tbc", function () + local _, class = UnitClass("player") + local color = RAID_CLASS_COLORS[class] + local cr, cg, cb = color.r , color.g, color.b + 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 + + local petvisibility = "[pet] show; hide" + + -- 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 during combat + if InCombatLockdown and InCombatLockdown() then return end + + -- 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) + -- skip during combat + if InCombatLockdown and InCombatLockdown() then return end + + 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) + -- skip during combat + if InCombatLockdown and InCombatLockdown() then return end + + 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 + + local macro = GetActionText(self.id) + self.spellslot = nil + self.booktype = nil + if macro then + local slot = GetMacroIndexByName(macro) + local name, _, body = GetMacroInfo(slot) + + if name and body then + local match + + for line in gfind(body, "[^%\n]+") do + _, _, match = string.find(line, '^#showtooltip (.+)') + + -- skip any further manual macro scanning on + -- gameclients with native macro spell detection + if pfUI.client > 11200 and match then + self.spellslot = nil + self.booktype = nil + return + end + + -- 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 + if pfUI.client <= 11200 then self:SetID(self.id) end + + 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 + + CooldownFrame_SetTimer(button.cd, start, duration, enable) + 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 + local function BarsUpdate(self) + self = self or this + + -- 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 ButtonSwitch(self, att, value) + if att == "state-parent" then + local action = SecureButton_GetModifiedAttribute(self, "action", SecureStateChild_GetEffectiveButton(self)) or self.id + if self.id == action then return end + updatecache[self.slot] = true + self.id = action + end + end + + local function EnablePaging(bar) + if pfUI.client <= 11200 then + 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 + else + -- append paging enabled bars to the filter list + for i=1,12 do bar[i]:SetScript("OnAttributeChanged", ButtonSwitch) end + + -- fill all possible page states + local page, pages = nil, {} + while not pages[6] do + for i=1, 6 do + page = i == 1 and 1 or C.bars["bar"..i] and C.bars["bar"..i].pageable == "1" and i + if page then table.insert(pages, page) end + end + end + + bar:SetAttribute("statemap-page", "$input") + bar:SetAttribute("state", (bar:GetAttribute("state-page") or 1)) + + -- prio posses bar + bar.filter = "[bonusbar: 5] 11;" + + -- set bar 8 for druid stealth if enabled + local prowl = class == "DRUID" and C.bars["druidstealth"] == "1" and "8" or "7" + + -- write default pages + for state, page in pairs(pages) do + if page ~= 1 then -- skip page 1 as it is supposed to stay dynamic for stances + bar.filter = string.format("%s[actionbar: %s] %s; ", bar.filter, state, page) + end + end + + -- write page driver conditions + bar.filter = string.format("%s[bonusbar:1,nostealth] 7; [bonusbar:1,stealth] %s; [bonusbar:2] 10; [bonusbar:3] 9; [bonusbar:4] 10; 1", bar.filter, prowl) + + -- prepend pagemaster states if enabled + if C.bars.pagemaster == "1" then + for mod, page in pairs({ ["shift"] = "6", ["ctrl"] = "5", ["alt"] = "3" }) do + bar.filter = string.format("[modifier:%s] %s;", mod, page) .. bar.filter + end + end + + -- enable page driver conditions + RegisterStateDriver(bar, "page", bar.filter) + SecureStateHeader_Refresh(bar) + 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 -- cached from buff scan + local prowlActive = nil -- tracks if prowl is active + + -- Full scan for cat form and prowl (only on login/reload) + local function FullScan() + if class ~= "DRUID" then return nil end + + local foundCat, foundStealth = nil, nil + + for i = 0, 31 do + local texture = GetPlayerBuffTexture(i) + if not texture then break end + + if strfind(texture, "Ability_Druid_CatForm") then + foundCat = true + end + + if strfind(texture, "Ability_Ambush") then + foundStealth = true + end + end + + inCatForm = foundCat + prowlActive = foundCat and foundStealth + return prowlActive + end + + -- Quick scan only for prowl (when we know we're in cat form) + local function HasProwlBuff() + for i = 0, 31 do + local texture = GetPlayerBuffTexture(i) + if not texture then break end + if strfind(texture, "Ability_Ambush") then + return true + end + end + return nil + end + + -- pagemaster / meta page switch + if pfUI.expansion == "vanilla" then + 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_AURAS_CHANGED") + pageswitch:RegisterEvent("PLAYER_ENTERING_WORLD") + pageswitch:RegisterEvent("UNIT_CASTEVENT") + pageswitch:SetScript("OnEvent", function() + if class ~= "DRUID" then return end + + -- On login/reload: full scan + if event == "PLAYER_ENTERING_WORLD" then + prowling = FullScan() + return + end + + -- UNIT_CASTEVENT: detect Prowl cast instantly + -- Prowl Spell IDs: 5215 (Rank 1), 6783 (Rank 2), 9913 (Rank 3) + if event == "UNIT_CASTEVENT" then + local guid, target, cEvent, spellId = arg1, arg2, arg3, arg4 + local _, playerGuid = UnitExists("player") + if guid == playerGuid and cEvent == "CAST" then + if spellId == 5215 or spellId == 6783 or spellId == 9913 then + -- Prowl cast detected + inCatForm = true + prowlActive = true + prowling = true + elseif spellId == 768 then + -- Cat Form cast (Spell ID 768) + inCatForm = true + end + end + return + end + + -- PLAYER_AURAS_CHANGED: smart scanning + if event == "PLAYER_AURAS_CHANGED" then + if prowlActive then + -- We were prowling, check if still prowling + if HasProwlBuff() then + prowling = true + else + -- Prowl ended + prowlActive = nil + prowling = nil + -- Also check if still in cat form + inCatForm = nil + for i = 0, 31 do + local texture = GetPlayerBuffTexture(i) + if not texture then break end + if strfind(texture, "Ability_Druid_CatForm") then + inCatForm = true + break + end + end + end + elseif not inCatForm then + -- Not in cat form, do a full scan (might have just shifted) + prowling = FullScan() + end + -- If inCatForm but not prowlActive, no scan needed (wait for UNIT_CASTEVENT) + 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 required attributes for regular tbc buttons + if pfUI.client > 11200 then + if bar == 11 then + f:SetAttribute("type", "spell") + f:SetAttribute('spell', select(2, GetShapeshiftFormInfo(button))) + elseif bar == 12 then + f:SetAttribute("type1", "pet") + f:SetAttribute("action1", button) + f:SetAttribute("type2", "macro") + f:SetAttribute("macrotext2", "/click PetActionButton".. button .. " RightButton") + else + bars[bar]:SetAttribute("addchild", f) + f:SetAttribute("type", "action") + f:SetAttribute("action", id) + f:SetAttribute("checkselfcast", true) + f:SetAttribute("useparent-unit", true) + f:SetAttribute("useparent-statebutton", true) + + for state = 0, 11 do -- add custom states + local action = ((state == 0 and bar or state)-1)*12+button + f:SetAttribute(string.format("*type-S%d", state), "action") + f:SetAttribute(string.format("*type-S%dRight", state), "action") + f:SetAttribute(string.format("*action-S%d", state), action) + f:SetAttribute(string.format("*action-S%dRight", state), action) + if C.bars.rightself == "1" then + f:SetAttribute(string.format("*unit-S%dRight", state), "player") + else + f:SetAttribute(string.format("*unit-S%dRight", state), nil) + end + end + end + 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 + if C.bars.macroscan == "0" 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 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 + if not 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 + if pfUI.client > 11200 then + if InCombatLockdown and InCombatLockdown() then + -- don't process those events during combat + else + -- set state driver for pet bars + bars[i]:SetAttribute("unit", "pet") + local visibility = pfUI.unlock and pfUI.unlock:IsShown() and "show" or petvisibility + if bars[i].visibility ~= visibility then + RegisterStateDriver(bars[i], 'visibility', visibility) + bars[i].visibility = visibility + end + end + else + -- 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 + 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) + 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 + + -- add up to 0-11 button parent states to each bar + if i <= 10 and pfUI.client > 11200 then + bars[i]:SetAttribute("statebutton", "0:S0;1:S1;2:S2;3:S3;4:S4;5:S5;6:S6;7:S7;8:S8;9:S9;10:S10;11:S11;") + bars[i]:SetAttribute("statebutton2", "0:S0Right;1:S1Right;2:S2Right;3:S3Right;4:S4Right;5:S5Right;6:S6Right;7:S7Right;8:S8Right;9:S9Right;10:S10Right;11:S11Right;") + 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) + 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 + if pfUI.client <= 11200 then + -- 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 + else + local bindwraps = { + ["ACTIONBUTTON%d"] = 1, + ["SHAPESHIFTBUTTON%d"] = 11, -- ShapeShiftBar + ["BONUSACTIONBUTTON%d"] = 12, -- BonusActionBar + ["MULTIACTIONBAR1BUTTON%d"] = 6, -- MultiBarBottomLeft + ["MULTIACTIONBAR2BUTTON%d"] = 5, -- MultiBarBottomRight + ["MULTIACTIONBAR3BUTTON%d"] = 3, -- MultiBarRight + ["MULTIACTIONBAR4BUTTON%d"] = 4, -- MultiBarLeft + ["PFPAGING%d"] = 2, + ["PFSTANCEONE%d"] = 7, + ["PFSTANCETWO%d"] = 8, + ["PFSTANCETHREE%d"] = 9, + ["PFSTANCEFOUR%d"] = 10, + } + + -- rebind all existing bindings to our own buttons + local keybinder = CreateFrame("Frame") + keybinder:RegisterEvent("UPDATE_BINDINGS") + keybinder:SetScript("OnEvent", function() + for name, bar in pairs(bindwraps) do + for i=1,12 do + local key = GetBindingKey(format(name, i)) + local button = bars[bar][i] + if key and button then + SetOverrideBindingClick(button, false, key, button:GetName(), 'LeftButton') + end + end + end + end) + 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 = { } + local reagent_counts = { } + local reagent_capture = SPELL_REAGENTS.."(.+)" + local scanner = libtipscan:GetScanner("actionbar") + + local UpdateSlot = function(slot) + local texture = GetActionTexture(slot) + + -- update buttons that previously had an reagent + if reagent_slots[slot] and not HasAction(slot) then + reagent_slots[slot] = nil + updatecache[slot] = true + end + + -- search for reagent requirements + if HasAction(slot) then + scanner:SetAction(slot) + local _, reagents = scanner:Find(reagent_capture) + + -- remove reagent counts if existing + reagents = reagents and string.gsub(reagents, " %((.+)%)", "") + + -- update on reagent requirement changes + if reagents and reagent_slots[slot] ~= reagents then + reagent_counts[reagents] = reagent_counts[reagents] or 0 + reagent_slots[slot] = reagents + updatecache[slot] = true + end + end + end + + local reagentcounter = CreateFrame("Frame", "pfReagentCounter", UIParent) + reagentcounter:RegisterEvent("PLAYER_ENTERING_WORLD") + reagentcounter:RegisterEvent("ACTIONBAR_SLOT_CHANGED") + reagentcounter:RegisterEvent("BAG_UPDATE") + reagentcounter:SetScript("OnEvent", function() + if event == "BAG_UPDATE" then + this.event = true + else + this.scan = 1 + end + end) + + -- limit events to one per second and smoothen action scanning + reagentcounter:SetScript("OnUpdate", function() + -- scan one action slot per frame + 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 item in pairs(reagent_counts) do + reagent_counts[item] = GetItemCount(item) + 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) diff --git a/modules/bgscore.lua b/modules/bgscore.lua new file mode 100644 index 00000000..932f3918 --- /dev/null +++ b/modules/bgscore.lua @@ -0,0 +1,60 @@ +pfUI:RegisterModule("bgscore", "vanilla:tbc", function () + local bgframe = WorldStateAlwaysUpFrame + if not bgframe then + bgframe = CreateFrame("Frame", "WorldStateAlwaysUpFrame", UIParent) + bgframe:SetWidth(200) + bgframe:SetHeight(25) + bgframe:SetPoint("TOP", UIParent, "TOP", 0, -100) + end + + local mover = CreateFrame("Frame", "pfUIBGScoreMover", UIParent) + mover:SetWidth(220) + mover:SetHeight(30) + mover:SetPoint("TOP", UIParent, "TOP", 0, -100) + mover:SetFrameStrata("DIALOG") + mover:SetMovable(true) + mover:EnableMouse(true) + mover:RegisterForDrag("LeftButton") + mover:SetScript("OnDragStart", function() mover:StartMoving() end) + mover:SetScript("OnDragStop", function() + mover:StopMovingOrSizing() + local x = mover:GetLeft() + local y = mover:GetTop() + pfUI_config = pfUI_config or {} + pfUI_config.positions = pfUI_config.positions or {} + pfUI_config.positions["WorldStateAlwaysUpFrame"] = { x = x, y = y } + bgframe:ClearAllPoints() + bgframe:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", x, y) + DEFAULT_CHAT_FRAME:AddMessage("|cff33ffccBG Score Frame|r position saved.") + end) + mover:Hide() + + pfUI.api.CreateBackdrop(mover, nil, nil, .8) + + -- Title label + local title = mover:CreateFontString(nil, "OVERLAY") + title:SetFont("Fonts\\FRIZQT__.TTF", 14, "OUTLINE") + title:SetText("Battleground Frames") + title:SetPoint("TOP", mover, "TOP", 0, -2) + + -- BG score preview text + local bgscore = mover:CreateFontString(nil, "OVERLAY") + bgscore:SetFont("Fonts\\FRIZQT__.TTF", 10, "OUTLINE") + bgscore:SetText("|cff3399ffAlliance: 123|r | |cffff4444Horde: 456|r") + bgscore:SetPoint("BOTTOM", mover, "BOTTOM", 0, 2) + + mover.label = "BG Score" + pfUI.unlock.frames = pfUI.unlock.frames or {} + table.insert(pfUI.unlock.frames, mover) + + local pos = pfUI_config and pfUI_config.positions and pfUI_config.positions["WorldStateAlwaysUpFrame"] + if pos then + bgframe:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", pos.x, pos.y) + mover:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", pos.x, pos.y) + end + + local origShow = pfUI.unlock.Show + local origHide = pfUI.unlock.Hide + pfUI.unlock.Show = function(self) origShow(self); mover:Show() end + pfUI.unlock.Hide = function(self) origHide(self); mover:Hide(); bgframe:Show() end +end) diff --git a/modules/chat.lua b/modules/chat.lua index ad09a24d..884d027a 100644 --- a/modules/chat.lua +++ b/modules/chat.lua @@ -455,6 +455,8 @@ pfUI:RegisterModule("chat", "vanilla:tbc", function () if C.chat.global.tabmouse == "1" then pfUI.chat.mouseovertab = CreateFrame("Frame") pfUI.chat.mouseovertab:SetScript("OnUpdate", function() + -- throttle to 0.1s + if ( this.tick or .1) > GetTime() then return else this.tick = GetTime() + .1 end if pfUI.chat.hideLock then return end diff --git a/modules/energytick.lua b/modules/energytick.lua index 8e966afc..d3c8e24d 100644 --- a/modules/energytick.lua +++ b/modules/energytick.lua @@ -7,6 +7,9 @@ pfUI:RegisterModule("energytick", "vanilla:tbc", function () energytick:RegisterEvent("UNIT_DISPLAYPOWER") energytick:RegisterEvent("UNIT_ENERGY") energytick:RegisterEvent("UNIT_MANA") + energytick:RegisterEvent("CHAT_MSG_SPELL_SELF_BUFF") + energytick:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_SELF_BUFFS") + energytick:SetScript("OnEvent", function() if UnitPowerType("player") == 0 and C.unitframes.player.manatick == "1" then this.mode = "MANA" @@ -18,6 +21,14 @@ pfUI:RegisterModule("energytick", "vanilla:tbc", function () this:Hide() end + -- Filter nur eigene Energy-Gewinne von Talents/Buffs + if event == "CHAT_MSG_SPELL_SELF_BUFF" or event == "CHAT_MSG_SPELL_PERIODIC_SELF_BUFFS" then + if string.find(arg1, "You gain") and string.find(arg1, "Energy from") then + this.ignoreNextGain = true + end + return + end + if event == "PLAYER_ENTERING_WORLD" then this.lastMana = UnitMana("player") end @@ -38,7 +49,10 @@ pfUI:RegisterModule("energytick", "vanilla:tbc", function () this.badtick = diff end elseif this.mode == "ENERGY" and diff > 0 then - this.target = 2 + if not this.ignoreNextGain then + this.target = 2 + end + this.ignoreNextGain = false end this.lastMana = this.currentMana end @@ -69,14 +83,10 @@ pfUI:RegisterModule("energytick", "vanilla:tbc", function () energytick.spark:SetWidth(C.unitframes.player.pheight + 5) energytick.spark:SetBlendMode('ADD') - -- update spark size on player frame changes local hookUpdateConfig = pfUI.uf.player.UpdateConfig function pfUI.uf.player.UpdateConfig() - -- update spark sizes energytick.spark:SetHeight(C.unitframes.player.pheight + 15) energytick.spark:SetWidth(C.unitframes.player.pheight + 5) - - -- run default unitframe update function hookUpdateConfig(pfUI.uf.player) end -end) +end) \ No newline at end of file diff --git a/modules/nameplates.lua b/modules/nameplates.lua index d751ec9d..c181418a 100644 --- a/modules/nameplates.lua +++ b/modules/nameplates.lua @@ -321,6 +321,9 @@ pfUI:RegisterModule("nameplates", "vanilla:tbc", function () end) nameplates:SetScript("OnUpdate", function() + -- throttle to 10 updates per second instead of every frame + if (this.tick or 0.1) > GetTime() then return else this.tick = GetTime() + 0.1 end + -- propagate events to all nameplates if this.eventcache then this.eventcache = nil diff --git a/modules/skin.lua b/modules/skin.lua index dbf64c1a..7c890016 100644 --- a/modules/skin.lua +++ b/modules/skin.lua @@ -2,6 +2,9 @@ pfUI:RegisterModule("skin", "vanilla:tbc", function () -- align UIParent panels pfUI.panelalign = CreateFrame("Frame", "pfUIParentPanelAlign", UIParent) pfUI.panelalign:SetScript("OnUpdate", function() + -- throttle to 5 updates per second instead of every frame + if (this.tick or 0.2) > GetTime() then return else this.tick = GetTime() + 0.2 end + local left = UIParent.left local center = UIParent.center local rbpos, ropos diff --git a/modules/tooltip.lua b/modules/tooltip.lua index f45159ed..03c51040 100644 --- a/modules/tooltip.lua +++ b/modules/tooltip.lua @@ -33,6 +33,10 @@ pfUI:RegisterModule("tooltip", "vanilla:tbc", function () tooltip.cursor:SetWidth(tonumber(C.tooltip.cursoroffset) * 2) tooltip.cursor:SetHeight(tonumber(C.tooltip.cursoroffset) * 2) tooltip.cursor:SetScript("OnUpdate", function() + -- throttle to 0.1s - cursor following doesn't need to be every frame + if (this.tick or 0) > GetTime() then return end + this.tick = GetTime() + 0.1 + local scale = UIParent:GetScale() local x, y = GetCursorPosition() this:SetPoint("CENTER", UIParent, "BOTTOMLEFT", x/scale, y/scale)