--[[ Author: Alternator (Massiner of Nathrezim) Copyright 2010 Notes: ]] local Util = BFUtil; local Const = BFConst; local UILib = BFUILib; local CustomAction = BFCustomAction; local Button = BFButton; local Bar = BFBar; local EventFull = BFEventFrames["Full"]; local MiscFrame = BFEventFrames["Misc"]; local Delay = BFEventFrames["Delay"]; local ConfigureLayer = BFConfigureLayer; local DestroyBarOverlay = BFDestroyBarOverlay; --This will get the currently applicable locale, or allocate it if needed (note that locales other than enUS will need the metatable set) BFLocales[GetLocale()] = BFLocales[GetLocale()] or {}; local Locale = BFLocales[GetLocale()]; if (GetLocale() ~= "enUS") then setmetatable(Locale, BFLocales["enUS"]); end Util.ActiveButtons = {}; Util.InactiveButtons = {}; Util.ActiveMacros = {}; Util.ActiveSpells = {}; Util.ActiveItems = {}; Util.ActiveBonusActions = {}; Util.RangeTimerButtons = {}; Util.FlashButtons = {}; Util.ActiveBars = {}; Util.InactiveBars = {}; Util.ActiveTabs = {}; Util.InactiveTabs = {}; Util.SpellIndex = {}; Util.SpellMana = {}; Util.NewSpellIndex = {}; Util.GlowSpells = {}; Util.PetSpellIndex = {}; Util.NewPetSpellIndex = {}; Util.BagItemNameIndex = {}; Util.BagItemIdIndex = {}; Util.BagItemNameId = {}; Util.InvItemNameIndex = {}; Util.InvItemIdIndex = {}; Util.InvItemNameId = {}; Util.GridHidden = true; Util.LowStrata = true; Util.BlizBarWrappers = {}; Util.BlizEnabledBars = {}; Util.CallbackFunctions = {}; Util.CallbackArgs = {}; Util.ButtonWidgetMap = {}; Util.UpdateMacroEventCount = 0; Util.MacroCheckDelayComplete = false; Util.ForceOffCastOnKeyDown = false; Util.MountUselessIndexToIndex = {}; --One quick override function local G_PickupSpellBookItem = PickupSpellBookItem; local function PickupSpellBookItem(NameRank, Book) local Index, Alt_Book = Util.LookupSpellIndex(NameRank); if (Index) then return G_PickupSpellBookItem(Index, Alt_Book); elseif (Book) then return G_PickupSpellBookItem(NameRank, Book); end return G_PickupSpellBookItem(NameRank); end --[[ Make sure that the saved data is kept inline with the version being run --]] function Util.UpdateSavedData() ---- FIX for MACS, if character data hasn't loaded but is otherwise available in the global save ---- local CharRealm = UnitName("player"); CharRealm = CharRealm.."-"..GetRealmName(); if (ButtonForgeSave == nil and ButtonForgeGlobalBackup ~= nil and ButtonForgeGlobalBackup[CharRealm] ~= nil) then ButtonForgeSave = ButtonForgeGlobalBackup[CharRealm]; end ------The following section updates the per character saved data------ --Need to allocate save structure if (not ButtonForgeSave) then --Swap v0.9.0 / v0.9.1 / v0.9.2 users to the new save structure if (type(BFSave) == "table" and BFSave["Version"] and BFSave["VersionMinor"] and BFSave["Version"] == 0.9 and BFSave["VersionMinor"] <= 2) then --the above test checks if a legitimate ButtonForge BFSave exists before we adopt it to the new table name ButtonForgeSave = BFSave; BFSave = nil; else ButtonForgeSave = {}; ButtonForgeSave["ConfigureMode"] = true; ButtonForgeSave["AdvancedMode"] = false; ButtonForgeSave["RightClickSelfCast"] = false; ButtonForgeSave["Version"] = Const.Version; ButtonForgeSave["VersionMinor"] = Const.VersionMinor; ButtonForgeSave.Bars = {}; end ButtonForgeSave["AddonName"] = "Button Forge"; end --v0.9.3 update if (ButtonForgeSave["Version"] == 0.9 and ButtonForgeSave["VersionMinor"] < 3) then for i = 1, #ButtonForgeSave.Bars do ButtonForgeSave.Bars[i]["HBonusBar"] = true; end ButtonForgeSave["VersionMinor"] = 3; DEFAULT_CHAT_FRAME:AddMessage(Util.GetLocaleString("UpgradedChatMsg").."v0.9.3", .5, 1, 0, 1); end --v0.9.12 update if (ButtonForgeSave["Version"] == 0.9 and ButtonForgeSave["VersionMinor"] < 12) then for i = 1, #ButtonForgeSave.Bars do ButtonForgeSave.Bars[i]["MacroText"] = true; ButtonForgeSave.Bars[i]["KeyBindText"] = true; end ButtonForgeSave["VersionMinor"] = 12; DEFAULT_CHAT_FRAME:AddMessage(Util.GetLocaleString("UpgradedChatMsg").."v0.9.12", .5, 1, 0, 1); end --v0.9.13 update if (ButtonForgeSave["Version"] == 0.9 and ButtonForgeSave["VersionMinor"] < 13) then for i = 1, #ButtonForgeSave.Bars do ButtonForgeSave.Bars[i]["Enabled"] = true; ButtonForgeSave.Bars[i]["ButtonGap"] = 6; end ButtonForgeSave["VersionMinor"] = 13; DEFAULT_CHAT_FRAME:AddMessage(Util.GetLocaleString("UpgradedChatMsg").."v0.9.13", .5, 1, 0, 1); end --v0.9.17 update if (ButtonForgeSave["Version"] == 0.9 and ButtonForgeSave["VersionMinor"] < 17) then for i = 1, #ButtonForgeSave.Bars do ButtonForgeSave.Bars[i]["GUI"] = true; ButtonForgeSave.Bars[i]["Alpha"] = 1; end ButtonForgeSave["VersionMinor"] = 17; DEFAULT_CHAT_FRAME:AddMessage(Util.GetLocaleString("UpgradedChatMsg").."v0.9.17", .5, 1, 0, 1); end --v0.9.22 update if (ButtonForgeSave["Version"] == 0.9 and ButtonForgeSave["VersionMinor"] < 22) then for i = 1, #ButtonForgeSave.Bars do if (ButtonForgeSave.Bars[i]["VDriver"] == "[bonusbar:5] show; hide") then ButtonForgeSave.Bars[i]["VDriver"] = "[overridebar][vehicleui] show; hide"; end end ButtonForgeSave["VersionMinor"] = 22; DEFAULT_CHAT_FRAME:AddMessage(Util.GetLocaleString("UpgradedChatMsg").."v0.9.22", .5, 1, 0, 1); end -- v0.9.34 update if (ButtonForgeSave["Version"] == 0.9 and ButtonForgeSave["VersionMinor"] < 34) then for i = 1, #ButtonForgeSave.Bars do Util.UpdateMounts602(ButtonForgeSave.Bars[i].Buttons); end ButtonForgeSave["VersionMinor"] = 34; DEFAULT_CHAT_FRAME:AddMessage(Util.GetLocaleString("UpgradedChatMsg").."v0.9.34", .5, 1, 0, 1); end -- v0.9.36 update if (ButtonForgeSave["Version"] == 0.9 and ButtonForgeSave["VersionMinor"] < 36) then if (ButtonForgeSave.UndoProfileBars ~= nil) then for i = 1, #ButtonForgeSave.UndoProfileBars do Util.UpdateMounts602(ButtonForgeSave.UndoProfileBars[i].Buttons); end end ButtonForgeSave["VersionMinor"] = 36; DEFAULT_CHAT_FRAME:AddMessage(Util.GetLocaleString("UpgradedChatMsg").."v0.9.36", .5, 1, 0, 1); end -- v0.9.41 update if (ButtonForgeSave["Version"] == 0.9 and ButtonForgeSave["VersionMinor"] < 41) then for i = 1, #ButtonForgeSave.Bars do Util.UpdateMounts700(ButtonForgeSave.Bars[i].Buttons); end if (ButtonForgeSave.UndoProfileBars ~= nil) then for i = 1, #ButtonForgeSave.UndoProfileBars do Util.UpdateMounts700(ButtonForgeSave.UndoProfileBars[i].Buttons); end end ButtonForgeSave["VersionMinor"] = 41; DEFAULT_CHAT_FRAME:AddMessage(Util.GetLocaleString("UpgradedChatMsg").."v0.9.41", .5, 1, 0, 1); end -- v0.9.42 update if (ButtonForgeSave["Version"] == 0.9 and ButtonForgeSave["VersionMinor"] < 42) then for i = 1, #ButtonForgeSave.Bars do ButtonForgeSave.Bars[i].HSpec3 = false; ButtonForgeSave.Bars[i].HSpec4 = false; end if (ButtonForgeSave.UndoProfileBars ~= nil) then for i = 1, #ButtonForgeSave.UndoProfileBars do ButtonForgeSave.UndoProfileBars[i].HSpec3 = false; ButtonForgeSave.UndoProfileBars[i].HSpec4 = false; end end ButtonForgeSave["VersionMinor"] = 42; DEFAULT_CHAT_FRAME:AddMessage(Util.GetLocaleString("UpgradedChatMsg").."v0.9.42", .5, 1, 0, 1); end -- v0.9.44 update if (ButtonForgeSave["Version"] == 0.9 and ButtonForgeSave["VersionMinor"] < 44) then for i = 1, #ButtonForgeSave.Bars do Util.RemoveCancelPossession700(ButtonForgeSave.Bars[i].Buttons); end if (ButtonForgeSave.UndoProfileBars ~= nil) then for i = 1, #ButtonForgeSave.UndoProfileBars do Util.RemoveCancelPossession700(ButtonForgeSave.UndoProfileBars[i].Buttons); end end ButtonForgeSave["VersionMinor"] = 44; DEFAULT_CHAT_FRAME:AddMessage(Util.GetLocaleString("UpgradedChatMsg").."v0.9.44", .5, 1, 0, 1); end --Bring v up to the latest version if (ButtonForgeSave["Version"] < Const.Version) then ButtonForgeSave["Version"] = Const.Version; ButtonForgeSave["VersionMinor"] = Const.VersionMinor; DEFAULT_CHAT_FRAME:AddMessage(Util.GetLocaleString("UpgradedChatMsg").."v"..Const.Version.."."..Const.VersionMinor, .5, 1, 0, 1); elseif (ButtonForgeSave["Version"] == Const.Version and ButtonForgeSave["VersionMinor"] < Const.VersionMinor) then ButtonForgeSave["VersionMinor"] = Const.VersionMinor; DEFAULT_CHAT_FRAME:AddMessage(Util.GetLocaleString("UpgradedChatMsg").."v"..Const.Version.."."..Const.VersionMinor, .5, 1, 0, 1); end -----This section updates the global button forge data (introduced at 0.9.16) if (not ButtonForgeGlobalSettings) then ButtonForgeGlobalSettings = {}; ButtonForgeGlobalSettings["Version"] = 0.9; ButtonForgeGlobalSettings["VersionMinor"] = 16; ButtonForgeGlobalSettings["MacroCheckDelay"] = 3; ButtonForgeGlobalSettings["RemoveMissingMacros"] = true; end --v0.9.30 update (to global settings) if (ButtonForgeGlobalSettings["Version"] == 0.9 and ButtonForgeGlobalSettings["VersionMinor"] < 30) then ButtonForgeGlobalSettings["ForceOffCastOnKeyDown"] = false; ButtonForgeGlobalSettings["VersionMinor"] = 30; end --v0.9.31 update (to global profiles) if (ButtonForgeGlobalSettings["Version"] == 0.9 and ButtonForgeGlobalSettings["VersionMinor"] < 31) then ButtonForgeGlobalProfiles = {}; ButtonForgeGlobalSettings["VersionMinor"] = 31; end --pre v0.9.36 Safety process if (not ButtonForgeGlobalProfiles) then ButtonForgeGlobalProfiles = {}; end --v0.9.36 update if (ButtonForgeGlobalSettings["Version"] == 0.9 and ButtonForgeGlobalSettings["VersionMinor"] < 36) then for k,v in pairs(ButtonForgeGlobalProfiles) do for i = 1, #v.Bars do Util.UpdateMounts602(v.Bars[i].Buttons); end end ButtonForgeGlobalSettings["VersionMinor"] = 36; end --v0.9.38 update if (ButtonForgeGlobalBackup == nil) then ButtonForgeGlobalBackup = {}; end -- v0.9.41 if (ButtonForgeGlobalSettings["Version"] == 0.9 and ButtonForgeGlobalSettings["VersionMinor"] < 41) then for k, v in pairs(ButtonForgeGlobalProfiles) do for i = 1, #v.Bars do Util.UpdateMounts700(v.Bars[i].Buttons); end end ButtonForgeGlobalSettings["VersionMinor"] = 41; end -- v0.9.42 if (ButtonForgeGlobalSettings["Version"] == 0.9 and ButtonForgeGlobalSettings["VersionMinor"] < 42) then for k, v in pairs(ButtonForgeGlobalProfiles) do for i = 1, #v.Bars do v.Bars[i].HSpec3 = false; v.Bars[i].HSpec4 = false; end end ButtonForgeGlobalSettings["VersionMinor"] = 42; end -- v0.9.44 if (ButtonForgeGlobalSettings["Version"] == 0.9 and ButtonForgeGlobalSettings["VersionMinor"] < 44) then ButtonForgeGlobalSettings["UseCollectionsFavoriteMountButton"] = not AreDangerousScriptsAllowed(); ButtonForgeGlobalSettings["VersionMinor"] = 44; for k, v in pairs(ButtonForgeGlobalProfiles) do for i = 1, #v.Bars do Util.RemoveCancelPossession700(v.Bars[i].Buttons); end end end --Bring the global settings up to the latest version if (ButtonForgeGlobalSettings["Version"] < Const.Version) then ButtonForgeGlobalSettings["Version"] = Const.Version; ButtonForgeGlobalSettings["VersionMinor"] = Const.VersionMinor; elseif (ButtonForgeGlobalSettings["Version"] == Const.Version and ButtonForgeGlobalSettings["VersionMinor"] < Const.VersionMinor) then ButtonForgeGlobalSettings["VersionMinor"] = Const.VersionMinor; end end function Util.UpdateMounts602(Buttons) Util.UpdateMounts700(Buttons) for j = 1, #Buttons do if (Buttons[j]["Mode"] == "companion") then -- Either fix the mapping, or clear the mount local MountID = Util.GetMountIDFromName(Buttons[j]["CompanionName"]); Buttons[j]["Mode"] = nil; Buttons[j]["CompanionId"] = nil; Buttons[j]["CompanionType"] = nil; Buttons[j]["CompanionIndex"] = nil; Buttons[j]["CompanionName"] = nil; Buttons[j]["CompanionSpellName"] = nil; if (Index) then Buttons[j]["Mode"] = "mount"; Buttons[j]["MountID"] = MountID; end end end end function Util.UpdateMounts700(Buttons) for j = 1, #Buttons do if (Buttons[j]["Mode"] == "mount") then local MountIndex = Buttons[j]["MountIndex"]; local MountName = Buttons[j]["MountName"]; Buttons[j]["MountID"] = nil; Buttons[j]["MountSpellID"] = nil; Buttons[j]["MountName"] = nil; Buttons[j]["MountIndex"] = nil; if (MountIndex == 0) then Buttons[j]["MountID"] = Const.SUMMON_RANDOM_FAVORITE_MOUNT_ID; else local MountID = Util.GetMountIDFromName(MountName); if (MountID) then Buttons[j]["MountID"] = MountID; else Buttons[j]["Mode"] = nil; end end end end end function Util.RemoveCancelPossession700(Buttons) for j = 1, #Buttons do if (Buttons[j]["Mode"] == "customaction" and Buttons[j]["CustomActionName"] == "possesscancel") then Buttons[j]["Mode"] = nil; Buttons[j]["CustomActionName"] = nil; end end end --[[ Load the bars and buttons from the saved addon values --]] function Util.Load() local CharRealm = UnitName("player"); CharRealm = CharRealm.."-"..GetRealmName(); ButtonForgeGlobalBackup[CharRealm] = ButtonForgeSave; Util.ForceOffCastOnKeyDown = ButtonForgeGlobalSettings["ForceOffCastOnKeyDown"]; if (not Util.ForceOffCastOnKeyDown) then hooksecurefunc("SetCVar", MiscFrame.SetCVarCalled); end if (ButtonForgeSave.ConfigureMode) then ConfigureLayer:Show(); end if (ButtonForgeSave.AdvancedMode) then UILib.ToggleAdvancedTools(); end --if (Util.LBFMasterGroup and ButtonForgeSave["SkinID"]) then -- Util.LBFMasterGroup:Skin(ButtonForgeSave.SkinID, ButtonForgeSave.Gloss, ButtonForgeSave.Backdrop, ButtonForgeSave.Colors); --end for i = 1, #ButtonForgeSave.Bars do Util.NewBar(0, 0, ButtonForgeSave.Bars[i]); end UILib.ToggleRightClickSelfCast(ButtonForgeSave["RightClickSelfCast"] or false); Util.Loaded = true; Util.StartMacroCheckDelay(); Util.RefreshOnUpdateFunction(); SLASH_BUTTONFORGE1 = Util.GetLocaleString("SlashButtonForge1"); -- = "/buttonforge"; --these two identifiers probably shouldn't change, but if need be they can be?! SLASH_BUTTONFORGE2 = Util.GetLocaleString("SlashButtonForge2"); -- = "/bufo"; collectgarbage("collect"); Util.CallbackEvent("INITIALISED"); end -- Grabbed from the Lua wiki function Util.deepcopy(orig) local orig_type = type(orig) local copy if orig_type == 'table' then copy = {} for orig_key, orig_value in next, orig, nil do copy[Util.deepcopy(orig_key)] = Util.deepcopy(orig_value) end -- setmetatable(copy, Util.deepcopy(getmetatable(orig))) -- I don't need this specifically for ButtonForge else -- number, string, boolean, etc copy = orig end return copy end --[[ Load the bars and buttons from a profile --]] function Util.LoadProfile(ProfileName) if (InCombatLockdown()) then Util.SlashShowMessageByLine(Util.GetLocaleString("ActionFailedCombatLockdown")); return; end if (ButtonForgeGlobalProfiles[string.upper(ProfileName)] == nil) then Util.SlashShowMessageByLine(Util.GetLocaleString("ProfileNotFound")); return; end -- 1. Store the current configuration as the revert configuration for this character, in case the players wishes to go back ButtonForgeSave.UndoProfileBars = Util.deepcopy(ButtonForgeSave.Bars); -- 2. Deallocate the current UI for i = #Util.ActiveBars, 1, -1 do Util.DeallocateBar(Util.ActiveBars[i]); end -- 3. Apply the profile as the new bars/buttons for this character ButtonForgeSave.Bars = Util.deepcopy(ButtonForgeGlobalProfiles[string.upper(ProfileName)].Bars); -- 4. Attach the Bars back to the UI for i = 1, #ButtonForgeSave.Bars do Util.NewBar(0, 0, ButtonForgeSave.Bars[i]); end Util.RefreshCompanions(); Util.RefreshMacros(); Util.RefreshEquipmentSets(); Util.RefreshSpells(); Util.RefreshGridStatus(true); Util.RefreshBarStrata(true); Util.RefreshBarGUIStatus(); DEFAULT_CHAT_FRAME:AddMessage(Util.GetLocaleString("LoadedProfile"), .5, 1, 0, 1); end --[[ Load the bars and buttons from a profile --]] function Util.LoadProfileTemplate(ProfileName) if (InCombatLockdown()) then Util.SlashShowMessageByLine(Util.GetLocaleString("ActionFailedCombatLockdown")); return; end if (ButtonForgeGlobalProfiles[string.upper(ProfileName)] == nil) then Util.SlashShowMessageByLine(Util.GetLocaleString("ProfileNotFound")); return; end -- 1. Store the current configuration as the revert configuration for this character, in case the players wishes to go back ButtonForgeSave.UndoProfileBars = Util.deepcopy(ButtonForgeSave.Bars); -- 2. Deallocate the current UI for i = #Util.ActiveBars, 1, -1 do Util.DeallocateBar(Util.ActiveBars[i]); end -- 3. Apply the profile as the new bars/buttons for this character ButtonForgeSave.Bars = Util.deepcopy(ButtonForgeGlobalProfiles[string.upper(ProfileName)].Bars); -- 4. Attach the Bars back to the UI for i = 1, #ButtonForgeSave.Bars do Util.NewBar(0, 0, ButtonForgeSave.Bars[i]); end -- 5. Blank all the buttons - this is programatically the easiest way to handle it for i = 1, #Util.ActiveButtons do if (Util.ActiveButtons[i].Mode ~= "bonusaction") then Util.ActiveButtons[i]:SetCommandFromTriplet(); end end Util.RefreshGridStatus(true); Util.RefreshBarStrata(true); Util.RefreshBarGUIStatus(); DEFAULT_CHAT_FRAME:AddMessage(Util.GetLocaleString("LoadedProfileTemplate"), .5, 1, 0, 1); end --[[ Save the bars and buttons to a profile, NB ProfileName is case insenstive for the purposes of saving loading deleting --]] function Util.SaveProfile(ProfileName) local NewProfile = {}; NewProfile.Name = ProfileName; -- To capture the case sensitive version -- Record this extra info in case it is useful later, probably wont be but could help keep profiles organised with a future update?? NewProfile.Icon = "INV_Misc_QuestionMark"; --"Interface/Icons/".. for when/if a gui gets setup it will be good to assign an icon NewProfile.Date = date("%c"); NewProfile.Realm = GetRealmName(); NewProfile.Char = UnitName("player"); NewProfile.Class = UnitClass("player"); NewProfile.Bars = Util.deepcopy(ButtonForgeSave.Bars) ButtonForgeGlobalProfiles[string.upper(ProfileName)] = NewProfile; DEFAULT_CHAT_FRAME:AddMessage(Util.GetLocaleString("SavedProfile"), .5, 1, 0, 1); end --[[ -- Lazy, I should've factored this with the save profile function --]] function Util.UndoProfile() if (InCombatLockdown()) then Util.SlashShowMessageByLine(Util.GetLocaleString("ActionFailedCombatLockdown")); return; end if (ButtonForgeSave.UndoProfileBars == nil) then return; end for i = #Util.ActiveBars, 1, -1 do Util.DeallocateBar(Util.ActiveBars[i]); end ButtonForgeSave.Bars = Util.deepcopy(ButtonForgeSave.UndoProfileBars); for i = 1, #ButtonForgeSave.Bars do Util.NewBar(0, 0, ButtonForgeSave.Bars[i]); end Util.RefreshCompanions(); Util.RefreshMacros(); Util.RefreshEquipmentSets(); Util.RefreshSpells(); Util.RefreshGridStatus(true); Util.RefreshBarStrata(true); Util.RefreshBarGUIStatus(); DEFAULT_CHAT_FRAME:AddMessage(Util.GetLocaleString("UndoneProfile"), .5, 1, 0, 1); end function Util.DeleteProfile(ProfileName) ProfileName = string.upper(ProfileName); if (ButtonForgeGlobalProfiles[ProfileName]) then ButtonForgeGlobalProfiles[ProfileName] = nil; DEFAULT_CHAT_FRAME:AddMessage(Util.GetLocaleString("DeletedProfile"), .5, 1, 0, 1); end -- Some day I may add comfirmations, at least perhaps for the /slash commands... perhaps also a session log?? But when?! end --[[ Save Button Facade settings if present --]] function Util:ButtonFacadeCallback(SkinID, Gloss, Backdrop, Group, Button, Colors) -- If no group is specified, save the data as the root add-on skin. -- This will allow the ButtonFacade GUI to display it correctly. return; -- This may longer be necessary --[[if not Group then ButtonForgeSave["SkinID"] = SkinID; ButtonForgeSave["Gloss"] = Gloss; ButtonForgeSave["Backdrop"] = Backdrop; ButtonForgeSave["Colors"] = Colors; else --not presently implemented by Button Forge -- ButtonForgeSave[Group]["SkinID"] = SkinID; ButtonForgeSave[Group]["Gloss"] = Gloss; ButtonForgeSave[Group]["Backdrop"] = Backdrop; ButtonForgeSave[Group]["Colors"] = Colors; end]] end --[[ To allow auto-alignment to also consider the blizzard multibars, this function will create wrappers to go on these bars which will also be considered when dragging a Button Forge bar... Since this is possibly a compatibility point, it can be hard disabled by a const param, it will also disable in the presence of Bartender which I know it doesn't function properly with --]] function Util.CreateBlizzardBarWrappers() if (IsAddOnLoaded("Bartender4") or Const.DisableAutoAlignAgainstDefaultBars) then return; end Util.BlizBarWrappers[1] = CreateFrame("FRAME", nil, UIParent); Util.BlizBarWrappers[1]:SetPoint("TOPLEFT", MultiBarBottomLeftButton1, "TOPLEFT", -Const.I, Const.I + 1); Util.BlizBarWrappers[1]:SetPoint("BOTTOMRIGHT", MultiBarBottomLeftButton12, "BOTTOMRIGHT", Const.I, -Const.I + 1); Util.BlizBarWrappers[2] = CreateFrame("FRAME", nil, UIParent); Util.BlizBarWrappers[2]:SetPoint("TOPLEFT", MultiBarBottomRightButton1, "TOPLEFT", -Const.I, Const.I + 1); Util.BlizBarWrappers[2]:SetPoint("BOTTOMRIGHT", MultiBarBottomRightButton12, "BOTTOMRIGHT", Const.I, -Const.I + 1); Util.BlizBarWrappers[3] = CreateFrame("FRAME", nil, UIParent); Util.BlizBarWrappers[3]:SetPoint("TOPLEFT", MultiBarRightButton1, "TOPLEFT", -Const.I, Const.I + 1); Util.BlizBarWrappers[3]:SetPoint("BOTTOMRIGHT", MultiBarRightButton12, "BOTTOMRIGHT", Const.I, -Const.I + 1); Util.BlizBarWrappers[4] = CreateFrame("FRAME", nil, UIParent); Util.BlizBarWrappers[4]:SetPoint("TOPLEFT", MultiBarLeftButton1, "TOPLEFT", -Const.I, Const.I + 1); Util.BlizBarWrappers[4]:SetPoint("BOTTOMRIGHT", MultiBarLeftButton12, "BOTTOMRIGHT", Const.I, -Const.I + 1); end function Util.UpdateBlizzardEnabledBarsMap() Util.BlizEnabledBars = {GetActionBarToggles()}; end --[[ Button Allocation Functions --]] function Util.NewButton(Parent, ButtonSave, ButtonLocked, TooltipOn, MacroText, KeyBindText) if (InCombatLockdown()) then return; end local NewButton; if (#(Util.InactiveButtons) > 0) then NewButton = table.remove(Util.InactiveButtons); NewButton:Configure(Parent, ButtonSave, ButtonLocked, TooltipOn, MacroText, KeyBindText); else NewButton = Button.New(Parent, ButtonSave, ButtonLocked, TooltipOn, MacroText, KeyBindText); Util.ButtonWidgetMap[NewButton.Widget] = NewButton; end table.insert(Util.ActiveButtons, NewButton); Util.CallbackEvent("BUTTON_ALLOCATED", NewButton.Widget:GetName()); return NewButton; end function Util.DeallocateButton(Value) if (InCombatLockdown()) then return; end Value:Deallocate(); table.remove(Util.ActiveButtons, Util.FindInTable(Util.ActiveButtons, Value)); table.insert(Util.InactiveButtons, Value); Util.CallbackEvent("BUTTON_DEALLOCATED", Value.Widget:GetName()); end function Util.DetachButton(Value) if (InCombatLockdown()) then return; end Value:Detach(); table.remove(Util.ActiveButtons, Util.FindInTable(Util.ActiveButtons, Value)); table.insert(Util.InactiveButtons, Value); Util.CallbackEvent("BUTTON_DEALLOCATED", Value.Widget:GetName()); end --[[ Bar Allocation Functions --]] function Util.NewBarSave() local Save = {}; Save["Left"] = 0; Save["Top"] = 0; Save["Scale"] = 1; Save["Order"] = #Util.ActiveBars; Save["Label"] = nil; Save["Rows"] = Const.DefaultRows; Save["Cols"] = Const.DefaultCols; Save["VDriver"] = nil; Save["HVehicle"] = true; Save["HSpec1"] = false; Save["HSpec2"] = false; Save["HSpec3"] = false; Save["HSpec4"] = false; Save["HBonusBar"] = true; Save["GridAlwaysOn"] = true; Save["ButtonsLocked"] = false; Save["TooltipsOn"] = true; Save["MacroText"] = true; Save["KeyBindText"] = true; Save["ButtonGap"] = 6; Save["Enabled"] = true; Save["BonusBar"] = false; Save["GUI"] = true; Save["Alpha"] = 1; Save["Buttons"] = {}; return Save; end function Util.NewBar(Left, Top, BarSave) if (InCombatLockdown()) then return; end local NewBar; if (not BarSave) then BarSave = Util.NewBarSave(); BarSave["Left"] = Left; BarSave["Top"] = Top; table.insert(ButtonForgeSave.Bars, BarSave); PlaySound(177, "Master"); end if (#(Util.InactiveBars) > 0) then NewBar = table.remove(Util.InactiveBars); NewBar:Configure(BarSave); else NewBar = Bar.New(BarSave); end table.insert(Util.ActiveBars, NewBar); if (NewBar.Cols * NewBar.Rows == 0) then --Failed to allocate buttons, get rid of the bar NewBar:Deallocate(); return nil; else Util.RefreshTab(NewBar.ControlFrame:GetLeft(), NewBar.ControlFrame:GetTop()); return NewBar; end end function Util.NewBonusBar(Left, Top) if (InCombatLockdown()) then return; end local BarSave = Util.NewBarSave(); BarSave["BonusBar"] = true; BarSave["Left"] = Left; BarSave["Top"] = Top; BarSave["Rows"] = 1; BarSave["Cols"] = 13; BarSave["HBonusBar"] = false; BarSave["HVehicle"] = false; BarSave["VDriver"] = "[overridebar][vehicleui] show; hide"; BarSave["ButtonsLocked"] = true; BarSave["GridAlwaysOn"] = false; table.insert(ButtonForgeSave.Bars, BarSave); PlaySound(177, "Master"); return Util.NewBar(Left, Top, BarSave); end function Util.DeallocateBar(Value) if (InCombatLockdown()) then return; end Value:Deallocate(); --Note that deallocating a bar will call a function that changes the bars state (primarily it removes all buttons, and changes it's order... both of which change the save state data) table.remove(Util.ActiveBars, Util.FindInTable(Util.ActiveBars, Value)); table.remove(ButtonForgeSave.Bars, Util.FindInTable(ButtonForgeSave.Bars, Value.BarSave)); table.insert(Util.InactiveBars, Value); local Left, Top = Value.ControlFrame:GetLeft(), Value.ControlFrame:GetTop(); Util.RefreshTab(Left, Top); PlaySound(6523, "Master"); end function Util.DetachBar(Value) if (InCombatLockdown()) then return; end Value:Detach(); table.remove(Util.ActiveBars, Util.FindInTable(Util.ActiveBars, Value)); table.insert(Util.InactiveBars, Value); local Left, Top = Value.ControlFrame:GetLeft(), Value.ControlFrame:GetTop(); Util.RefreshTab(Left, Top); end function Util.GetButtonFrameName(Label) Label = Label or ""; local Name = Const.BarNaming.."Bar_"..(Label or ""); if (not _G[Name.."_ButtonFrame"] and Label ~= "") then return Name.."_ButtonFrame"; end while true do if (not _G[Name..Const.BarSeq.."_ButtonFrame"]) then return Name..Const.BarSeq.."_ButtonFrame"; end Const.BarSeq = Const.BarSeq + 1; end end --[[ Bar Management Functions --]] function Util.ReorderBar(Bar, NewPosition) local CurrentPosition = Bar.BarSave["Order"]; local Order; if (CurrentPosition > NewPosition) then for i = 1, #Util.ActiveBars do Order = Util.ActiveBars[i].BarSave["Order"]; if (Order < CurrentPosition and Order >= NewPosition) then Util.ActiveBars[i]:SetOrder(Order + 1); end end elseif (CurrentPosition < NewPosition) then for i = 1, #Util.ActiveBars do Order = Util.ActiveBars[i].BarSave["Order"]; if (Order > CurrentPosition and Order <= NewPosition) then Util.ActiveBars[i]:SetOrder(Order - 1); end end end Bar:SetOrder(NewPosition); end function Util.DockCoords(Left, Top, ExcludeBar) local CLeft, CTop, CDist, CBar = 0, 0, 100000000, nil; --This is an arbitrary number that will be big enough here local Dist, X, Y; local Bars = Util.ActiveBars; for i = 1, #Bars do X = Bars[i].ControlFrame:GetLeft();--BarSave["Left"]; Y = Bars[i].ControlFrame:GetTop();--BarSave["Top"]; Dist = ((Left - X) ^ 2)+ ((Top - Y) ^ 2); if (Dist < CDist and Bars[i] ~= ExcludeBar) then CLeft = X; CTop = Y; CDist = Dist; CBar = Bars[i]; end end return CLeft, CTop, CDist, CBar; end function Util.FindClosestPoint(Coord, Points, Offsets, ExcludeBar) local MatchedBar, MatchedPoint, Shift, MatchedCoord = nil, 0, 0, 0; local Calc, CalcCoord, MinCalc = 0, 0, 10000000; local Bars = Util.ActiveBars; for i = 1, #Bars do if (Bars[i] ~= ExcludeBar) then for j = 1, #Points do CalcCoord = Bars[i].ControlFrame[Points[j]](Bars[i].ControlFrame) + Offsets[j]; --basically translates down to things like Bars[i]:GetLeft() + Offset[j]; if Points[j] is "GetLeft" Calc = CalcCoord - Coord; Calc = Calc * Calc; if (Calc < MinCalc - 0.1) then MinCalc = Calc; MatchedCoord = CalcCoord; Shift = CalcCoord - Coord; MatchedBar = Bars[i].ControlFrame; MatchedPoint = j; end end end end Bars = Util.BlizBarWrappers; local EnabledBars = Util.BlizEnabledBars; for i = 1, #Bars do if (EnabledBars[i]) then for j = 1, #Points do CalcCoord = Bars[i][Points[j]](Bars[i]); if (not CalcCoord) then Util.BlizEnabledBars[i] = false; break; else CalcCoord = CalcCoord + Offsets[j]; --basically translates down to things like Bars[i]:GetLeft() + Offset[j]; if Points[j] is "GetLeft" Calc = CalcCoord - Coord; Calc = Calc * Calc; if (Calc < MinCalc - 0.1) then MinCalc = Calc; MatchedCoord = CalcCoord; Shift = CalcCoord - Coord; MatchedBar = Bars[i]; MatchedPoint = j; end end end end end return MatchedBar, MatchedPoint, MinCalc, Shift, MatchedCoord; end --[[ While the Configure overlay is shown make sure that all bars are visible (unless in combat), this function also cleansup when the overlay is hidden again --]] function Util.VDriverOverride() if (InCombatLockdown()) then return; end local Bars = Util.ActiveBars; if (ConfigureLayer:IsShown() or DestroyBarOverlay:IsShown()) then for i = 1, #Bars do Bars[i]:SetTempShowVD(); end else for i = 1, #Bars do Bars[i]:ClearTempShowVD(); end end end --[[ Since the Spellbook has an annoying tendancy to cover bars this function will raise (or relower) the buttons to so they can be clicked Note: It will do this for everything but items - since that could quickly get annoying when moving items around (there isn't really a perfect solution, so best to not go overboard) Also it doesn't do a combat lockdown check, it is expected that this has been done up the chain (the bars will auto drop to the low strata if combat begins!) --]] function Util.RefreshBarStrata(ForceUpdate) local LowStrata = Util.InCombat or DestroyBarOverlay:IsShown() or (GetCursorInfo() == "item" and not IsShiftKeyDown()) or not Util.CursorAction; if (LowStrata ~= Util.LowStrata or ForceUpdate) then Util.LowStrata = LowStrata; local Bars = Util.ActiveBars; if (LowStrata) then for i = 1, #Bars do Bars[i].ButtonFrame:SetFrameStrata("LOW"); Bars[i]:SetOrder(); --without a param this will cause a refresh (just in case moving the strata causes the level to change) end else for i = 1, #Bars do Bars[i].ButtonFrame:SetFrameStrata("DIALOG"); Bars[i]:SetOrder(); --without a param this will cause a refresh (just in case moving the strata causes the level to change) end end end end function Util.RefreshGridStatus(ForceUpdate) local Hide = Util.InCombat or not (Util.CursorAction or ConfigureLayer:IsShown() or DestroyBarOverlay:IsShown()); if (Hide ~= Util.GridHidden or ForceUpdate) then Util.GridHidden = Hide; local Bars = Util.ActiveBars; if (Hide) then for i = 1, #Bars do if (not Bars[i].BarSave["GridAlwaysOn"]) then Bars[i]:GridHide(); end end else for i = 1, #Bars do if (not Bars[i].BarSave["GridAlwaysOn"]) then Bars[i]:GridShow(); end end end end end function Util.RefreshBarGUIStatus() local BarGUIForceOn = (not Util.InCombat) and (ConfigureLayer:IsShown() or DestroyBarOverlay:IsShown() or (IsShiftKeyDown() and Util.CursorAction)); local Bars = Util.ActiveBars; if (BarGUIForceOn) then for i = 1, #Bars do Bars[i]:GUIOn(); end else for i = 1, #Bars do if (not Bars[i].BarSave["GUI"]) then Bars[i]:GUIOff(); end end end end function Util.SetControlFrameAlphas(Alpha) local Bars = Util.ActiveBars; for i = 1, #Bars do Bars[i].ControlFrame:SetAlpha(Alpha); end end function Util.RefreshTab(Left, Top) local Count = 0; local Bar; local StackedBar; local Label; for i = 1, #Util.ActiveBars do Bar = Util.ActiveBars[i]; if (math.abs(Bar.ControlFrame:GetLeft() - Left) < 0.01 and math.abs(Bar.ControlFrame:GetTop() - Top) < 0.01) then Count = Count + 1; StackedBar = Bar; end end if (Count == 0) then return; end if (Count == 1) then --Set it's label back in and dealloc the tabframe StackedBar.Tabbed = false; Label = StackedBar.LabelFrame; Label:ClearAllPoints(); Label:SetPoint("TOPLEFT", StackedBar.TopIconsFrame, "TOPLEFT", Const.BarInset, -Const.BarEdge); --Const.MiniIconSize + Const.MiniIconGap +Const.BarEdge, -Const.BarEdge); Label:SetBackdropColor(0, 0, 0, 1); Label:SetAlpha(1); Label:EnableMouse(false); Label:SetScript("OnMouseDown", nil); Label:SetScript("OnEnter", nil); Label:SetScript("OnLeave", nil); Util.DeallocateTab(Left, Top); StackedBar:ReflowUI(); return; end local Offset = 0; local HighestBar; local TabFrame = Util.GetTabFrame(Left, Top); for i = 1, #Util.ActiveBars do Bar = Util.ActiveBars[i]; if (math.abs(Bar.ControlFrame:GetLeft() - Left) < 0.01 and math.abs(Bar.ControlFrame:GetTop() - Top) < 0.01) then if (not HighestBar) then --anchor tabframe HighestBar = Bar; TabFrame:ClearAllPoints(); TabFrame:SetPoint("TOPLEFT", Bar.ControlFrame, "TOPLEFT", 0, Const.MiniIconSize); elseif (HighestBar.BarSave["Order"] < Bar.BarSave["Order"]) then HighestBar = Bar; end Bar.Tabbed = true; Label = Bar.LabelFrame; Label:ClearAllPoints(); Label:SetPoint("TOPLEFT", TabFrame, "TOPLEFT", Offset, 0); Label:SetBackdropColor(0, 0, 0, 1); Label:SetAlpha(.5); Label:EnableMouse(true); Label:SetScript("OnMouseDown", Bar.SendToFront); Label:SetScript("OnEnter", Bar.LabelOnEnter); Label:SetScript("OnLeave", Bar.LabelOnLeave); if (Label:GetWidth() > 4.5) then Offset = Offset + Label:GetWidth(); end Bar:ReflowUI(); end end TabFrame:SetSize(Offset, Const.MiniIconSize); Label = HighestBar.LabelFrame; Label:SetAlpha(1); --Label:SetScript("OnMouseDown", nil); Label:SetScript("OnEnter", nil); Label:SetScript("OnLeave", nil); end function Util.DeallocateTab(Left, Top) if (Util.ActiveTabs[Left.." "..Top]) then table.insert(Util.InactiveTabs, Util.ActiveTabs[Left.." "..Top]); Util.ActiveTabs[Left.." "..Top] = nil; end end function Util.GetTabFrame(Left, Top) if (not Util.ActiveTabs[Left.." "..Top]) then if (#Util.InactiveTabs > 0) then Util.ActiveTabs[Left.." "..Top] = table.remove(Util.InactiveTabs); else Util.ActiveTabs[Left.." "..Top] = CreateFrame("FRAME", nil, ConfigureLayer); Util.ActiveTabs[Left.." "..Top]:SetSize(1, 1); Util.ActiveTabs[Left.." "..Top]:SetClampedToScreen(true); end end return Util.ActiveTabs[Left.." "..Top]; end function Util.BarHasButton(Bar, Command, Data, Subvalue) local BCommand, BData, BSubvalue for i = 1, #Bar.Buttons do BCommand, BData, BSubvalue = Bar.Buttons[i]:GetCursor(); if (Command == BCommand and Data == BData and Subvalue == BSubvalue) then return true; end end return false; end --[[ Helper functions --]] function Util.FindInTable(Table, Value, Start) for i = Start or 1, #Table do if (Table[i] == Value) then return i; end end return nil; end function Util.GetLocaleString(Value) return Locale[Value]; end function Util.GetLocaleEnabledDisabled(Value) if (Value) then return Locale["Enabled"]; else return Locale["Disabled"]; end end function Util.CastBool(Value) return Locale.BoolTable[strlower(Value or '')]; end function Util.ProcessSlashCommandParams(Command, Params) Params = Params or ""; if (Const.SlashCommands[Command].params == "bool") then local Bool = Util.CastBool(Params, "^%s*(%w*)%s*$"); if (Bool == nil) then DEFAULT_CHAT_FRAME:AddMessage(Util.GetLocaleString("SlashParamsInvalid")..Command.." "..Params, .5, 1, 0, 1); return; end return {Bool}; else local Values = {string.match(Params, Const.SlashCommands[Command].params)}; if (Values[1] == nil) then DEFAULT_CHAT_FRAME:AddMessage(Util.GetLocaleString("SlashParamsInvalid")..Command.." "..Params, .5, 1, 0, 1); return; end return Values; end end --[[Unused function for allowing a more... ]]-- local SlashRemainingMessage = ''; function Util.SlashShowRemainingMessage() local Display, Remainder = string.match(SlashRemainingMessage, "^("..strrep(".-\n", Const.SlashNumLines-1)..".-)\n(.-)$"); if (Display) then SlashRemainingMessage = Remainder; else Display = SlashRemainingMessage; SlashRemainingMessage = ''; end DEFAULT_CHAT_FRAME:AddMessage(' '..Display, .5, 1, 0, 1); end function Util.SlashShowMessageByLine(Message) for Line in string.gmatch(Message, '([^\n]*)') do DEFAULT_CHAT_FRAME:AddMessage(Line, .5, 1, 0, 1); end end function SlashCmdList.BUTTONFORGE(msg, editbox) local PreparedCommands = {}; local Command, Params; local Count = 0; Params = ''; for Token, Space in string.gmatch(msg, '([^%s]+)([%s]*)') do if (Const.SlashCommands[strlower(Token)]) then if (Command) then Count = Count + 1; --PreparedCommands["Count"] = Count; PreparedCommands[Command] = Util.ProcessSlashCommandParams(Command, Params); if (PreparedCommands[Command] == nil) then return; end end Command = strlower(Token); Params = ''; elseif (string.match(Token, '^-') == '-') then DEFAULT_CHAT_FRAME:AddMessage(Util.GetLocaleString("SlashCommandNotRecognised")..Token, .5, 1, 0, 1); return; else Params = Params..Token..Space; end end if (Command) then Count = Count + 1; --PreparedCommands["Count"] = Count; PreparedCommands[Command] = Util.ProcessSlashCommandParams(Command, Params); if (PreparedCommands[Command] == nil) then return; end local Bars = Util.ActiveBars; local Commands = PreparedCommands; -- Check the constraint rules -- 1. Only 1 group is allowed -- 2. A rules required's must be present -- 3. A rules exclusions must not be present local Group; local FirstCommand; for k, v in pairs(Commands) do FirstCommand = FirstCommand or k; if (Group ~= nil and Group ~= Const.SlashCommands[k].group) then -- must be the same group DEFAULT_CHAT_FRAME:AddMessage(string.gsub(string.gsub(Util.GetLocaleString("SlashCommandIncompatible"), "", FirstCommand), "", k), .5, 1, 0, 1); return; end Group = Const.SlashCommands[k].group; local Requires = Const.SlashCommands[k].requires; if (Requires) then for k1, v1 in pairs(Requires) do if (Commands[v1] == nil) then -- Missing a required command DEFAULT_CHAT_FRAME:AddMessage(string.gsub(string.gsub(Util.GetLocaleString("SlashCommandRequired"), "", k), "", v1), .5, 1, 0, 1); return; end end end local Incompat = Const.SlashCommands[k].incompat; if (Incompat) then for k1, v1 in pairs(Incompat) do if (Commands[v1]) then -- Incompatible command present DEFAULT_CHAT_FRAME:AddMessage(string.gsub(string.gsub(Util.GetLocaleString("SlashCommandIncompatible"), "", k), "", v1), .5, 1, 0, 1); return; end if (v1 == "ALL" and Count > 1) then -- Only 1 command would be allowed DEFAULT_CHAT_FRAME:AddMessage(string.gsub(Util.GetLocaleString("SlashCommandAlone"), "", k), .5, 1, 0, 1); return; end end end local Validate = Const.SlashCommands[k].validate; if (Validate) then if (not Validate(unpack(v))) then -- Validate function failed DEFAULT_CHAT_FRAME:AddMessage(Util.GetLocaleString("SlashParamsInvalid")..k.." "..table.concat(v, " "), .5, 1, 0, 1); return; end end end if (Group == "bar") then if (Commands["-createbar"]) then Util.ApplySlashCommands(Commands); else local BarName; if (Commands["-bar"]) then BarName = Commands["-bar"][1]; elseif (Commands["-destroybar"]) then BarName = Commands["-destroybar"][1]; end for i = 1, #Bars do if ((not BarName) or strlower(BarName) == strlower(Bars[i].BarSave["Label"])) then Util.ApplySlashCommands(Commands, Bars[i]); end end end else Util.ApplySlashCommands(Commands); end else Util.SlashShowMessageByLine(Util.GetLocaleString("SlashHelpFormatted")); --]] end end function Util.ApplySlashCommands(Commands, Bar) if (Commands["-createbar"]) then Bar = Util.NewBar(0, 0); if (Bar == nil) then DEFAULT_CHAT_FRAME:AddMessage(Util.GetLocaleString("SlashCreateBarFailed"), .5, 1, 0, 1); return end Commands["-rename"] = Commands["-createbar"]; --this could arguably work by having an empty param to createbar but I think it will feel more natural to require a name with this command end if (Commands["-destroybar"]) then Util.DeallocateBar(Bar); end if (Commands["-macrotext"]) then Bar:SetMacroText(Commands["-macrotext"][1]); end if (Commands["-keybindtext"]) then Bar:SetKeyBindText(Commands["-keybindtext"][1]); end if (Commands["-tooltips"]) then Bar:SetTooltips(Commands["-tooltips"][1]); end if (Commands["-emptybuttons"]) then Bar:SetGridAlwaysOn(Commands["-emptybuttons"][1]); end if (Commands["-lockbuttons"]) then Bar:SetButtonsLocked(Commands["-lockbuttons"][1]); end if (Commands["-scale"]) then Bar:SetScale(tonumber(Commands["-scale"][1])); end if (Commands["-rows"]) then Bar:SetNumButtons(Bar.BarSave["Cols"], tonumber(Commands["-rows"][1])); end if (Commands["-cols"]) then Bar:SetNumButtons(tonumber(Commands["-cols"][1]), Bar.BarSave["Rows"]); end if (Commands["-coords"]) then Bar:SetPosition(tonumber(Commands["-coords"][1]), tonumber(Commands["-coords"][2])); end if (Commands["-rename"]) then Bar:SetLabel(Commands["-rename"][1]); end if (Commands["-hidespec1"]) then Bar:SetHSpec1(Commands["-hidespec1"][1]); end if (Commands["-hidespec2"]) then Bar:SetHSpec2(Commands["-hidespec2"][1]); end if (Commands["-hidespec3"]) then Bar:SetHSpec3(Commands["-hidespec3"][1]); end if (Commands["-hidespec4"]) then Bar:SetHSpec4(Commands["-hidespec4"][1]); end if (Commands["-hidevehicle"]) then Bar:SetHVehicle(Commands["-hidevehicle"][1]); end if (Commands["-hideoverridebar"]) then Bar:SetHBonusBar(Commands["-hideoverridebar"][1]); end if (Commands["-vismacro"]) then Bar:SetVD(Commands["-vismacro"][1]); end if (Commands["-gui"]) then Bar:SetGUI(Commands["-gui"][1]); end if (Commands["-alpha"]) then Bar:SetAlpha(tonumber(Commands["-alpha"][1])); end if (Commands["-enabled"]) then Bar:SetEnabled(Commands["-enabled"][1]); end if (Commands["-gap"]) then Bar:SetButtonGap(tonumber(Commands["-gap"][1])); end if (Commands["-info"]) then --print out the bar info's local String = Util.GetLocaleString("InfoLabel")..": "..(Bar:GetLabel() or "").."\n".. Util.GetLocaleString("InfoRowsCols")..": "..select(3, Bar:GetNumButtons()).."\n".. Util.GetLocaleString("InfoScale")..": "..Bar:GetScale().."\n".. Util.GetLocaleString("InfoGap")..": "..Bar:GetButtonGap().."\n".. Util.GetLocaleString("InfoCoords")..": "..select(3, Bar:GetPosition()).."\n".. Util.GetLocaleString("InfoTooltips")..": "..select(2, Bar:GetTooltips()).."\n".. Util.GetLocaleString("InfoEmptyGrid")..": "..select(2, Bar:GetGridAlwaysOn()).."\n".. Util.GetLocaleString("InfoLock")..": "..select(2, Bar:GetButtonsLocked()).."\n".. Util.GetLocaleString("InfoMacroText")..": "..select(2, Bar:GetMacroText()).."\n".. Util.GetLocaleString("InfoKeybindText")..": "..select(2, Bar:GetKeyBindText()).."\n".. Util.GetLocaleString("InfoHSpec1")..": "..select(2, Bar:GetHSpec1()).."\n".. Util.GetLocaleString("InfoHSpec2")..": "..select(2, Bar:GetHSpec2()).."\n".. Util.GetLocaleString("InfoHSpec3")..": "..select(2, Bar:GetHSpec3()).."\n".. Util.GetLocaleString("InfoHSpec4")..": "..select(2, Bar:GetHSpec4()).."\n".. Util.GetLocaleString("InfoHVehicle")..": "..select(2, Bar:GetHVehicle()).."\n".. Util.GetLocaleString("InfoHBonusBar5")..": "..select(2, Bar:GetHBonusBar()).."\n".. Util.GetLocaleString("InfoVisibilityMacro")..": "..(Bar:GetVD() or "").."\n".. Util.GetLocaleString("InfoGUI")..": "..select(2, Bar:GetGUI()).."\n".. Util.GetLocaleString("InfoAlpha")..": "..Bar:GetAlpha().."\n".. Util.GetLocaleString("InfoEnabled")..": "..select(2, Bar:GetEnabled()); Util.SlashShowMessageByLine(String); end if (Commands["-technicalinfo"]) then --print out technical info for the bar local String = Util.GetLocaleString("InfoButtonFrameName")..": "..Bar.ButtonFrame:GetName(); Util.SlashShowMessageByLine(String); end if (Commands["-saveprofile"]) then Util.SaveProfile(Commands["-saveprofile"][1]); end if (Commands["-loadprofile"]) then Util.LoadProfile(Commands["-loadprofile"][1]); end if (Commands["-loadprofiletemplate"]) then Util.LoadProfileTemplate(Commands["-loadprofiletemplate"][1]); end if (Commands["-undoprofile"]) then Util.UndoProfile(); end if (Commands["-listprofiles"]) then local String = Util.GetLocaleString("BFProfiles").."\n---------------------\n"; for k,v in pairs(ButtonForgeGlobalProfiles) do String = String..v.Name.."\n"; end String = String.."---------------------\n"; Util.SlashShowMessageByLine(String); end if (Commands["-deleteprofile"]) then Util.DeleteProfile(Commands["-deleteprofile"][1]); end if (Commands["-macrocheckdelay"]) then ButtonForgeGlobalSettings["MacroCheckDelay"] = tonumber(Commands["-macrocheckdelay"][1]); end if (Commands["-removemissingmacros"]) then ButtonForgeGlobalSettings["RemoveMissingMacros"] = Commands["-removemissingmacros"][1]; end if (Commands["-forceoffcastonkeydown"]) then ButtonForgeGlobalSettings["ForceOffCastOnKeyDown"] = Commands["-forceoffcastonkeydown"][1]; end if (Commands["-usecollectionsfavoritemountbutton"]) then ButtonForgeGlobalSettings["UseCollectionsFavoriteMountButton"] = Commands["-usecollectionsfavoritemountbutton"][1]; end if (Commands["-globalsettings"]) then --print out what the global settings for Button Forge are local String = Util.GetLocaleString("InfoMacroCheckDelay")..": "..ButtonForgeGlobalSettings["MacroCheckDelay"].."\n".. Util.GetLocaleString("InfoRemoveMissingMacros")..": "..Util.GetLocaleEnabledDisabled(ButtonForgeGlobalSettings["RemoveMissingMacros"]).."\n".. Util.GetLocaleString("InfoForceOffCastOnKeyDown")..": "..Util.GetLocaleEnabledDisabled(ButtonForgeGlobalSettings["ForceOffCastOnKeyDown"]).."\n".. Util.GetLocaleString("InfoUseCollectionsFavoriteMountButton")..": "..Util.GetLocaleEnabledDisabled(ButtonForgeGlobalSettings["UseCollectionsFavoriteMountButton"]); Util.SlashShowMessageByLine(String); end end --[[ Store cursor type info for later use --]] function Util.StoreCursor(Command, Data, Subvalue, Subsubvalue) Util.Command = Command; Util.Data = Data; Util.Subvalue = Subvalue; Util.Subsubvalue = Subsubvalue end function Util.GetStoredCursor() return Util.Command, Util.Data, Util.Subvalue, Util.Subsubvalue; end --[[ Set the cursor based on the triplet passed in --]] function Util.SetCursor(Command, Data, Subvalue, Subsubvalue) ClearCursor(); UILib.StopDraggingIcon(); SpellFlyout:Hide(); if (Command == "spell") then PickupSpell(Subsubvalue); elseif (Command == "item") then PickupItem(Data); elseif (Command == "macro") then PickupMacro(Data); elseif (Command == "mount") then --if (Subvalue == nil) then -- Data = Util.GetMountIndexFromUselessIndex(Data); --end C_MountJournal.Pickup(Util.GetMountIndexFromMountID(Data)); elseif (Command == "equipmentset") then local SetCount = C_EquipmentSet.GetNumEquipmentSets(); for i=0,SetCount-1 do name, _, setIndex = C_EquipmentSet.GetEquipmentSetInfo(i); if (name == Data) then C_EquipmentSet.PickupEquipmentSet(setIndex); break; end end; elseif (Command == "bonusaction") then local page = 12; --The page for vehicleactionbar if (HasOverrideActionBar()) then page = 14; end local Texture = GetActionTexture(Data + ((page - 1) * 12)); if (Texture and (HasOverrideActionBar() or HasVehicleActionBar())) then UILib.StartDraggingIcon(Texture, 23, 23, "bonusaction", Data); else UILib.StartDraggingIcon(Const.ImagesDir.."Bonus"..Data, 23, 23, "bonusaction", Data); end elseif (Command == "flyout") then local ind, booktype = Util.LookupSpellIndex("FLYOUT"..Data); if (ind) then PickupSpellBookItem(ind, booktype); end elseif (Command == "customaction") then CustomAction.SetCursor(Data); end end --[[ These two functions will take care of non secure gui updates when the player enters or exits combat ]]-- function Util.PreCombatStateUpdate() Util.InCombat = true; Util.RefreshGridStatus(); Util.RefreshBarStrata(); --Im surprised that these refreshes dont lead to taint, I must be forgetting something??! UILib.ToggleCreateBarMode(true); UILib.ToggleDestroyBarMode(true); Util.RefreshBarGUIStatus(); end function Util.PostCombatStateUpdate() Util.InCombat = false; Util.VDriverOverride(); Util.RefreshGridStatus(); Util.RefreshBarStrata(); Util.RefreshBarGUIStatus(); if (Util.DelayedRefreshMacros) then Util.RefreshMacros(); Util.DelayedRefreshMacros = nil; end if (Util.DelayedUpdateButtonClickHandling) then Util.UpdateButtonClickHandling(); Util.DelayedUpdateButtonClickHandling = nil; end if (Util.DelayedPromoteSpells) then Util.PromoteSpells(); Util.DelayedPromoteSpells = nil; end if (Util.DelayedRefreshCompanions) then Util.RefreshCompanions(); Util.DelayedRefreshCompanions = nil; end if (Util.DelayedRefreshEquipmentSets) then Util.RefreshEquipmentSets(); Util.DelayedRefreshEquipmentSets = nil; end end --[[ Add and remove buttons to a list that gets updated for the range timer --]] function Util.AddToRangeTimer(Value) if (#Util.RangeTimerButtons == 0) then --BF end Util.RangeTimerButtons[Value] = true; end function Util.RemoveFromRangeTimer(Value) Util.RangeTimerButtons[Value] = nil; end --[[ Add and remove buttons to a list that gets updated for flashing --]] function Util.AddToFlash(Value) if (#Util.FlashButtons == 0) then --BF end Util.FlashButtons[Value] = true; end function Util.RemoveFromFlash(Value) Util.FlashButtons[Value] = nil; end function Util.RightClickSelfCast(Value) if (InCombatLockdown()) then return; end if (Value) then for i = 1, #Util.ActiveButtons do Util.ActiveButtons[i].Widget:SetAttribute("unit2", "player"); end else for i = 1, #Util.ActiveButtons do Util.ActiveButtons[i].Widget:SetAttribute("unit2", nil); end end ButtonForgeSave["RightClickSelfCast"] = Value; end --[[--------------------------------------- Spell Functions -------------------------------------------]] function Util.GetFullSpellName(Name, Rank) --BFA fix: GetSpellInfo now returns a nil for the rank. That's passed in here --So we check to make sure ranx exists or only pass back the name itself. if (Rank) then Rank = "("..Rank..")"; else Rank = ""; end if (Name) then return Name..Rank; end end function Util.GetSpellId(NameRank) local Link = GetSpellLink(NameRank); return select(3, strfind(Link, "spell:(%d+)|")); end function Util.IsSpellIdTalent(SpellId) local TalentInfoFuncs = {GetTalentInfo, GetPvpTalentInfo}; -- Scan both normal and PvP talents -- Note rather than assume number of talents, we just scan till the rows and columns till we hit a nil for _, TalentInfoFunc in ipairs(TalentInfoFuncs) do local r = 1; local c = 1; local TalentSpellID = select(6, TalentInfoFunc(r, c, 1)); while (TalentSpellID) do while (TalentSpellID) do if (TalentSpellID == SpellId) then return true; end c = c + 1; TalentSpellID = select(6, TalentInfoFunc(r, c, 1)); end r = r + 1; c = 1; TalentSpellID = select(6, TalentInfoFunc(r, c, 1)); end end return false; end function Util.CacheSpellIndexes() local i = 1; local NewSI = {}; local NewSM = {-10000000, 10000000}; local ManaPoints = {}; local ItemType, Id; Util.NewSpellIndex = {}; local total = 0; for j = 1, GetNumSpellTabs() do total = total + select(4, GetSpellTabInfo(j)); end for i = total, 1, -1 do ItemType, Id = GetSpellBookItemInfo(i, BOOKTYPE_SPELL); --local Name, Rank, Icon, PowerCost, IsFunnel, PowerType = GetSpellInfo(i, BOOKTYPE_SPELL); local Name, Rank, Icon, PowerCost, IsFunnel, PowerType = GetSpellInfo(Id); local NameRank = Util.GetFullSpellName(Name, Rank); if (ItemType == "SPELL") then NewSI[NameRank] = i; elseif (ItemType == "FLYOUT") then NewSI["FLYOUT"..Id] = i; elseif (ItemType == nil) then break; end -- if (not Util.SpellIndex[NameRank]) then -- Util.NewSpellIndex[NameRank] = i; --end if (PowerType == 0 and not ManaPoints[Power]) then ManaPoints[PowerCost] = true; table.insert(NewSM, PowerCost); end end -- local total = 0; --for j = 1, GetNumSpellTabs() do -- total = total + select(4, GetSpellTabInfo(j)); --end --print("Calc total = "..total, "actual total = "..i); Util.SpellIndex = NewSI; table.sort(NewSM); Util.SpellMana = NewSM; end function Util.CachePetSpellIndexes() local i = 1; local NewPSI = {}; --Util.NewPetSpellIndex = {}; while true do local NameRank = Util.GetFullSpellName(GetSpellInfo(i, BOOKTYPE_PET)); if (not NameRank) then break; end --if (not Util.PetSpellIndex[NameRank]) then -- Util.NewPetSpellIndex[NameRanl] = i; --end NewPSI[NameRank] = i; i = i + 1; end Util.PetSpellIndex = NewPSI; end function Util.LookupSpellIndex(NameRank) local Index = Util.SpellIndex[NameRank]; if (Index) then return Index, BOOKTYPE_SPELL; end Index = Util.PetSpellIndex[NameRank]; if (Index) then return Index, BOOKTYPE_PET; end end function Util.LookupNewSpellIndex(NameRank) local Index = Util.NewSpellIndex[NameRank]; if (Index) then return Index, BOOKTYPE_SPELL; end Index = Util.NewPetSpellIndex[NameRank]; if (Index) then return Index, BOOKTYPE_PET; end end --[[ Used when the players mana crosses a threshold to find the next thresholds to test against --]] function Util.FindNewThresholds(Mana, Index, SearchDown) local SpellMana = Util.SpellMana; if (SearchDown) then for i = Index-1, 1, -1 do if (SpellMana[i] <= Mana) then return SpellMana[i], SpellMana[i+1], i; end end else for i = Index+2, #SpellMana do if (SpellMana[i] > Mana) then return SpellMana[i-1], SpellMana[i], i-1; end end end end --[[ I will probably need to do the above for Rage/Energy and Runic Power, but for now will skip such tests --]] --[[ If a spell is learnt this will promote any usage of that spell to it's highest rank --]] function Util.PromoteSpells() if (InCombatLockdown()) then Util.DelayedPromoteSpells = true; --Since this relies on the NewSpell table this may not work too well? (ultimately this is minor and perhaps not worth the extra code paths to manage) return; end return; --for k, v in pairs(Util.ActiveButtons) do -- v:PromoteSpell(); --end end function Util.RefreshSpells() --Unlike the others this can be done during combat (ironically) for k, v in pairs(Util.ActiveButtons) do v:RefreshSpell(); end end function Util.AddSpell(Value) if (not Util.FindInTable(Util.ActiveSpells, Value)) then table.insert(Util.ActiveSpells, Value); end end function Util.RemoveSpell(Value) local Index = Util.FindInTable(Util.ActiveSpells, Value); if (Index) then table.remove(Util.ActiveSpells, Index); end end --[[--------------------------------------- Companion Functions -------------------------------------------]] function Util.CacheCompanions() Util.Critters = {}; --[[ for i = 1, GetNumCompanions("CRITTER") do local Id, Name = GetCompanionInfo("CRITTER", i); if (not Name) then return; end Util.Critters[Name] = i; end]] Util.Mounts = {}; for i, mountID in pairs(C_MountJournal.GetMountIDs()) do local creatureName, spellID = C_MountJournal.GetMountInfoByID(mountID); if (not creatureName) then return; end Util.Mounts[spellID] = mountID; end Util.CompanionsCached = true; end function Util.LookupCompanion(Name) if (Util.Critters[Name]) then return "CRITTER", Util.Critters[Name]; elseif (Util.Mounts[Name]) then return "MOUNT", Util.Mounts[Name]; else return nil, nil; end end function Util.RefreshCompanions() if (InCombatLockdown()) then Util.DelayedRefreshCompanions = true; return; end for k, v in pairs(Util.ActiveButtons) do v:RefreshCompanion(); end end --[[--------------------------------------- Item Functions -------------------------------------------]] function Util.GetItemId(Name) --return select(3, strfind(Link, "item:(%d+)|")); return Util.InvItemNameId[Name] or Util.BagItemNameId[Name]; end function Util.CacheBagItems() local BagItemNameIndexes = {}; local BagItemIdIndexes = {}; local BagItemNameId = {}; local ItemId; local ItemName; for b = 4, 0, -1 do for s = GetContainerNumSlots(b), 1, -1 do ItemId = GetContainerItemID(b, s); ItemName = GetItemInfo(ItemId or ""); if (ItemName ~= nil and ItemName ~= "") then BagItemNameIndexes[ItemName] = {b, s}; BagItemIdIndexes[ItemId] = {b, s}; BagItemNameId[ItemName] = ItemId; end end end Util.BagItemNameIndexes = BagItemNameIndexes; Util.BagItemIdIndexes = BagItemIdIndexes; Util.BagItemNameId = BagItemNameId; end function Util.CacheInvItems() local InvItemNameIndexes = {}; local InvItemIdIndexes = {}; local InvItemNameId = {}; local ItemId; local ItemName; for s = 32, 0, -1 do ItemId = GetInventoryItemID("player", s); ItemName = GetItemInfo(ItemId or ""); if (ItemName ~= nil and ItemName ~= "") then InvItemNameIndexes[ItemName] = s; InvItemIdIndexes[ItemId] = s; InvItemNameId[ItemName] = ItemId; end end Util.InvItemNameIndexes = InvItemNameIndexes; Util.InvItemIdIndexes = InvItemIdIndexes; Util.InvItemNameId = InvItemNameId; end --[[ Look for the item in players equipped slots --]] function Util.LookupItemNameEquippedSlot(ItemName) return Util.InvItemNameIndexes[ItemName]; end function Util.LookupItemIdEquippedSlot(ItemId) return Util.InvItemIdIndexes[ItemId]; end --[[ Look for the item in the players inventory --]] function Util.LookupItemNameBagSlot(ItemName) local Result = Util.BagItemNameIndexes[ItemName]; if (Result) then return Result[1], Result[2]; else return nil, nil; end end function Util.LookupItemIdBagSlot(ItemId) local Result = Util.BagItemIdIndexes[ItemId]; if (Result) then return Result[1], Result[2]; else return nil, nil; end end --[[ Look for the item in players equipped slots --]] function Util.LookupItemEquippedSlot(ItemId) for s = 0,23 do local Id = GetInventoryItemID("player", s); if (ItemId == Id) then return s; end end return nil; end --[[ Look for the item in the players inventory Notes: Don't use this function, the above functions are better Reason: In the simple case profiling shows that the performance hit is neglible from doing this scan... That is until GetItemInfo is used to get the name of the item all other things being equal that call increases my perf time from 0.04ms (approx 100 bag slots, with half used) to 0.44ms The above caching mechanism requires more work of the addon (not a good thing, complexity breeds issues) but it avoids the whole perf issue and doesn't even register (i.e. 0.00ms) --]] function Util.LookupItemInvSlot(ItemId) local Id; local Name = ""; for b = 0, 4 do for s = 1, GetContainerNumSlots(b) do Id = GetContainerItemID(b, s); if (Id) then Name = GetItemInfo(Id); if (ItemId == Id) then return b, s; end end end end return nil, nil; end --[[ Look the item in the players bank (not sure if I need to make such a function so will leave this stub for now --]] function Util.LookupItemBankSlot(ItemName) return nil, nil; end function Util.AddItem(Value) if (not Util.FindInTable(Util.ActiveItems, Value)) then table.insert(Util.ActiveItems, Value); end end function Util.RemoveItem(Value) local Index = Util.FindInTable(Util.ActiveItems, Value); if (Index) then table.remove(Util.ActiveItems, Index); end end --[[--------------------------------------- EquipmentSet Functions -------------------------------------------]] function Util.RefreshEquipmentSets() if (InCombatLockdown()) then Util.DelayedRefreshEquipmentSets = true; return; end for k, v in pairs(Util.ActiveButtons) do v:RefreshEquipmentSet(); end end --[[--------------------------------------- Bonus Action Functions -------------------------------------------]] function Util.AddBonusAction(Value) if (not Util.FindInTable(Util.ActiveBonusActions, Value)) then table.insert(Util.ActiveBonusActions, Value); end end function Util.RemoveBonusAction(Value) local Index = Util.FindInTable(Util.ActiveBonusActions, Value); if (Index) then table.remove(Util.ActiveBonusActions, Index); end end function Util.UpdateButtonClickHandling() if (InCombatLockdown() or not Util.Loaded) then Util.DelayedUpdateButtonClickHandling = true; return; end for i = 1, #Util.ActiveButtons do Util.ActiveButtons[i]:SetupActionButtonClick(); end for i = 1, #Util.InactiveButtons do Util.InactiveButtons[i]:SetupActionButtonClick(); end end --[[--------------------------------------- Macro Functions -------------------------------------------]] function Util.Trim5(S) return strmatch(S or '', '^%s*(.*%S)') or ''; end function Util.IncBetween(Val, Low, High) return Val >= Low and Val <= High; end function Util.RefreshMacros() if (InCombatLockdown() or not Util.Loaded) then Util.DelayedRefreshMacros = true; return; end if (Util.UpdateMacroEventCount < 2) then --Not all macros have been loaded yet so don't refresh return; end local AccMacros, CharMacros = GetNumMacros(); if (not Util.MacroCount) then Util.MacroCount = AccMacros + CharMacros; elseif (Util.MacroCount > AccMacros + CharMacros) then Util.MacroDeleted = true; end for i = 1, #Util.ActiveButtons do Util.ActiveButtons[i]:RefreshMacro(); end Util.MacroDeleted = false; Util.MacroCount = AccMacros + CharMacros; end function Util.AddMacro(Value) if (not Util.FindInTable(Util.ActiveMacros, Value)) then table.insert(Util.ActiveMacros, Value); end Util.RefreshOnUpdateFunction(); end function Util.RemoveMacro(Value) local Index = Util.FindInTable(Util.ActiveMacros, Value); if (Index) then table.remove(Util.ActiveMacros, Index); Util.RefreshOnUpdateFunction(); end end --[[ Monitor the macro check delay --]] function Util.StartMacroCheckDelay() Delay:SetScript("OnUpdate", Delay.OnUpdate); end function Util.StopMacroCheckDelay() Delay:SetScript("OnUpdate", nil); Util.MacroCheckDelayComplete = true; Util.RefreshMacros(); end --[[ The following creates an OnUpdate function designed to scan for macro conditionals that can't adequately be covered by events alone - it will only perform processing for conditionals that actually exist in allocated macros --]] function Util.RefreshOnUpdateFunction() if (not Util.Loaded) then return; end local ConcatMacros = ""; local FunctionString = [[return function (self, Elapsed) ]]; for i = 1, #(Util.ActiveMacros) do if (Util.ActiveMacros[i].Mode == "macro") then ConcatMacros = ConcatMacros..":"..(GetMacroBody(Util.ActiveMacros[i].MacroIndex) or ""); end end ConcatMacros = strupper(ConcatMacros); --The following tests should be performed the buttons are updated if (strfind(ConcatMacros, "FLYING", 1, true)) then FunctionString = FunctionString..Util.SnippetIsFlying(); end if (strfind(ConcatMacros, "MOUNTED", 1, true)) then FunctionString = FunctionString..Util.SnippetIsMounted(); end FunctionString = FunctionString..Util.SnippetRefreshButtons(); --The following tests need to be performed after the buttons are updated (so that the buttons can be updated at the next onupdate) if (strfind(ConcatMacros, "FLYABLE", 1, true)) then FunctionString = FunctionString..Util.SnippetIsFlyable(); end if (strfind(ConcatMacros, "MOUSEOVER", 1, true)) then FunctionString = FunctionString..Util.SnippetMouseOver(); end FunctionString = FunctionString.."end"; local Func = assert(loadstring(FunctionString, "ButtonForgeOnUpdate")); Util.OnUpdate = Func(); EventFull:SetScript("OnUpdate", Util.OnUpdate); end function Util.SnippetRefreshButtons() return [[if (self.RefreshButtons) then local ActiveButtons = self.Util.ActiveButtons; for i = 1, #ActiveButtons do ActiveButtons[i]:UpdateTexture(); --make sure the texture is always upto date (most actions wont need to do anything here, really this is just for spellwisp) end if (self.RefChecked) then for i = 1, #ActiveButtons do ActiveButtons[i]:UpdateChecked(); end end if (self.RefEquipped) then for i = 1, #ActiveButtons do ActiveButtons[i]:UpdateEquipped(); end end if (self.RefUsable) then --print("Usable"); for i = 1, #ActiveButtons do ActiveButtons[i]:UpdateUsable(); end end if (self.RefCooldown) then for i = 1, #ActiveButtons do ActiveButtons[i]:UpdateCooldown(); end end if (self.RefText) then for i = 1, #ActiveButtons do ActiveButtons[i]:UpdateTextCount(); end end if (self.RefFlyouts) then for i = 1, #ActiveButtons do ActiveButtons[i]:UpdateFlyout(); end end if (self.RefGlow) then for i = 1, #ActiveButtons do ActiveButtons[i]:UpdateGlow(); end end if (self.RefConditional) then local ActiveMacros = self.Util.ActiveMacros; for i = 1, #ActiveMacros do ActiveMacros[i]:TranslateMacro(); end end self.RefreshButtons = false; self.RefFull = false; self.RefChecked = false; self.RefEquipped = false; self.RefUsable = false; self.RefCooldown = false; self.RefText = false; self.RefFlyouts = false; self.RefGlow = false; self.RefConditional = false; end ]]; end function Util.SnippetMouseOver() return [[if (UnitName("mouseover") ~= self.MOUnit or UnitIsDead("mouseover") ~= self.MOUnitDead) then self.MOUnit = UnitName("mouseover"); self.MOUnitDead = UnitIsDead("mouseover"); self.RefreshButtons = true; self.RefConditional = true; end ]]; end function Util.SnippetIsFlying() return [[if (IsFlying() ~= self.IsFlying) then self.IsFlying = IsFlying(); self.RefreshButtons = true; self.RefConditional = true; end ]]; end function Util.SnippetIsMounted() return [[if (IsMounted() ~= self.IsMounted) then self.IsMounted = IsMounted(); self.RefreshButtons = true; self.RefConditional = true; end ]]; end function Util.SnippetIsFlyable() return [[if (IsFlyableArea() ~= self.IsFlyableArea) then self.IsFlyableArea = IsFlyableArea(); self.RefreshButtons = true; self.RefConditional = true; end ]]; end --[[ API1 Support Functions --]] function Util.RegisterCallback(Callback, Arg) table.insert(Util.CallbackFunctions, Callback); table.insert(Util.CallbackArgs, Arg); end function Util.UnregisterCallback(Callback, Arg) for i = #Util.CallbackFunctions, 1, -1 do if (Util.CallbackFunctions[i] == Callback and Util.CallbackArgs[i] == Arg) then table.remove(Util.CallbackFunctions, i); table.remove(Util.CallbackArgs, i); end end end local CallbackFunc; local CallbackArg; local CallbackEvent; local CallbackEventArgs; function Util.CallbackWrapper() CallbackFunc(CallbackArg, CallbackEvent, unpack(CallbackEventArgs)); end function Util.CallbackEvent(Event, ...) CallbackEvent = Event; CallbackEventArgs = {...}; --local Args = {...}; for i = 1, #Util.CallbackFunctions do CallbackFunc = Util.CallbackFunctions[i]; CallbackArg = Util.CallbackArgs[i]; xpcall(Util.CallbackWrapper, geterrorhandler()); --xpcall(function () Util.CallbackFunctions[i](Util.CallbackArgs[i], Event, unpack(Args)); end, geterrorhandler()); --The other way provides a little more context if an error occurs end end --This one breaks with the philosophy of the Button implementation but for now should be sufficient to support the API function function Util.GetButtonActionInfo(ButtonName) local Button = Util.ButtonWidgetMap[_G[ButtonName]]; if (not Button) then return; end if (Button.Mode == "spell") then return "spell", Button.SpellId, Button.SpellBook; elseif (Button.Mode == "item") then return "item", Button.ItemId; elseif (Button.Mode == "macro") then return "macro", Button.MacroIndex; elseif (Button.Mode == "companion") then local spellid = select(3, GetCompanionInfo(Button.CompanionType, Button.CompanionIndex)); return "companion", spellid, Button.CompanionType; elseif (Button.Mode == "equipmentset") then return "equipmentset", Button.EquipmentSetName; elseif (Button.Mode == "flyout") then return "flyout", Button.FlyoutId; end end --This one breaks with the philosophy of the Button implementation but for now should be sufficient to support the API function function Util.GetButtonActionInfo2(ButtonName) local Button = Util.ButtonWidgetMap[_G[ButtonName]]; if (not Button) then return; end --[[ "spell", SpellName, SpellSubName, SpellId, SpellIndex, SpellBook "item", ItemId, ItemName "macro", MacroIndex "companion", CompanionType, CompanionIndex "equipmentset", Name "flyout", FlyoutId "bonusaction", BonusActionSlot "customaction", CustomActionName --]] if (Button.Mode == "spell") then local Rank = select(2, GetSpellInfo(Button.SpellId)); return "spell", Button.SpellName, Rank, Button.SpellId, Util.LookupSpellIndex(Button.SpellNameRank), Button.SpellBook; elseif (Button.Mode == "item") then return "item", Button.ItemId, Button.ItemName; elseif (Button.Mode == "macro") then return "macro", Button.MacroIndex; elseif (Button.Mode == "companion") then return "companion", Button.CompanionType, Button.CompanionIndex; elseif (Button.Mode == "equipmentset") then return "equipmentset", Button.EquipmentSetName; elseif (Button.Mode == "flyout") then return "flyout", Button.FlyoutId; elseif (Button.Mode == "bonusaction") then return "bonusaction", Button.BonusActionSlot; elseif (Button.Mode == "customaction") then return "customaction", Button.CustomActionName; end end --[[------------------------------------------------ Get Correct Mount Index The Hack: hooksecurefunc C_MountJournal.Pickup GameTooltip:SetAction Both these functions offer a moment when both the UselessIndex and the actual Index or SpellID for a mount is available... Also in theory one of these will have to fire before the player can actually put a mount on the cursor - so we simply patch work build a map of these Useless Index to useful index mappings. It does rely on the useless index not changing during a session - i suspect it wont, but it might when a new mount is learned, something that is hard to test on my account these days --------------------------------------------------]] --[[ should no longer be needed function Util.HookSecureFunc_C_MountJournal_Pickup(Index) local UselessIndex = select(2, GetCursorInfo()); if (Index and UselessIndex) then Util.MountUselessIndexToIndex[select(2, GetCursorInfo())] = Index; end end hooksecurefunc(C_MountJournal, "Pickup", Util.HookSecureFunc_C_MountJournal_Pickup); function Util.HookSecureFunc_GameTooltip_SetAction(_, Slot) if (Slot == nil or Slot < 1 or Slot > 1000) then return; end local Command, UselessIndex = GetActionInfo(Slot); if (Command == "summonmount") then if (Util.MountUselessIndexToIndex[UselessIndex] == nil) then Util.MountUselessIndexToIndex[UselessIndex] = Util.GetMountIndexFromSpellID(select(3, GameTooltip:GetSpell())); end end end hooksecurefunc(GameTooltip, "SetAction", Util.HookSecureFunc_GameTooltip_SetAction); function Util.GetMountIndexFromUselessIndex(Index) return Util.MountUselessIndexToIndex[Index]; end ]] --[[------------------------------------------------ GetCorrectMountIndex --------------------------------------------------]] --[[ function Util.GetMountIndexFromSpellID(SpellID) local Num = C_MountJournal.GetNumMounts(); if (SpellID == Const.SUMMON_RANDOM_FAVORITE_MOUNT_SPELL) then return 0; -- This is summon favorite end for i = 1, Num do if (select(2, C_MountJournal.GetDisplayedMountInfo(i)) == SpellID) then return i; end end return nil; end ]] --[[------------------------------------------------ --------------------------------------------------]] function Util.GetMountIDFromName(Name) local Num = C_MountJournal.GetNumMounts(); for i = 1, Num do if (C_MountJournal.GetDisplayedMountInfo(i) == Name) then return select(12, C_MountJournal.GetDisplayedMountInfo(i)); end end return nil; end function Util.GetMountIndexFromMountID(MountID) local Num = C_MountJournal.GetNumMounts(); if (MountID == Const.SUMMON_RANDOM_FAVORITE_MOUNT_ID) then return 0; end for i = 1, Num do if (select(12, C_MountJournal.GetDisplayedMountInfo(i)) == MountID) then return i; end end end function Util.UpdateButtonsCooldownSwipeBling() local ActiveButtons = Util.ActiveButtons; for i = 1, #ActiveButtons do local Alpha = ActiveButtons[i].WCooldown:GetEffectiveAlpha(); ActiveButtons[i].WCooldown:SetDrawBling(Alpha == 1); ActiveButtons[i].WCooldown:SetSwipeColor(0,0,0,Alpha); end end --[[ Copied from Bliz implementation The difference is I use the effective alpha to override the bling and edge settings, and also adjust the swipe alpha I suspect this will need fine tuning for Masque and perhaps OmniCC, but that can be down the track ]] function Util.CooldownFrame_SetTimer(self, start, duration, enable, charges, maxCharges, forceShowDrawEdge) if(enable) then if (enable ~= 0) then local drawEdge = false; if ( duration > 2 and charges and maxCharges and charges ~= 0) then drawEdge = true; end local Alpha = self:GetEffectiveAlpha(); self:SetSwipeColor(0, 0, 0, Alpha); -- eventually I may need to make this obey the current color!!! if (Alpha > 0.4) then self:SetDrawEdge(drawEdge or forceShowDrawEdge); self:SetDrawBling(true); else self:SetDrawEdge(false); self:SetDrawBling(false); end self:SetDrawSwipe(not drawEdge); self:SetCooldown(start, duration); else self:SetCooldown(0, 0); end end end function Util.LookupEquipmentSetIndex(EquipmentSetID) local Total = C_EquipmentSet.GetNumEquipmentSets(); for i = 0, Total-1 do if (select(3, C_EquipmentSet.GetEquipmentSetInfo(i)) == EquipmentSetID) then return i; end end return nil; end