Stamp the living hooks' fragments (API 356-359)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Ludovic MarieandClaude Opus 5.5 committed 2026-11-02 15:10:21 +01:00
1 parent e33c801291
commit b118744829
12 files changed
+210 -218

No files matched your search

+1 -1
View File
@@ -60,7 +60,7 @@ pub fn breathe(entity: u32, kind: &str, vanilla: Breath) -> Breath {
/// from `LivingEntity.baseTick`), with vanilla's answers as the defaults: it
/// drowns at -20 air, for 2 damage and 8 bubbles. Bubble count is read-only;
/// changes to the event field do not affect the vanilla entity event. Asked on
/// servers, for the entities `native_living_breathe` is. API @API:native_living_drown@.
/// servers, for the entities `native_living_breathe` is. API 356.
#[inline(never)]
pub fn native_living_drown(
entity: u32,
@@ -6,6 +6,14 @@
//! Horizon's own HUD extras do, and keep a mod's settings where the
//! WebAssembly mod of the same id kept them.
//!
//! API 359 adds a living entity jumping (`native_living_jump`).
//!
//! API 358 adds a living entity healing, with a changeable amount and cancellation (`native_living_heal`).
//!
//! API 357 adds a living entity's fall, with changeable distance and multiplier and cancellation (`native_living_fall`).
//!
//! API 356 adds a living entity drowning, with changeable drowning and damage and a read-only bubble count (`native_living_drown`).
//!
//! API 355 adds a mod's own picture in the HUD atlas (`native_mod_image`).
//!
//! API 354 adds A timed world border size change finished (`native_world_border_bounds_change_finish`).
@@ -938,7 +946,7 @@ pub(super) use horizon_world::level::flags;
/// The hook points' API version. The launcher reads this line from the
/// source it builds; keep its form.
#[allow(dead_code)]
pub(crate) const NATIVE_HOOKS_API: u32 = 355;
pub(crate) const NATIVE_HOOKS_API: u32 = 359;
impl GameLevel<'_> {
/// A hopper at `pos` is about to pick up `count` of `item`. Set `cancel` to leave the dropped stack in the world. Server-side. 232.
+1 -1
View File
@@ -14,7 +14,7 @@ use horizon_world::{
};
/// A living entity is about to heal. Mods may change the amount or cancel.
/// `entity` identifies the affected entity. @API:native_living_heal@.
/// `entity` identifies the affected entity. 358.
#[inline(never)]
pub fn native_living_heal(entity: u32, amount: &mut f32, cancel: &mut bool) {
crate::native_client_hooks::living_heal(entity, amount, cancel);
+1 -1
View File
@@ -27,7 +27,7 @@ use std::{collections::BTreeMap, fs, path::Path};
/// A living entity is about to handle a fall (`LivingFallEvent`). Mods may
/// change the distance and damage multiplier, or cancel fall handling.
/// @API:native_living_fall@.
/// 357.
#[inline(never)]
pub fn native_living_fall(
entity: u32,
+142 -142
View File
@@ -3347,38 +3347,6 @@ pub trait Hooks: Send + Sync {
let _ = event;
Flow::Continue
}
/// An entity that can't breathe and has no air (`LivingDrownEvent`, posted
/// from `LivingEntity.baseTick`), with vanilla's answers as the defaults: it
/// drowns at -20 air, for 2 damage and 8 bubbles. Bubble count is read-only;
/// changes to the event field do not affect the vanilla entity event. Asked on
/// servers, for the entities `native_living_breathe` is. API @API:native_living_drown@.
///
/// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped.
fn living_drown(&self, event: &mut LivingDrownEvent<'_>) -> Flow {
let _ = event;
Flow::Continue
}
/// A living entity is about to handle a fall (`LivingFallEvent`). Mods may
/// change the distance and damage multiplier, or cancel fall handling.
/// @API:native_living_fall@.
///
/// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped.
fn living_fall(&self, event: &mut LivingFallEvent<'_>) -> Flow {
let _ = event;
Flow::Continue
}
/// A living entity is about to heal. Mods may change the amount or cancel.
/// `entity` identifies the affected entity. @API:native_living_heal@.
///
/// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped.
fn living_heal(&self, event: &mut LivingHealEvent) -> Flow {
let _ = event;
Flow::Continue
}
/// A living entity jumped (`LivingEvent.LivingJumpEvent`). @API:native_living_jump@.
fn living_jump(&self, event: &mut LivingJumpEvent<'_>) {
let _ = event;
}
/// `player` (`name`) changed dimension (NeoForge's
/// `PlayerChangedDimensionEvent`): `from` and `to` are dimension ids
/// (`minecraft:overworld`, `minecraft:the_nether`, `minecraft:the_end`).
@@ -4508,6 +4476,38 @@ pub trait Hooks: Send + Sync {
let _ = host;
let _ = event;
}
/// An entity that can't breathe and has no air (`LivingDrownEvent`, posted
/// from `LivingEntity.baseTick`), with vanilla's answers as the defaults: it
/// drowns at -20 air, for 2 damage and 8 bubbles. Bubble count is read-only;
/// changes to the event field do not affect the vanilla entity event. Asked on
/// servers, for the entities `native_living_breathe` is. API 356.
///
/// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped.
fn living_drown(&self, event: &mut LivingDrownEvent<'_>) -> Flow {
let _ = event;
Flow::Continue
}
/// A living entity is about to handle a fall (`LivingFallEvent`). Mods may
/// change the distance and damage multiplier, or cancel fall handling.
/// 357.
///
/// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped.
fn living_fall(&self, event: &mut LivingFallEvent<'_>) -> Flow {
let _ = event;
Flow::Continue
}
/// A living entity is about to heal. Mods may change the amount or cancel.
/// `entity` identifies the affected entity. 358.
///
/// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped.
fn living_heal(&self, event: &mut LivingHealEvent) -> Flow {
let _ = event;
Flow::Continue
}
/// A living entity jumped (`LivingEvent.LivingJumpEvent`). 359.
fn living_jump(&self, event: &mut LivingJumpEvent<'_>) {
let _ = event;
}
}
/// What [`Hooks::frame`] is told. Fields may be added: the struct is `#[non_exhaustive]`.
#[non_exhaustive]
@@ -11087,116 +11087,6 @@ pub fn dispatch_item_damaged(
}
*amount = event.amount;
}
/// What [`Hooks::living_drown`] is told. Fields may be added: the struct is `#[non_exhaustive]`.
#[non_exhaustive]
pub struct LivingDrownEvent<'a> {
pub entity: u32,
pub kind: &'a str,
pub is_drowning: bool,
pub damage_amount: f32,
pub bubble_count: i32,
}
pub fn dispatch_living_drown(
mods: &[Box<dyn Hooks>],
entity: u32,
kind: &str,
is_drowning: &mut bool,
damage_amount: &mut f32,
bubble_count: i32,
cancel: &mut bool,
) {
if *cancel {
return;
}
let mut event = LivingDrownEvent {
entity,
kind,
is_drowning: *is_drowning,
damage_amount: *damage_amount,
bubble_count,
};
for mod_hook in mods {
if matches!(mod_hook.living_drown(&mut event), Flow::Cancel) {
*cancel = true;
break;
}
}
*is_drowning = event.is_drowning;
*damage_amount = event.damage_amount;
}
/// What [`Hooks::living_fall`] is told. Fields may be added: the struct is `#[non_exhaustive]`.
#[non_exhaustive]
pub struct LivingFallEvent<'a> {
pub entity: u32,
pub kind: &'a str,
pub distance: f64,
pub multiplier: f32,
}
pub fn dispatch_living_fall(
mods: &[Box<dyn Hooks>],
entity: u32,
kind: &str,
distance: &mut f64,
multiplier: &mut f32,
cancel: &mut bool,
) {
if *cancel {
return;
}
let mut event = LivingFallEvent {
entity,
kind,
distance: *distance,
multiplier: *multiplier,
};
for mod_hook in mods {
if matches!(mod_hook.living_fall(&mut event), Flow::Cancel) {
*cancel = true;
break;
}
}
*distance = event.distance;
*multiplier = event.multiplier;
}
/// What [`Hooks::living_heal`] is told. Fields may be added: the struct is `#[non_exhaustive]`.
#[non_exhaustive]
pub struct LivingHealEvent {
pub entity: u32,
pub amount: f32,
}
pub fn dispatch_living_heal(
mods: &[Box<dyn Hooks>],
entity: u32,
amount: &mut f32,
cancel: &mut bool,
) {
if *cancel {
return;
}
let mut event = LivingHealEvent {
entity,
amount: *amount,
};
for mod_hook in mods {
if matches!(mod_hook.living_heal(&mut event), Flow::Cancel) {
*cancel = true;
break;
}
}
*amount = event.amount;
}
/// What [`Hooks::living_jump`] is told. Fields may be added: the struct is `#[non_exhaustive]`.
#[non_exhaustive]
pub struct LivingJumpEvent<'a> {
pub entity: u32,
pub kind: &'a str,
}
pub fn dispatch_living_jump(mods: &[Box<dyn Hooks>], entity: u32, kind: &str) {
let mut event = LivingJumpEvent { entity, kind };
for mod_hook in mods {
mod_hook.living_jump(&mut event);
}
}
/// What [`Hooks::player_changed_dimension`] is told. Fields may be added: the struct is `#[non_exhaustive]`.
#[non_exhaustive]
pub struct PlayerChangedDimensionEvent<'a> {
@@ -13795,3 +13685,113 @@ pub fn dispatch_world_saved(host: &mut dyn Host, mods: &[Box<dyn Hooks>], wait:
mod_hook.world_saved(host, &mut event);
}
}
/// What [`Hooks::living_drown`] is told. Fields may be added: the struct is `#[non_exhaustive]`.
#[non_exhaustive]
pub struct LivingDrownEvent<'a> {
pub entity: u32,
pub kind: &'a str,
pub is_drowning: bool,
pub damage_amount: f32,
pub bubble_count: i32,
}
pub fn dispatch_living_drown(
mods: &[Box<dyn Hooks>],
entity: u32,
kind: &str,
is_drowning: &mut bool,
damage_amount: &mut f32,
bubble_count: i32,
cancel: &mut bool,
) {
if *cancel {
return;
}
let mut event = LivingDrownEvent {
entity,
kind,
is_drowning: *is_drowning,
damage_amount: *damage_amount,
bubble_count,
};
for mod_hook in super::entered(mods, "living_drown") {
if matches!(mod_hook.living_drown(&mut event), Flow::Cancel) {
*cancel = true;
break;
}
}
*is_drowning = event.is_drowning;
*damage_amount = event.damage_amount;
}
/// What [`Hooks::living_fall`] is told. Fields may be added: the struct is `#[non_exhaustive]`.
#[non_exhaustive]
pub struct LivingFallEvent<'a> {
pub entity: u32,
pub kind: &'a str,
pub distance: f64,
pub multiplier: f32,
}
pub fn dispatch_living_fall(
mods: &[Box<dyn Hooks>],
entity: u32,
kind: &str,
distance: &mut f64,
multiplier: &mut f32,
cancel: &mut bool,
) {
if *cancel {
return;
}
let mut event = LivingFallEvent {
entity,
kind,
distance: *distance,
multiplier: *multiplier,
};
for mod_hook in super::entered(mods, "living_fall") {
if matches!(mod_hook.living_fall(&mut event), Flow::Cancel) {
*cancel = true;
break;
}
}
*distance = event.distance;
*multiplier = event.multiplier;
}
/// What [`Hooks::living_heal`] is told. Fields may be added: the struct is `#[non_exhaustive]`.
#[non_exhaustive]
pub struct LivingHealEvent {
pub entity: u32,
pub amount: f32,
}
pub fn dispatch_living_heal(
mods: &[Box<dyn Hooks>],
entity: u32,
amount: &mut f32,
cancel: &mut bool,
) {
if *cancel {
return;
}
let mut event = LivingHealEvent {
entity,
amount: *amount,
};
for mod_hook in super::entered(mods, "living_heal") {
if matches!(mod_hook.living_heal(&mut event), Flow::Cancel) {
*cancel = true;
break;
}
}
*amount = event.amount;
}
/// What [`Hooks::living_jump`] is told. Fields may be added: the struct is `#[non_exhaustive]`.
#[non_exhaustive]
pub struct LivingJumpEvent<'a> {
pub entity: u32,
pub kind: &'a str,
}
pub fn dispatch_living_jump(mods: &[Box<dyn Hooks>], entity: u32, kind: &str) {
let mut event = LivingJumpEvent { entity, kind };
for mod_hook in super::entered(mods, "living_jump") {
mod_hook.living_jump(&mut event);
}
}
+1 -1
View File
@@ -43,7 +43,7 @@ use std::{
sync::Arc,
};
/// A living entity jumped (`LivingEvent.LivingJumpEvent`). @API:native_living_jump@.
/// A living entity jumped (`LivingEvent.LivingJumpEvent`). 359.
#[inline(never)]
pub fn native_living_jump(entity: u32, kind: &str) {
crate::native_client_hooks::living_jump(entity, kind);
+12 -4
View File
@@ -934,13 +934,13 @@ API 142 adds an energy buffer on a mod block's block entity (`Capabilities.Energ
API 143 adds data maps (`RegisterDataMapTypesEvent`). `native_data_map_types(types)` pushes the types a mod registers: an `id` (`mod_id:name`, not in `minecraft`) for a `registry`, `minecraft:item` or `minecraft:block` so far; asked once per load of the server's data (a world's first open and every `/reload`). The values come from `data/<mod_id>/data_maps/<registry path>/<name>.json` in the world's data packs and from files a mod pushes through `native_data_pack_files` under the key `data_maps/<mod_id>:<registry path>/<name>`: `{"replace": false, "values": {"minecraft:coal": ..., "#minecraft:logs": ...}, "remove": [...]}`, an entry's value being the data itself or `{"value": ..., "replace": false}`; `replace` clears what earlier files set, a tag gives every entry in it the value, `remove` applies after the file's own values, and a newer value overrides an older one. `native_data_map(registry, type, entry)` reads an entry's value (the JSON the file held) and `native_data_map_entries(registry, type)` lists them. Server side only and not synced to clients; mergers, conditions, other registries and mod datapack registries are not here yet. Bundles need "hooks": 143.
`native_living_fall(entity, kind, distance, multiplier, cancel)` corresponds to NeoForge's `LivingFallEvent` at `LivingEntity.causeFallDamage`. Mods may change the distance and damage multiplier or cancel fall handling; cancellation does not reverse physical movement. This free function receives the entity ID and type and cannot call game helpers. Player landings, mounted player falls and non-equine mob falls are wired. Equines use their own fall-damage override rather than `LivingEntity.causeFallDamage`. Bundles need `"hooks": @API:native_living_fall@`.
`native_living_fall(entity, kind, distance, multiplier, cancel)` corresponds to NeoForge's `LivingFallEvent` at `LivingEntity.causeFallDamage`. Mods may change the distance and damage multiplier or cancel fall handling; cancellation does not reverse physical movement. This free function receives the entity ID and type and cannot call game helpers. Player landings, mounted player falls and non-equine mob falls are wired. Equines use their own fall-damage override rather than `LivingEntity.causeFallDamage`. Bundles need `"hooks": 357`.
`native_living_jump(entity, kind)` listens for NeoForge's `LivingEvent.LivingJumpEvent` after a living entity jumps. It receives the entity ID and type, and cannot cancel or change the jump. The player motion and mob ground-jump paths, including cubes and ridden horses and camels, are wired; whether every client-side prediction path reaches them remains unverified. Bundles need `"hooks": @API:native_living_jump@`.
`native_living_jump(entity, kind)` listens for NeoForge's `LivingEvent.LivingJumpEvent` after a living entity jumps. It receives the entity ID and type, and cannot cancel or change the jump. The player motion and mob ground-jump paths, including cubes and ridden horses and camels, are wired; whether every client-side prediction path reaches them remains unverified. Bundles need `"hooks": 359`.
`native_living_heal(entity, amount, cancel)` corresponds to NeoForge's `LivingHealEvent` at `LivingEntity.heal`. The amount can be changed and cancellation prevents the heal. It covers player effect, food and peaceful regeneration healing and the wired mob healing paths. Other healing paths and client-side coverage remain unverified. `entity` is the affected player or mob ID. Bundles need `"hooks": @API:native_living_heal@`.
`native_living_heal(entity, amount, cancel)` corresponds to NeoForge's `LivingHealEvent` at `LivingEntity.heal`. The amount can be changed and cancellation prevents the heal. It covers player effect, food and peaceful regeneration healing and the wired mob healing paths. Other healing paths and client-side coverage remain unverified. `entity` is the affected player or mob ID. Bundles need `"hooks": 358`.
`native_living_drown(entity, kind, is_drowning, damage_amount, bubble_count, cancel)` asks on the server when a player or generic-air mob cannot breathe and has no air (NeoForge's `LivingDrownEvent`, `LivingEntity.baseTick`). Mods may change the drowning decision, damage and bubble count, or cancel. Damage at or below zero does not hurt. The normal eight bubbles use the entity event; other positive counts are sent in a particle packet, and nonpositive counts suppress bubbles. Bundles need "hooks": @API:native_living_drown@.
`native_living_drown(entity, kind, is_drowning, damage_amount, bubble_count, cancel)` asks on the server when a player or generic-air mob cannot breathe and has no air (NeoForge's `LivingDrownEvent`, `LivingEntity.baseTick`). Mods may change the drowning decision, damage and bubble count, or cancel. Damage at or below zero does not hurt. The normal eight bubbles use the entity event; other positive counts are sent in a particle packet, and nonpositive counts suppress bubbles. Bundles need "hooks": 356.
API 144 adds a player's or mob's breathing, on the single player server thread and on a dedicated server: `native_living_breathe(entity, kind, can_breathe, consume, refill)` every tick for a player and for a mob with the generic air logic (NeoForge's `LivingBreatheEvent`, vanilla's `LivingEntity.baseTick`), with Horizon's answers as the defaults: set `can_breathe` and how much air `consume` takes or `refill` gives (up to 300); at -20 the entity is set to 0 air and takes 2 drowning damage. It is a free function in `horizon_client::breathing`, so it answers and cannot call helpers. Axolotls and `WaterAnimal`s keep their own air logic. Bundles that use it need "hooks": 144.
@@ -1382,6 +1382,14 @@ API 353 adds A vault is about to eject a loot item: `native_vault_loot(pos, item
API 354 adds A timed world border size change finished: `native_world_border_bounds_change_finish()` fires after the border reaches its final size. Bundles need "hooks": 354.
API 355 adds `Game::native_mod_image(width, height, rgba)`: a mod's own RGBA image, uploaded into the HUD atlas slot `horizon/modimage` (1024 by 1024) and drawn with `native_sprite`, whose source rectangle crops and stretches it, so a map or a schematic view needs no texture of its own. The slot is reserved only while a native mod is loaded, so a game without mods pays none of its 4 MiB. False for an image that is not `width * height` pixels of four bytes, is larger than the slot, or when there is no slot. Each upload is a texture patch of the image, so send one when the image changed, not every frame.
API 356 adds a living entity drowning (`native_living_drown`), with changeable drowning and damage, a read-only bubble count and cancellation. Bundles need "hooks": 356.
API 357 adds a living entity's fall (`native_living_fall`), with changeable distance and multiplier and cancellation. Bundles need "hooks": 357.
API 358 adds a living entity healing (`native_living_heal`), with a changeable amount and cancellation. Bundles need "hooks": 358.
API 359 adds a living entity jumping (`native_living_jump`). Bundles need "hooks": 359.
## What the launcher does
**Developing a mod?** Run `horizon-launcher settings native-profile dev` once, or pick "Dev build" in the Mods page's
+43
View File
@@ -4254,6 +4254,49 @@
"mod_api": {
"contract": "listen"
}
},
{
"name": "living_drown",
"version": 1,
"target": "horizon_client_lib::breathing::native_living_drown",
"neoforge": "LivingDrownEvent",
"side": "server",
"mod_api": {
"contract": "cancel",
"out": "cancel"
}
},
{
"name": "living_fall",
"version": 1,
"target": "horizon_client_lib::landing::native_living_fall",
"neoforge": "LivingFallEvent",
"side": "both",
"mod_api": {
"contract": "cancel",
"out": "cancel"
}
},
{
"name": "living_heal",
"version": 1,
"target": "horizon_client_lib::hunger::native_living_heal",
"neoforge": "LivingHealEvent",
"side": "both",
"mod_api": {
"contract": "cancel",
"out": "cancel"
}
},
{
"name": "living_jump",
"version": 1,
"target": "horizon_client_lib::motion::native_living_jump",
"neoforge": "LivingEvent.LivingJumpEvent",
"side": "both",
"mod_api": {
"contract": "listen"
}
}
]
}
@@ -1,17 +0,0 @@
{
"hooks": [
{
"name": "living_drown",
"target": "horizon_client_lib::breathing::native_living_drown",
"neoforge": "LivingDrownEvent",
"side": "server",
"mod_api": {
"contract": "cancel",
"out": "cancel"
}
}
],
"rust_summary": "a living entity drowning, with changeable drowning and damage and a read-only bubble count (`native_living_drown`).",
"doc_summary": "a living entity drowning (`native_living_drown`), with changeable drowning and damage, a read-only bubble count and cancellation. Bundles need \"hooks\": @API:native_living_drown@.",
"comments": ["crates/horizon-client/src/breathing.rs"]
}
@@ -1,17 +0,0 @@
{
"hooks": [
{
"name": "living_fall",
"target": "horizon_client_lib::landing::native_living_fall",
"neoforge": "LivingFallEvent",
"side": "both",
"mod_api": {
"contract": "cancel",
"out": "cancel"
}
}
],
"rust_summary": "a living entity's fall, with changeable distance and multiplier and cancellation (`native_living_fall`).",
"doc_summary": "a living entity's fall (`native_living_fall`), with changeable distance and multiplier and cancellation. Bundles need \"hooks\": @API:native_living_fall@.",
"comments": ["crates/horizon-client/src/landing.rs"]
}
@@ -1,17 +0,0 @@
{
"hooks": [
{
"name": "living_heal",
"target": "horizon_client_lib::hunger::native_living_heal",
"neoforge": "LivingHealEvent",
"side": "both",
"mod_api": {
"contract": "cancel",
"out": "cancel"
}
}
],
"rust_summary": "a living entity healing, with a changeable amount and cancellation (`native_living_heal`).",
"doc_summary": "a living entity healing (`native_living_heal`), with a changeable amount and cancellation. Bundles need \"hooks\": @API:native_living_heal@.",
"comments": ["crates/horizon-client/src/hunger.rs"]
}
@@ -1,16 +0,0 @@
{
"hooks": [
{
"name": "living_jump",
"target": "horizon_client_lib::motion::native_living_jump",
"neoforge": "LivingEvent.LivingJumpEvent",
"side": "both",
"mod_api": {
"contract": "listen"
}
}
],
"rust_summary": "a living entity jumping (`native_living_jump`).",
"doc_summary": "a living entity jumping (`native_living_jump`). Bundles need \"hooks\": @API:native_living_jump@.",
"comments": ["crates/horizon-client/src/motion.rs"]
}