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

Block Parts

BlockPart::clone_item_stack answers on the server when a player picks the block. It receives the level, position, block state and include_data, true when a creative player picks with Ctrl. Return None to keep the game's item.

BlockPart::fall_on runs at vanilla's landing moment on a read-only level random host. Request a deferred write with event.request(LandingWrite::SetBlock(state)), where state is an API BlockState, or LandingWrite::DestroyBlock { drop }. The game checks the complete write batch before queuing it. An invalid block key or ordinal refuses that callback's writes and other outputs, including damage; it never applies a valid prefix. After the entity batch ticks, writes apply in request order if the block still matches its original landed state. That guard runs once, so a first write can change the state without suppressing the next write. SetBlock notifies neighbors and clients; DestroyBlock preserves the requested resource-drop flag.

BlockPart::contents_changed(host, event) observes a world block's storage at setChanged, with a ServerHost, typed event.level, event.pos: BlockPos and event.state. Attach it to the specific blocks you observe, including vanilla containers; it has no global Hooks counterpart. The backing contents are already written, before dirty/comparator updates. Native transactions notify once per backing block at root commit, never on abort or simulation. An explicit BlockEntityRef::set_changed notifies even if contents are unchanged. A double container notifies each component, not once again for the other half's comparator refresh. A listener's writes still apply, but a nested notice for that same backing block does not re-enter the listener; writes to a different backing block notify normally. Entity/menu placeholder coordinates are not block notices. This replaces Create's positional contents_changed mixin for depot/package/schematic storage; there is no NeoForge bus event. A compiled listener that reads the committed items through the typed level (the game's own test, so it cannot drift):

#[derive(Clone, Debug, PartialEq, Eq)]
struct Notice {
    level: LevelRef,
    pos: [i32; 3],
    amount: u32,
    version: Option<i32>,
}

#[derive(Clone, Default)]
struct Changes(Arc<Mutex<Vec<Notice>>>);

#[mod_api::horizon_part]
impl BlockPart for Changes {
    fn contents_changed(&self, host: &mut dyn ServerHost, event: &mut ContentsChangedEvent) {
        let pos = [event.pos.x, event.pos.y, event.pos.z];
        let amount = event
            .level
            .capability(host, &ITEMS, pos, None)
            .map_or(0, |items| {
                (0..items.size(host))
                    .map(|slot| items.amount(host, slot))
                    .sum()
            });
        let version = host
            .block_entity(event.level, pos)
            .and_then(|entity| entity.data(host))
            .and_then(|data| match data.get(&"Version".into()) {
                Some(Tag::Int(value)) => Some(*value),
                _ => None,
            });
        self.0.lock().unwrap().push(Notice {
            level: event.level,
            pos,
            amount,
            version,
        });
    }
}