-
Notifications
You must be signed in to change notification settings - Fork 0
Hooks.md
Hooks let a mod run code before, after, deferred after, or instead of selected vanilla script methods.
Use hooks when you want to add behavior without replacing an entire vanilla script. They are usually more compatible than full resource replacement or script overrides.
OrcKit generates a temporary hook pack. For selected vanilla methods, it renames the original method internally and inserts a wrapper method with the original name.
The wrapper can:
- Dispatch
-precallbacks. - Run a replace hook or the vanilla method.
- Dispatch
-postcallbacks. - Dispatch
-callbackcallbacks deferred.
Only non-static functions are hookable.
Declare target scripts and methods in mod.txt:
[hooks]
res://scripts/player.gd="_ready,_process"This tells OrcKit which vanilla scripts need wrappers.
Wrap all methods:
[hooks]
res://scripts/player.gd="*"Prefer specific method names. * is convenient while investigating, but it creates a larger hook pack and can affect compatibility.
extends Node
func _ready() -> void:
var modlib = Engine.get_meta("OrcmodLib")
if modlib._is_ready:
_register_hooks(modlib)
else:
modlib.frameworks_ready.connect(func(): _register_hooks(modlib))
func _register_hooks(modlib) -> void:
modlib.hook("player-_ready-pre", Callable(self, "_before_player_ready"), 100)
func _before_player_ready() -> void:
print("Player is about to run _ready")<script-stem>-<method-name>-<phase>
Example vanilla path:
res://scripts/player.gd
Script stem:
player
Method:
_ready
Hook names:
player-_ready-pre
player-_ready-post
player-_ready-callback
player-_ready
Hook names are lowercase for the script stem and method name.
Runs before vanilla behavior.
modlib.hook("player-_process-pre", Callable(self, "_before_process"))
func _before_process(delta: float) -> void:
passRuns after vanilla behavior.
modlib.hook("player-_process-post", Callable(self, "_after_process"))
func _after_process(delta: float) -> void:
passFor methods with return values, a post hook can accept the current result as its last argument. If it returns a non-null value, that value becomes the new result.
func _after_get_damage(amount: int, current_result: int) -> int:
return current_result + 5Runs deferred after the wrapper.
modlib.hook("player-_ready-callback", Callable(self, "_after_ready_deferred"))Use callback hooks when you want work to happen after the current call stack.
modlib.hook("player-_process", Callable(self, "_replace_process"))
func _replace_process(delta: float) -> void:
var modlib = Engine.get_meta("OrcmodLib")
modlib.skip_super()
# Vanilla _process will not run.Only one replace hook can own a hook name. If another replace hook already exists, hook() returns -1.
Lower priority callbacks run earlier.
modlib.hook("player-_ready-pre", Callable(self, "_first"), 10)
modlib.hook("player-_ready-pre", Callable(self, "_second"), 100)Use priority only when order matters. Most mods can use the default 100.
add_hook() builds the hook name for you:
modlib.add_hook("res://scripts/player.gd", "_ready", Callable(self, "_before_ready"), true)
modlib.add_hook("res://scripts/player.gd", "_ready", Callable(self, "_after_ready"), false)The last argument means:
-
true: pre hook -
false: post hook
var result = modlib.hook_many({
"player-_ready-pre": Callable(self, "_before_ready"),
"player-_ready-post": Callable(self, "_after_ready"),
}, 100)
if not result["ok"]:
push_warning("Some hooks failed to register")OrcKit also exposes stable helper functions for common Sir, We Have an Orc Problem events. These are easier to use than raw hook names.
Call these from an autoload after frameworks_ready:
extends Node
func _ready() -> void:
var modlib = Engine.get_meta("OrcmodLib")
if modlib._is_ready:
_install(modlib)
else:
modlib.frameworks_ready.connect(func(): _install(modlib))
func _install(modlib) -> void:
modlib.on_battle_start(Callable(self, "_on_battle_start"))
modlib.on_enemy_killed(Callable(self, "_on_enemy_killed"))
modlib.on_tower_placed(Callable(self, "_on_tower_placed"))
func _on_battle_start(battle: Node) -> void:
print("Battle started")
func _on_enemy_killed(count: int, data: PackedByteArray, battle: Node) -> void:
print("Killed ", count, " enemy/enemies")
func _on_tower_placed(tower: Node, battle: Node) -> void:
print("Tower placed: ", tower.name)Available helpers:
| Helper | Callback arguments |
|---|---|
modlib.on_battle_start(callable, priority := 100) |
battle |
modlib.on_battle_end(callable, priority := 100) |
health, total_enemies_spawned, total_enemies_killed, battle
|
modlib.on_enemy_spawned(callable, priority := 100) |
count, battle
|
modlib.on_enemy_killed(callable, priority := 100) |
count, data, battle
|
modlib.on_tower_placed(callable, priority := 100) |
tower, battle
|
modlib.on_tower_removed(callable, priority := 100) |
tower, battle
|
modlib.on_level_loaded(callable, priority := 100) |
selected_level, battle
|
modlib.on_tech_tree_opened(callable, priority := 100) |
menu |
modlib.on_upgrade_purchased(callable, priority := 100) |
upgrade, new_level
|
Your callback may accept fewer arguments than listed. OrcKit only passes as many arguments as your callback declares.
These helper calls are statically scanned when they appear directly in your script, for example modlib.on_enemy_killed(...). If you register them dynamically through strings, call(), or generated code, add the matching raw target in [hooks].
var hook_id := -1
func _install(modlib) -> void:
hook_id = modlib.hook("player-_ready-pre", Callable(self, "_before_ready"))
func _exit_tree() -> void:
if hook_id != -1 and Engine.has_meta("OrcmodLib"):
Engine.get_meta("OrcmodLib").unhook(hook_id)Most mods register once and never unhook, but unhooking is useful for temporary systems.
Register after frameworks_ready.
Direct .hook() calls are scanned and OrcKit tries to infer the target script, but explicit [hooks] declarations are more reliable.
Direct game-specific helper calls such as modlib.on_battle_start(...) are also scanned. If a helper hook does not fire, add an explicit [hooks] entry while debugging.
For res://scripts/player_controller.gd, the stem is:
player_controller
not:
PlayerController
Static methods are skipped by the wrapper generator.
Replace hooks are powerful but conflict-prone. Prefer -pre or -post unless you need to suppress vanilla behavior.
- Declare hook targets in
mod.txt. - Register hooks from one autoload script.
- Wait for
frameworks_ready. - Keep hook callbacks small.
- Avoid heavy work in
_processand_physics_processhooks. - Use
-postfor result mutation. - Use replace hooks sparingly.
- Document hook names in your mod release notes for compatibility authors.
OrcKit developer wiki for Sir, We Have an Orc Problem Playtest mods. These pages document OrcKit 1.0.0.