DISCLAIMER

The following page covers content that is not recommended for use in mods.

This method of hooking is superseded by Script Hooks. It is highly recommended that you use them instead.

Function Hooking (with Utils.hook())

A function hook changes just one function at a time using the Utils.hook() function.

When using Utils.hook(), we should always be inside Mod:init(), which is inside the mod.lua file.

The full signature of the function is Utils.hook(target, name, hook). target is the name of the target class, name is the function we want to replace, and hook is our new function.

hook is a function receiving orig (the original function), self, and then all of the hooked function's arguments.

Let's put that down as some template code we can read. If we were to insert a hook into an otherwise empty mod.lua, it would look something like this:

function Mod:init()
    print("Loaded "..self.info.name.."!")

    -- This would hook over "MyClass:myFunction(...)", replacing it's code with our own.
    Utils.hook(MyClass, "myFunction", function (orig, self, ...)
        orig(self, ...) -- Call the original "MyClass:myFunction(...)"
    end)
end

(This code is only an example of hook structure, MyClass does not exist, and attempting to use this will cause a crash.)

That's all it takes to hook a function! Our hook wouldn't be very interesting though, as all it's doing is calling the original function...

So... let's test hooking on a real Kristal function!

Our target for this example will be the EnemyBattler:hurt() function.

Let's swap in some names to our above template to hook EnemyBattler:

Utils.hook(EnemyBattler, "hurt", function (orig, self, ...)
    orig(self, ...)
end)

Having our function arguments as ... here isn't great. They'll pass into the original function fine, but we can't really use them ourselves.

This is where having the source code is important. Let's look for EnemyBattler:hurt inside enemybattler.lua!

In VSCode, opening the source code folder and searching enemybattler in the top searchbar will lead you to the file. Similarly GitHub has the go to file searchbox. (Other editors will likely have similar search features)

Then, use Ctrl+F to search up EnemyBattler:hurt. You should be taken straight to the function!

What we want to do is swap ... for the arguments listed there.

If you've done it correctly, the hook should now look like this:

Utils.hook(EnemyBattler, "hurt", function (orig, self, amount, battler, on_defeat, color, show_status, attacked)
    orig(self, amount, battler, on_defeat, color, show_status, attacked)
end)

Now we can make a few changes - let's log some information to the Kristal console before and after the EnemyBattler is hurt.

Utils.hook(EnemyBattler, "hurt", function (orig, self, amount, battler, on_defeat, color, show_status, attacked)
    -- Code above the original function runs before it:
    Kristal.Console:log("Enemy " .. self.name .. " has " .. self.health .. " HP.")

    Kristal.Console:log("Hurting enemy for " .. amount .. " damage")
    -- Original EnemyBattler:hurt runs
    orig(self, amount, battler, on_defeat, color, show_status, attacked) 

    -- Code below the original function runs after it:
    Kristal.Console:log("Enemy " .. self.name .. " has " .. self.health .. " HP.")

    Kristal.Console:log("-------------")
end)

Aside from the differences we mentioned before, you can see that everything we write is as if we were in EnemyBattler:hurt itself!

If we test this out ingame by attacking an enemy a few times, we should see this code printing to the console every time EnemyBattler:hurt runs:

image

(You can press ` to make the Kristal console appear.)

With this, we can make any code we want run just before or after any EnemyBattler gets hurt - including changing what gets passed into the function (increasing the damage amount, for example).

Feel free to experiment with this hook a bit to get a taste of what they can do!


If you want to change something that happens within EnemyBattler:hurt, things are a bit different.

Hooks can't partially modify the code of an existing function, so the only way to do this is to remove the original function call and copy the source code of EnemyBattler:hurt into your hook.

From there, you can safely modify it however you like.

Hook Annotations

The Lua language server can't properly understand changes to functions made through Utils.hook(), even with annotations.

For hook annotations and language server support, you must use the recommended Utils.hookScript() method of hooking.