[Tutorial] Use custom callbacks⚓︎
Repentance update 1.79b added various callback functions, including priority and the capability of creating your own callbacks.
Basic callbacks⚓︎
You can use mod:AddCallback
(or Isaac.AddCallback
, but it's recommended to use the mod table instead) with any value as the callback ID now, even strings:
1 2 3 |
|
In fact, it's recommended to use strings instead of numbers like the base game does if you're using custom callbacks, to avoid conflicts with other mods.
Just doing this won't do anything though, as nothing in the game or the mod triggers the "test" callback yet. What you can do to trigger it is:
1 |
|
This will run all callbacks with the "test" id in order of priority and then in the order they were added.
Notice that you didn't need to "define" the callback anywhere, any mod can add callbacks to your custom id using its value and you can run your custom callback without any previous definitions.
Returned values⚓︎
Isaac.RunCallback
will stop at the first callback that returns a value and return it. For example:
1 2 3 4 5 6 7 |
|
This should print "3" in the log on run start.
Entity types / parameter matching⚓︎
Like with vanilla callback, you can make your callback only run for certain entity types, variants, or any other condition you need.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Of course, this can be any value, not necessarily entity types. Isaac.RunCallbackWithParam
checks if each callback's parameter matches its second argument, or is nil.
Mod compatibility⚓︎
As before, you don't need to "define" your callback, creating a custom callback just needs Isaac.RunCallback
to run that at least once and AddCallback
to be used with it.
This allows for mods that offer an API to other mods to do that in a simple way: the dependent mods don't need to wait for the main mod to be loaded. For example, imagine a Minimap mod that wants to allow its dependents to run code after the minimap changes size. Let's call it MinimapLibrary: The code in MinimapLibrary would be:
1 2 |
|
While the code in any dependent mod (Mod 2, here) would be:
1 2 3 4 |
|
Mod 2 doesn't need to check if MinimapAPI is even loaded before adding inter-mod functionality: if it's loaded, it will run, otherwise it simply won't. No need to wait for the library mod to be loaded, like you'd need to do when calling functions defined by it.
Run behavior⚓︎
Normally, Isaac.RunCallback
breaks on the first return and returns it. If you need some other way of handling the callback running and returning behavior, you need to do it manually via Isaac.GetCallbacks
.
For example, if you want to continue running the callback, and override its first param with the last runs' return:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Isaac.GetCallbacks
returns a table already ordered by callback priority and order of adding, so you don't need to worry about order unless you want to change it. Its elements are structured like this:
1 2 3 4 5 6 |
|
Passing true as the second argument to Isaac.GetCallbacks assigns an empty table to the callback if it didn't exist yet.
Advanced parameters⚓︎
As you can get the callback table via GetCallbacks, you can also assign a metatable to it. In particular, Isaac can use a new function to have a different parameter checking than default (which uses ==).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
|
(Example source: _Kilburn)
Unique callback IDs⚓︎
It's heavily recommended to use string IDs with a prefix unique to your mod in your callbacks, to avoid overlapping other mods' callbacks that might have the same name. For example, for a mod named AchievementLibrary, something like ACHLIB_TEST_CALLBACK.
If you need your callback ID to be unique even if the name is shared, for example if you make a library to be included in other mods, and as such whose logic might run more than once (which would lead in running callbacks with the same name more than once, if using a string name), you can use table references as IDs instead. Here is an example:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
As each table reference is unique, every new created table will be treated as a different ID. The advantage to this is having a unique locally defined ID, that isn't shared with the global space of possible strings.