Pumpkin-Docs/docs/de/developer/blocks/adding-blocks.md
Alexander Medvedev 5c23b676ed feat: d binding
2026-09-17 09:23:24 +02:00

6.8 KiB
Raw Blame History

Adding a Block Behavior

This guide walks through implementing interactive behavior for a block in Pumpkin.


When Is a Behavior Needed?

  • Non-Interactive Blocks (e.g., stone, dirt, terracotta): Do not require any custom Rust code. Their breaking speeds, hardness, and placement properties are automatically derived from pumpkin-data.
  • Interactive or Functional Blocks (e.g., doors, chests, levers, crops, campfires, TNT): Require an implementation of the BlockBehaviour trait to respond to player clicks, redstone signals, physics steps, or timer ticks.

Step 1: Create the Behavior Struct

Create a new file under crates/pumpkin/src/block/blocks/<category>/<block_name>.rs (or directly in crates/pumpkin/src/block/blocks/).

Use the #[pumpkin_block(...)] macro to associate your behavior struct with one or more vanilla block identifiers:

use std::sync::Arc;
use pumpkin_data::Block;
use pumpkin_data::block_properties::{DoorLikeProperties, Facing};
use pumpkin_macros::pumpkin_block;
use pumpkin_util::math::position::BlockPos;
use crate::block::registry::BlockActionResult;
use crate::block::{
    BlockBehaviour, NormalUseArgs, OnNeighborUpdateArgs, PlacedArgs, UseWithItemArgs,
};
use crate::world::World;

/// Associates this behavior struct with one or multiple block IDs.
#[pumpkin_block([
    "minecraft:oak_door",
    "minecraft:iron_door",
    "minecraft:spruce_door",
    "minecraft:birch_door"
])]
pub struct DoorBlock;

impl BlockBehaviour for DoorBlock {
    fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
        let state_id = args.world.get_block_state(args.location).await;

        // Iron doors require redstone power and cannot be opened manually by hand
        if args.block.id == pumpkin_data::Block::IRON_DOOR.id {
            return BlockActionResult::Pass;
        }

        // Toggle open state
        let is_open = DoorLikeProperties::open(state_id);
        let new_state = DoorLikeProperties::with_open(state_id, !is_open);
        args.world.set_block_state(args.location, new_state).await;

        BlockActionResult::Consume
    }

    fn on_neighbor_update(&self, args: OnNeighborUpdateArgs<'_>) {
        // Handle neighbor redstone signals or support block destruction
    }
}

Step 2: BlockBehaviour Hook Reference

The BlockBehaviour trait exposes lifecycle hooks modeled on vanilla mechanics:

Player & Item Interaction Hooks

Hook Signature Description
normal_use (&self, args: NormalUseArgs<'_>) -> BlockActionResult Triggered when right-clicking the block with an empty hand.
use_with_item (&self, args: UseWithItemArgs<'_>) -> BlockActionResult Triggered when right-clicking the block while holding an item.
get_screen_handler_factory (&self, args: GetScreenHandlerFactoryArgs<'_>) -> Option<Box<dyn ScreenHandlerFactory>> Returns an inventory container factory to open a UI screen (e.g., Chests, Crafting Tables).

World Lifecycle & Physics Hooks

Hook Signature Description
placed (&self, args: PlacedArgs<'_>) Triggered immediately after the block is placed in the world.
broken (&self, args: BrokenArgs<'_>) Triggered when broken by a player or game mechanics.
on_entity_collision (&self, args: OnEntityCollisionArgs<'_>) Triggered when an entity walks inside, steps on, or falls into the block.
on_neighbor_update (&self, args: OnNeighborUpdateArgs<'_>) Triggered when an adjacent block state updates or is destroyed.
on_projectile_hit (&self, args: OnProjectileHitArgs<'_>) Triggered when an arrow, snowball, or projectile strikes the block.

Redstone & Power Hooks

Hook Signature Description
emits_redstone_power (&self, state_id: BlockStateId) -> bool Declares whether the block emits redstone power to neighbors.
get_weak_redstone_power (&self, ...) -> u8 Returns emitted weak power level (0–15).
get_strong_redstone_power (&self, ...) -> u8 Returns emitted strong direct power level (0–15).

Foliage & Bonemeal Hooks

Hook Signature Description
is_valid_bonemeal_target (&self, args: BonemealArgs<'_>) -> bool Checks if bone meal can be applied (e.g., crop not fully grown).
is_bonemeal_success (&self, args: BonemealArgs<'_>) -> bool Evaluates random chance roll for bone meal success.
perform_bonemeal (&self, args: BonemealArgs<'_>) Advances growth stages or spawns adjacent flora.

Step 3: Modifying Block States

All block properties are strongly typed in pumpkin_data::block_properties:

use pumpkin_data::block_properties::{BedLikeProperties, Part};

// 1. Read typed properties from the numeric BlockStateId
let part = BedLikeProperties::part(state_id); // Part::Head or Part::Foot
let is_occupied = BedLikeProperties::occupied(state_id);

// 2. Generate a new BlockStateId with updated properties
let updated_state = BedLikeProperties::with_occupied(state_id, true);

// 3. Commit the updated state to the world
world.set_block_state(pos, updated_state).await;

Step 4: Adding a Block Entity (Tile Entity)

If your block needs to store persistent custom state (inventories, custom text, timers):

  1. Create a block entity struct in crates/pumpkin/src/block/entities/:

    use pumpkin_util::math::position::BlockPos;
    use crate::block::entities::BlockEntity;
    
    pub struct CustomBlockEntity {
        pub pos: BlockPos,
        pub custom_counter: u32,
    }
    
    impl BlockEntity for CustomBlockEntity {
        fn to_nbt(&self) -> pumpkin_nbt::compound::NbtCompound {
            let mut compound = pumpkin_nbt::compound::NbtCompound::new();
            compound.put_int("Counter", self.custom_counter as i32);
            compound
        }
    
        fn from_nbt(&mut self, tag: &pumpkin_nbt::compound::NbtCompound) {
            if let Some(val) = tag.get_int("Counter") {
                self.custom_counter = val as u32;
            }
        }
    
        fn tick(&mut self, world: &std::sync::Arc<crate::world::World>, pos: &BlockPos) {
            // Periodic logic per game tick
        }
    }
    
  2. Link the block entity in your BlockBehaviour:

    impl BlockBehaviour for CustomBlock {
        fn create_block_entity(&self, pos: BlockPos) -> Option<Arc<Mutex<dyn BlockEntity>>> {
            Some(Arc::new(Mutex::new(CustomBlockEntity::new(pos))))
        }
    }
    

Step 5: Registering the Block

  1. Export your module in crates/pumpkin/src/block/blocks/mod.rs:

    pub mod doors;
    
  2. Register the behavior struct in crates/pumpkin/src/block/registry.rs:

    manager.register(DoorBlock);
    

At startup, manager.register automatically reads the block IDs declared in #[pumpkin_block(...)] and routes interactions to your implementation.