Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Adding a hook

A new hook needs its declaration in the area file of crates/horizon-mod-vocab/src/hooks/ (/// lines and #[hook(side = "server", contract = "cancel", out = "cancel")] on fn name();; the argument list is in that folder's mod.rs), its native_* function with the placeholder body let _ = (every, parameter); (all its parameters, in order), and a run of cargo run -p xtask -- mod-api. Put that function in crates/horizon-client/src/game/native_hooks/<area>.rs, matching the declaration's area filename. Alternatively, a named hook may live in any logical module under game/, including area folders, mod.rs and #[path] modules. Its target must name that module and Game<'_>::native_<name>. Exactly one matching Game method must exist. Outside native_hooks/, the method must call dispatch_<name> itself: placeholders are not filled there. Remove macros, cfg and cfg_attr from that method so injections can patch it. A refusal names the hook and what to change. These checks run at build time and add no dispatch-time work. The build discovers native hook modules automatically; do not add a module list entry. Shared support lives in shared.rs; fire.rs includes generated dispatch entry points, not hook implementations. That command only replaces the placeholder in its owning area file with the dispatch_<hook> call; commit that source change and the declaration. Fills are idempotent; rerun the command after a failed write. The horizon-mod-api-gen build dependency generates the hook traits, Host methods and dispatch code into Cargo's OUT_DIR when the game and renderer build, including release builds. These generated Rust files are not kept in the source tree or committed, and no manual regeneration is needed after a merge. Every hook needs a mod_api entry: the build fails naming any hook without one. A hook that stands in for a method a vanilla block, block entity, item or menu overrides also gets a scope and a key ({"contract": "collect", "out": "output", "scope": "block", "key": "state"}): the key is a BlockState (routed by its prepared block key for block parts), an &ItemStack, a ModBlockEntity or an id &str, and the generator also puts the method on that scope's part trait (Parts). A body that is neither the placeholder nor already calls the dispatch is an error, and the generator never changes it. Hooks that are free functions outside Game are wired by hand: those crates can't see the mod list, so each calls a callback the executable installs once, such as NativeLoadHooks in horizon-world and NativeClientHooks in the client library (menu_slot_rules, living_breathe, ingredient). A build reports a placeholder still to fill instead of changing hand-written source; run cargo run -p xtask -- mod-api to fill it. Mixin injections go at the head of the body, before the dispatch, so they still run first.