WoW:Hooking functions: Difference between revisions
(→How to Hook a Function: Better example, imo.) |
(→How to Hook a Function: Added a note that the level showing no longer works and is just an example) |
||
Line 35: | Line 35: | ||
So in this function, we first invoke the old function to let it do what it needs to do. Next, un-hide the level and hide the skull. Pretty simple right? | So in this function, we first invoke the old function to let it do what it needs to do. Next, un-hide the level and hide the skull. Pretty simple right? | ||
'' This is only an example for how to hook, it will not actually show you the level anymore. '' -- [[User:Shadowed|Shadowed]] | |||
== An Easier Way to Hook a Function? == | == An Easier Way to Hook a Function? == |
Revision as of 18:41, 16 July 2006
What a Function Is
In LUA, a function is simply a variable. From the LUA documentation:
The statement function f () ... end translates to f = function () ... end
That means that any function can be replaced by any other function via simple assignment. With this in mind, it becomes quite easy to "Hook", or attach your own function into a predefined function.
How to Hook a Function
Let's say we wanted to show the target's level instead of the skull for players and monsters much higher level than us. The function responsible for hiding the level is TargetFrame_CheckLevel(), so we need to hook that function to make it un-hide the level.
Let's assume our AddOn is named "MyAddOn", and that is has an OnLoad handler that gets called from its XML <OnLoad> event. In the Lua file we would have:
local MyAddOn_Orig_TargetFrame_CheckLevel; function MyAddOn_OnLoad() MyAddOn_Orig_TargetFrame_CheckLevel = TargetFrame_CheckLevel; -- Remember old function TargetFrame_CheckLevel = MyAddOn_TargetFrame_CheckLevel; -- Hook in ours end
So what the above does is that it remembers the reference to the original "TargetFrame_CheckLevel" in "MyAddOn_Orig_TargetFrame_CheckLevel". Then it puts our function in place of the old one, so that anyone calling TargetFrame_CheckLevel() is now actually invoking MyAddOn_TargetFrame_CheckLevel() instead.
The next step is to create our MyAddOn_TargetFrame_CheckLevel(). Let's assume we want it to show target levels.
function MyAddOn_TargetFrame_CheckLevel() local retval = MyAddOn_Orig_TargetFrame_CheckLevel(); -- Call original function! TargetLevelText:Show(); TargetHighLevelTexture:Hide(); return retval; end
So in this function, we first invoke the old function to let it do what it needs to do. Next, un-hide the level and hide the skull. Pretty simple right?
This is only an example for how to hook, it will not actually show you the level anymore. -- Shadowed
An Easier Way to Hook a Function?
If you have the Sea library, then you can hook a function using Sea.util.hook.
Sea.util.hook("OldFunctionName", "NewFunctionName", "before|after|hide|replace");
If you specify replace, then the old function will only be called if the new function returns true.
If you use Sea.util.hook, then you're also able to remove the hook later with Sea.util.unhook.
Sea.util.unhook("OldFunctionName, "NewFunctionName");
Using Sea.util.hook will take care of parameter passing, priorities, chains and making sure you can clean yourself up later.
Optionally using Sea
If you don't want your AddOn to depend on Sea, but you would like to take advantage of it when it's available, you can check for its presence:
local MyAddOn_Old_FunctionToHook = function() end; if Sea then Sea.util.hook("FunctionToHook", "ReplacementFunction", "after"); else MyAddOn_Old_FunctionToHook = FunctionToHook; FunctionToHook = ReplacementFunction; end function ReplacementFunction() MyAddOn_Old_FunctionToHook(); ... end
This allows you to list Sea as an OptionalDep in your TOC. This can help prevent future conflicts with other AddOns that replace the old function entirely, as long as the user installs Sea.
Where to Call the Old Function
This can be tricky, and really depends on what you are writing. By default you should probably call the old function first, and then do whatever you need. However, in some instances, you will want to call the old function last, call it conditionally, or not even call it at all.
- Note: Not calling the old function at all can have very interesting effects if another addon hooked it before you did. What you will be doing then, is not letting that addon see the function call. This is where hook libraries do help.
Functions that Take Arguments
If you hook a function that takes arguments, make sure that your new function takes the same arguments, and that it passes it on.
So for instance if we want to hook ActionButton_GetPagedID( id ) our function definition should be something like:
function New_ActionButton_GetPagedID( id ) Old_ActionButton_GetPagedID( id ); ... end
Functions that Use Global Variables
Some functions use global variables, and might change these global variables during execution. For instance, GameTooltip_OnEvent() is one such function, in that it uses the global event variable, and changes it during the course of execution. In this case, it's important to make a copy of the variable before calling the old function. Something like:
function New_GameTooltip_OnEvent() local myEvent = event; Old_GameTooltip_OnEvent(); if ( myEvent = ... ) then ... end end
Hook Chains
This can be a little tricky, but it works. Let's say there are two seperate addons, one that attaches the player tooltip to the mouse, and one that replaces ?? with the player's level. One addon is called "AnchorToolTip", and the other is called "ShowLevel"
If the code for AnchorToolTip is:
local Pre_AnchorToolTip_GameTooltip_OnEvent; function AnchorToolTip_OnLoad() Pre_AnchorToolTip_GameTooltip_OnEvent = GameTooltip_OnEvent; GameTooltip_OnEvent = AnchorToolTip_GameTooltip_OnEvent; end
function AnchorToolTip_GameTooltip_OnEvent() Pre_AnchorToolTip_GameTooltip_OnEvent(); ... end
And the code for ShowLevel is:
local Pre_ShowLevel_GameTooltip_OnEvent; function ShowLevel_OnLoad() Pre_ShowLevel_GameTooltip_OnEvent = GameTooltip_OnEvent; GameTooltip_OnEvent = ShowLevel_GameTooltip_OnEvent; end
function ShowLevel_GameTooltip_OnEvent() Pre_ShowLevel_GameTooltip_OnEvent(); ... end
This will actually work. However, let's say you only want to anchor the tooltip to the mouse when there the shift key is held down. A small innocent change like:
function AnchorToolTip_GameTooltip_OnEvent() if ( isShiftKeyDown() ) then Pre_AnchorToolTip_GameTooltip_OnEvent(); ... end end
May break the hook chain depending on which addon is loaded first. Thus, be very very careful when deciding when and where to call the old function. You're not only calling Blizzard's code, you might also be calling a entire chain of hook functions.
Notes
- If you use a local variable to store the old function, its name matters less. But often in bigger addons, you may need to call it from several of your addon's files, and local variables won't work. Use a syntax that uniquely identifies your previous function reference, like:
- Pre_addon_functionname or
- addon_Orig_functionname.
- An alternative is to store the old function as a member of a global variable, a la MyAddOn.FunctionToBeHooked = FunctionToBeHooked; FunctionToBeHooked = function() ...
- Warning! Blizzard has moved some pieces of the UI to be loaded on demand. The functions in those pieces cannot be hooked until they are loaded.
- Warning! Blizzard does add new arguments and return values to their functions. See HOWTO: Hook functions in a safer way for a solution!