Skip to main content

steel_core/behavior/
mod.rs

1//! Block and item behavior system.
2//!
3//! This module contains the behavior traits and registries that define how
4//! blocks and items behave dynamically. This is separate from the static data
5//! in steel-registry to maintain a clean separation between constant data and
6//! functional/dynamic behavior.
7//!
8//! # Architecture
9//!
10//! After the main registry (`steel-registry`) is frozen, behavior registries
11//! are created:
12//! - `BlockBehaviorRegistry` - assigns default or custom behaviors to each block
13//! - `ItemBehaviorRegistry` - assigns default or custom behaviors to each item
14//!
15//! # Usage
16//!
17//! ```ignore
18//! use steel_core::behavior::{init_behaviors, BLOCK_BEHAVIORS, ITEM_BEHAVIORS};
19//!
20//! // After registry is frozen, call once at startup:
21//! init_behaviors();
22//!
23//! // Then access behaviors via the global registries:
24//! let behavior = BLOCK_BEHAVIORS.get_behavior(block);
25//! ```
26
27mod block;
28pub mod blocks;
29mod context;
30pub mod fluid;
31mod item;
32pub(crate) mod item_utils;
33pub mod items;
34
35#[expect(warnings)]
36#[rustfmt::skip]
37#[path = "generated/blocks.rs"]
38pub mod block_behaviors;
39
40#[expect(warnings)]
41#[rustfmt::skip]
42#[path = "generated/candle_cakes.rs"]
43pub mod candle_cakes;
44
45#[allow(warnings)]
46#[rustfmt::skip]
47#[path = "generated/items.rs"]
48pub mod item_behaviors;
49
50#[expect(warnings)]
51#[rustfmt::skip]
52#[path = "generated/strippables.rs"]
53pub mod strippables;
54
55#[expect(warnings)]
56#[rustfmt::skip]
57#[path = "generated/waxables.rs"]
58pub mod waxables;
59
60#[expect(warnings)]
61#[rustfmt::skip]
62#[path = "generated/weathering.rs"]
63pub mod weathering;
64
65pub(crate) use block::pickup_waterlogged_block;
66pub use block::{
67    BlockBehavior, BlockBehaviorRegistry, BlockCollisionBoxes, BlockCollisionContext,
68    BlockEntityCreation, BlockLootContext, DefaultBlockBehavior, EntityFallDamage,
69    EntityFallOnContext, EntityFallOnFacts, EntityLandingContext, RailBehavior,
70};
71use block_behaviors::register_block_behaviors;
72pub use context::{
73    BlockHitResult, BlockPlaceContext, InteractionResult, InventoryAccess, PlacementOrientation,
74    PlacementSource, UseItemContext, UseOnContext,
75};
76pub use fluid::{FLUID_BEHAVIORS, FluidBehaviorRegistry};
77pub use item::{ItemBehavior, ItemBehaviorRegistry};
78use item_behaviors::register_item_behaviors;
79pub use items::{
80    BlockItem, BucketItem, DefaultItemBehavior, DoubleHighBlockItem, EnderEyeItem, HangingSignItem,
81    ShovelItem, SignItem, StandingAndWallBlockItem,
82};
83use std::ops::Deref;
84use std::sync::OnceLock;
85use steel_registry::blocks::BlockRef;
86use steel_registry::blocks::block_state_ext::BlockStateExt;
87use steel_registry::vanilla_fluids;
88use steel_utils::BlockStateId;
89
90use crate::entity::ai::path::PathComputationType;
91use crate::fluid::{FluidBehavior, LavaFluid, WaterFluid};
92
93/// Wrapper for the global block behavior registry that implements `Deref`.
94pub struct BlockBehaviorLock(OnceLock<BlockBehaviorRegistry>);
95
96impl Deref for BlockBehaviorLock {
97    type Target = BlockBehaviorRegistry;
98
99    fn deref(&self) -> &Self::Target {
100        self.0.get().expect("Block behaviors not initialized")
101    }
102}
103
104/// Wrapper for the global item behavior registry that implements `Deref`.
105pub struct ItemBehaviorLock(OnceLock<ItemBehaviorRegistry>);
106
107impl Deref for ItemBehaviorLock {
108    type Target = ItemBehaviorRegistry;
109
110    fn deref(&self) -> &Self::Target {
111        self.0.get().expect("Item behaviors not initialized")
112    }
113}
114
115/// Extension trait for `BlockStateId` that provides access to behavior-dependent methods.
116///
117/// This is separate from `BlockStateExt` (in steel-registry) because these methods
118/// require access to the behavior registry which lives in steel-core.
119pub trait BlockStateBehaviorExt {
120    /// Returns whether this block state belongs to a vanilla `LiquidBlockContainer`.
121    fn is_liquid_container(&self) -> bool;
122
123    /// Returns whether this block state can be replaced by the given fluid block.
124    fn can_be_replaced_by_fluid(&self, fluid_block: BlockRef) -> bool;
125
126    /// Returns whether this block state can be replaced in this placement context.
127    fn can_be_replaced(&self, context: &BlockPlaceContext<'_>) -> bool;
128
129    /// Returns whether this block state is pathfindable for the supplied vanilla computation type.
130    fn is_pathfindable(&self, computation_type: PathComputationType) -> bool;
131}
132
133impl BlockStateBehaviorExt for BlockStateId {
134    fn is_liquid_container(&self) -> bool {
135        let block = self.get_block();
136        let behavior = BLOCK_BEHAVIORS.get_behavior(block);
137        behavior.is_liquid_container(*self)
138    }
139
140    fn can_be_replaced_by_fluid(&self, fluid_block: BlockRef) -> bool {
141        let block = self.get_block();
142        let behavior = BLOCK_BEHAVIORS.get_behavior(block);
143        behavior.can_be_replaced_by_fluid(*self, fluid_block)
144    }
145
146    fn can_be_replaced(&self, context: &BlockPlaceContext<'_>) -> bool {
147        let block = self.get_block();
148        let behavior = BLOCK_BEHAVIORS.get_behavior(block);
149        behavior.can_be_replaced(*self, context)
150    }
151
152    fn is_pathfindable(&self, computation_type: PathComputationType) -> bool {
153        let block = self.get_block();
154        let behavior = BLOCK_BEHAVIORS.get_behavior(block);
155        behavior.is_pathfindable(*self, computation_type)
156    }
157}
158
159/// Global block behavior registry.
160///
161/// Access behaviors directly via deref: `BLOCK_BEHAVIORS.get_behavior(block)`
162pub static BLOCK_BEHAVIORS: BlockBehaviorLock = BlockBehaviorLock(OnceLock::new());
163
164/// Global item behavior registry.
165///
166/// Access behaviors directly via deref: `ITEM_BEHAVIORS.get_behavior(item)`
167pub static ITEM_BEHAVIORS: ItemBehaviorLock = ItemBehaviorLock(OnceLock::new());
168
169/// Initializes the global behavior registries.
170///
171/// This should be called after the main registry is frozen. Repeated calls are a no-op.
172pub fn init_behaviors() {
173    BLOCK_BEHAVIORS.0.get_or_init(|| {
174        let mut block_behaviors = BlockBehaviorRegistry::new();
175        register_block_behaviors(&mut block_behaviors);
176        block_behaviors
177    });
178
179    FLUID_BEHAVIORS.0.get_or_init(|| {
180        let mut fluid_behaviors = FluidBehaviorRegistry::new();
181
182        // Water: WaterFluid implements FluidBehavior directly
183        let water_behavior: Box<dyn FluidBehavior> = Box::new(WaterFluid);
184        // Both WATER and FLOWING_WATER share the same behavior
185        fluid_behaviors.set_behavior(&vanilla_fluids::WATER, water_behavior);
186        fluid_behaviors.set_behavior(&vanilla_fluids::FLOWING_WATER, Box::new(WaterFluid));
187
188        // Lava: LavaFluid implements FluidBehavior directly
189        let lava_behavior: Box<dyn FluidBehavior> = Box::new(LavaFluid);
190        fluid_behaviors.set_behavior(&vanilla_fluids::LAVA, lava_behavior);
191        fluid_behaviors.set_behavior(&vanilla_fluids::FLOWING_LAVA, Box::new(LavaFluid));
192
193        fluid_behaviors
194    });
195
196    ITEM_BEHAVIORS.0.get_or_init(|| {
197        let mut item_behaviors = ItemBehaviorRegistry::new();
198        register_item_behaviors(&mut item_behaviors);
199        item_behaviors
200    });
201}