WoW:Hooking functions: Difference between revisions
m (sp) |
mNo edit summary |
||
Line 1: | Line 1: | ||
In LUA, a function is simply a variable. From the LUA documentation: | In LUA, a function is simply a variable. From the LUA documentation: | ||
Revision as of 23:32, 17 January 2008
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
This article or section contains information that is out-of-date.
|
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.
Other Hooking libraries
Stubby
With Stubby you can hook functions with Stubby.HookFunction
Stubby.RegisterFunctionHook("FunctionToHook", position, replacementFunction [, hook1, hook2, .., hookN])
- The FunctionToHook is a string that names the function you want to hook into. eg: "GameTooltip.SetOwner"
- The position is a negative or positive number that defines the actual calling order of the addon. The smaller or more negative the number, the earlier in the call sequence your hookFunction will be called, the larger the number, the later your hook will be called. The actual original (hooked) function is called at position 0, so if your addon is hooked at a negative position, your return values table (see below) will contain the return values of the previous function in Stubby's call chain.
- You pass (by reference) your function that you wish called as replacementFunction. This function will be called with the following parameters: hookFunction(hookParams, returnValue, hook1, hook2 .. hookN)
- hookParams is a table containing the additional parameters passed to the RegisterFunctionHook function (the "hook1, hook2, .., hookN" params)
- returnValue is an array of the returned values of the function called before you in Stubby's call chain or nil if none.
- hook1..hookN are the original parameters of the hooked function in the original order.
- You can optionally return the following strings to Stubby for special handling cases:
- "abort" Will abort the call chain (will not call any other functions scheduled after your own).
- "killorig" Will not call the original function, but will continue the call chain.
- "setparams" Will replace the function's call paramenters with the ones you specify in a list on your function's second return value.
- "setreturn" will change the return values for the next function in Stubby's call chain with your with the ones you specify in a list on your function's second return value.
Ace
Ace2
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 article or section contains information that is out-of-date.
|
This can be a little tricky, but it works. Let's say there are two separate 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!