# 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//.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: ```rust 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>` | 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`: ```rust 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/`: ```rust 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, pos: &BlockPos) { // Periodic logic per game tick } } ``` 2. Link the block entity in your `BlockBehaviour`: ```rust impl BlockBehaviour for CustomBlock { fn create_block_entity(&self, pos: BlockPos) -> Option>> { 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`: ```rust pub mod doors; ``` 2. Register the behavior struct in `crates/pumpkin/src/block/registry.rs`: ```rust manager.register(DoorBlock); ``` At startup, `manager.register` automatically reads the block IDs declared in `#[pumpkin_block(...)]` and routes interactions to your implementation.