WoW:Changing a button's action slot ID
Sometimes you would like to control the action a button will start when it is clicked. Maybe you want to add extra buttons, maybe you just want to control the behavior of existing ones. I will tell you in a moment how to do that, but first you should know something about how the default UI handles it's buttons.
Button Handling in the Default UI
World of Warcraft supplies the player with 6 actionbars, each having 12 buttons. Some classes like warriors, rogues and druids have a few more bars due to their stances. Ever noticed the lower left bar changes it's actions when you go in stealth or in battle stance? When changing stances, there are no buttons hidden or shown. Whatever stance you are in, the buttons remain tight at their position. What's actually changing are the action slot IDs of the buttons. You can read a bit more about this here: API_TYPE_ActionSlot. Two buttons having the same action ID means they are twins. If you drag an action onto one of them, the other one will adopt this action, too. This is because the potions, spells, abilities, macros, etc. on your buttons are not bound to the button itself, but to it's action ID.
So, how does a button know it's action ID? Let's look at Blizzard's ActionButton.lua, which you can obtain by download the Blizzard Interface Customization Tools here: http://www.blizzard.com/support/wow/?id=aww01669p (after installing this, you can find ActionButton.lua in "/Blizzard Interface Data/FrameXML"). The functions in this file handle the behaviour of (guess what?) buttons. But not of any buttons. The methods their are used by the action buttons on your bars.
In the ActionButton.lua file, everytime a function (i.e. API_UseAction) needs to know the action ID of a specific button, it calls
id = ActionButton_GetPagedID(button)
I wouldn't recommend looking at the code of this method, because your brain may explode (unless you are good at reading uncommented and foreign code in lua), but I will summarize the method for you. Every button has an ID, specified in the corresponding XML File:
<Button name="SomeName" ...someOtherValues... id="2"> ...some Code </Button>
This ID can be obtained by calling button:GetID(). Unfortunately, this ID ranges only from 1-12, so the GetPagedID method can't just return this id, or otherwise, we would have only twelve different actions for our disposal (the other 60 something buttons would be twins, triplets, and so on of the first 12, remember?). Don't ask me why Blizzard did this. To distinguish more than twelve buttons, getPagedID has to add an offset, according to the bar the button is on, to the action ID. That's not very convenient if we want to create our own buttons (i.e. to use these idle action IDs 109-120), as our buttons won't have an action bar. We couldn't call GetPagedID(button) to retrieve our button's ID.
Controlling a Button's Action Slot ID
So, what can we do about this? Right, we can hook the ActionButton_GetPagedID function and replace it with one of our functions:
oldGetPagedID = ActionButton_GetPagedID ActionButton_GetPagedID = newGetPagedID
To create our own button, we can define it in an XML-File and let it inherit it's traits from Blizzard's ActionBarButtonTemplate:
<CheckButton name="customButton" inherits="ActionBarButtonTemplate"> ... sizing, positioning, etc. </CheckButton>
Note that we don't have to define an ID in the XML tag. If you're wondering why I used CheckButton and not Button here, "CheckButton" is the frame type of all the default action buttons. This is because of channeled spells, autoattack actions, etc, where the yellow border around the icon stays while the action is active. With a simple "Button", you can't do that, because upon using the action, this border just flashes once and the button returns to it's former appearance. A checkbutton will stay like this, until he is told "Okay, the action's done, you can relax and be normal again". Now you should give your custom buttons some flag to indicate that they are yours, and the desired action slot id. You can do this anywhere in your code, as long as it is before the first call to ActionButton_GetPagedID. I would recommend doing it in the OnLoad script handler:
<OnLoad> button.isCustomButton = 1 button.customID = 103 ActionButton_OnLoad() </OnLoad>
this is to determine whether the function newGetPagedID should return the customID or the return value of the oldGetPagedID function. The call to ActionButton_OnLoad() is there because defining a new OnLoad tag will overwrite the inherited one. If you don't put this call there, your button will behave strangely. All we have to do now is define the function newGetPagedID:
function newGetPagedID(button)
if(button.isCustomButton) then
return button.customID
end
return oldGetPagedID(button)
end
Now we piggybacked the default UI's functions for managing buttons! Our custom button is treated just like a standard one, except that we can control it's action slot ID.