Merge pull request #5151 from levzzz5154/mods/native-item-entities

Native mods: read and change item entities near a block (API next)
This commit is contained in:
levzzz authored and GitHub committed 2026-10-31 20:44:37 +05:00
commit d91cab68de
2 files changed
+90 -1

No files matched your search

+83 -1
View File
@@ -6,6 +6,9 @@
//! Horizon's own HUD extras do, and keep a mod's settings where the
//! WebAssembly mod of the same id kept them.
//!
//! API 42 adds the item entities near a block (`native_item_entities`,
//! `native_set_item_entity`).
//!
//! API 41 adds bundles' data files: `horizon_client::native_data!` compiles a file of a
//! bundle's `data/` folder in, and `native_built_data` is the folder its post-build step wrote.
//!
@@ -194,7 +197,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 = 41;
pub(crate) const NATIVE_HOOKS_API: u32 = 42;
impl GameLevel<'_> {
/// An authoritative server committed a changed state through `Level.setBlock`.
@@ -1378,6 +1381,60 @@ impl Game<'_> {
let _ = self.process_level_events();
changed
}
/// The item entities of the server level `dimension` whose box meets
/// the box `min`..`max`, in the level's order: vanilla's
/// `Level.getEntitiesOfClass(ItemEntity.class, box, ENTITY_STILL_ALIVE)`, as a
/// hopper's `getItemsAtAndAbove` asks for its block and the one above
/// (`[x, y, z]`..`[x + 1, y + 2, z + 1]`). Empty for a level not in place
/// (as `native_set_block`) and on a client of a server. API 42.
pub(super) fn native_item_entities(
&self,
dimension: &str,
min: [f64; 3],
max: [f64; 3],
) -> Vec<NativeItemEntity> {
if (self.serving.is_none() && self.options.dedicated.is_none())
|| dimension != self.dim.id()
{
return Vec::new();
}
self.dropped
.iter()
.filter(|e| e.stack.count > 0 && box_meets(&e.bounds(), min, max))
.map(|e| NativeItemEntity {
id: e.id,
position: [e.position.x, e.position.y, e.position.z],
stack: e.stack.clone(),
pickup_delay: e.pickup_delay,
})
.collect()
}
/// Sets the stack of item entity `id` in the server level `dimension`, as
/// a hopper's `addItem` leaves what it couldn't take (`ItemEntity.setItem`);
/// an empty stack (count 0) removes the entity (`setItem(EMPTY)`, then
/// `discard`). Players see it on their next tracker update. False when the
/// level in place has no such item entity, or on a client. API 42.
pub(super) fn native_set_item_entity(
&mut self,
dimension: &str,
id: u32,
stack: ItemStack,
) -> bool {
if (self.serving.is_none() && self.options.dedicated.is_none())
|| dimension != self.dim.id()
{
return false;
}
let Some(i) = self.dropped.iter().position(|e| e.id == id) else {
return false;
};
if stack.count == 0 {
self.dropped.remove(i);
} else {
self.dropped[i].stack = stack;
}
true
}
/// Sets property `key` of the block at `pos` to `value`, as a furnace's
/// tick sets its `lit` (`Level.setBlock` with `UPDATE_ALL`: neighbours
/// and clients are told); false when the block has no such property or
@@ -2408,6 +2465,31 @@ fn finite(v: f32, lo: f32, hi: f32) -> f32 {
if v.is_finite() { v.clamp(lo, hi) } else { lo }
}
/// An item entity as `native_item_entities` lists it. API 42.
/// Read by mods only.
#[allow(dead_code)]
#[derive(Clone, Debug)]
pub(super) struct NativeItemEntity {
/// The entity's ID, for `native_set_item_entity`.
pub id: u32,
pub position: [f64; 3],
pub stack: ItemStack,
/// Ticks before a player may pick it up (`ItemEntity.pickupDelay`);
/// hoppers take it whatever this is.
pub pickup_delay: u32,
}
/// `AABB.intersects`: the boxes overlap, touching faces excluded.
fn box_meets(b: &horizon_core::geometry::Aabb, min: [f64; 3], max: [f64; 3]) -> bool {
let (lo, hi) = (b.min_position(), b.max_position());
lo.x < max[0]
&& hi.x > min[0]
&& lo.y < max[1]
&& hi.y > min[1]
&& lo.z < max[2]
&& hi.z > min[2]
}
#[cfg(test)]
mod tests {
+7
View File
@@ -227,6 +227,13 @@ need `"hooks": 40`.
API 41 adds bundles' data files (`native_data!`, `native_built_data`; "Data files" below). Bundles that use them need
`"hooks": 41`.
API 42 adds the item entities near a block, for machines that pick up items as a hopper does:
`native_item_entities(dimension, min, max)` lists those whose box meets `min`..`max` (vanilla's
`Level.getEntitiesOfClass(ItemEntity.class, box)`), each with its entity ID, position, stack and pickup delay, and
`native_set_item_entity(dimension, id, stack)` changes one's stack, or removes it when the stack is empty, as a hopper
does with what it takes. Both act on the level in place, as `native_set_block` does. Bundles that use them need
`"hooks": 42`.
## A bundle
A bundle is a folder with a `horizon-mixin.toml`. Besides its helpers and modules (horizon-mixins' README) it can