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 consume_effect;
30mod context;
31pub mod fluid;
32mod item;
33pub(crate) mod item_utils;
34pub mod items;
35mod mob_effect;
36
37#[expect(warnings)]
38#[rustfmt::skip]
39#[path = "generated/blocks.rs"]
40pub mod block_behaviors;
41
42#[expect(warnings)]
43#[rustfmt::skip]
44#[path = "generated/candle_cakes.rs"]
45pub mod candle_cakes;
46
47#[allow(warnings)]
48#[rustfmt::skip]
49#[path = "generated/items.rs"]
50pub mod item_behaviors;
51
52#[expect(warnings)]
53#[rustfmt::skip]
54#[path = "generated/waxables.rs"]
55pub mod waxables;
56
57#[expect(warnings)]
58#[rustfmt::skip]
59#[path = "generated/weathering.rs"]
60pub mod weathering;
61
62#[expect(warnings)]
63#[rustfmt::skip]
64#[path = "generated/skull_types.rs"]
65pub mod skull_types;
66
67pub use block::{
68    BlockBehavior, BlockBehaviorRegistry, BlockCollisionBoxes, BlockCollisionContext,
69    BlockEntityCreation, BlockLootContext, BrushableData, DefaultBlockBehavior, EntityFallDamage,
70    EntityFallOnContext, EntityFallOnFacts, EntityLandingContext, Fallable, RailBehavior,
71};
72pub(crate) use block::{pickup_waterlogged_block, try_drop_experience};
73use block_behaviors::register_block_behaviors;
74pub use consume_effect::{CONSUME_EFFECT_BEHAVIORS, ConsumeEffectBehaviorRegistry};
75pub use context::{
76    BlockHitResult, BlockPlaceContext, InteractionResult, InventoryAccess, InventoryTickContext,
77    PlacementOrientation, PlacementSource, UseItemContext, UseOnContext,
78};
79pub use fluid::{FLUID_BEHAVIORS, FluidBehaviorRegistry};
80pub(crate) use item::apply_use_remainder;
81pub use item::{ItemBehavior, ItemBehaviorRegistry, ItemUseAnimation, SignApplicator};
82use item_behaviors::register_item_behaviors;
83pub use items::{
84    BlockItem, BucketItem, DefaultItemBehavior, DoubleHighBlockItem, EnderEyeItem, HangingSignItem,
85    ScaffoldingBlockItem, ShieldItem, SolidBucketItem, StandingAndWallBlockItem,
86};
87pub use mob_effect::{MOB_EFFECT_BEHAVIORS, MobEffectBehaviorRegistry};
88use std::ops::Deref;
89use std::sync::OnceLock;
90use steel_registry::blocks::BlockRef;
91use steel_registry::blocks::block_state_ext::BlockStateExt;
92use steel_registry::consume_effect::vanilla_consume_effect_types;
93use steel_registry::fluid::FluidRef;
94use steel_registry::vanilla_fluids;
95use steel_registry::vanilla_mob_effects;
96use steel_utils::BlockStateId;
97
98use crate::entity::ai::path::PathComputationType;
99use crate::entity::consume_effect::{
100    ApplyEffectsBehavior, ClearAllEffectsBehavior, ConsumeEffectBehavior, PlaySoundBehavior,
101    RemoveEffectsBehavior, TeleportRandomlyBehavior,
102};
103use crate::entity::mob_effect::{
104    AbsorptionBehavior, BadOmenBehavior, HealOrHarmBehavior, HungerBehavior, InfestedBehavior,
105    MobEffectBehavior, OozingBehavior, PoisonBehavior, RaidOmenBehavior, RegenerationBehavior,
106    SaturationBehavior, WeavingBehavior, WindChargedBehavior, WitherBehavior,
107};
108use crate::fluid::{FluidBehavior, LavaFluid, WaterFluid};
109
110/// Wrapper for the global block behavior registry that implements `Deref`.
111pub struct BlockBehaviorLock(OnceLock<BlockBehaviorRegistry>);
112
113impl Deref for BlockBehaviorLock {
114    type Target = BlockBehaviorRegistry;
115
116    fn deref(&self) -> &Self::Target {
117        self.0.get().expect("Block behaviors not initialized")
118    }
119}
120
121/// Wrapper for the global item behavior registry that implements `Deref`.
122pub struct ItemBehaviorLock(OnceLock<ItemBehaviorRegistry>);
123
124impl Deref for ItemBehaviorLock {
125    type Target = ItemBehaviorRegistry;
126
127    fn deref(&self) -> &Self::Target {
128        self.0.get().expect("Item behaviors not initialized")
129    }
130}
131
132/// Extension trait for `BlockStateId` that provides access to behavior-dependent methods.
133///
134/// This is separate from `BlockStateExt` (in steel-registry) because these methods
135/// require access to the behavior registry which lives in steel-core.
136pub trait BlockStateBehaviorExt {
137    /// Returns whether this block state can hold a fluid inside it, such as
138    /// a waterlogged block, cauldron, or seagrass.
139    fn is_liquid_container(&self) -> bool;
140
141    /// Returns whether this block state can be replaced by the given fluid block.
142    fn can_be_replaced_by_fluid(&self, fluid: FluidRef) -> bool;
143
144    /// Returns whether this block state can be replaced in this placement context.
145    fn can_be_replaced(&self, context: &BlockPlaceContext<'_>) -> bool;
146
147    /// Returns whether this block state is pathfindable for the supplied vanilla computation type.
148    fn is_pathfindable(&self, computation_type: PathComputationType) -> bool;
149
150    /// Returns whether this block state extends `BedBlock`
151    fn is_bed(&self) -> bool;
152
153    /// Returns whether this block state extends `LiquidBlock`.
154    fn is_liquid_block(&self) -> bool;
155
156    /// Returns whether this block state can be occupied by a forced respawn position
157    fn is_possible_to_respawn_in_this(&self) -> bool;
158}
159
160impl BlockStateBehaviorExt for BlockStateId {
161    fn is_liquid_container(&self) -> bool {
162        let block = self.get_block();
163        let behavior = BLOCK_BEHAVIORS.get_behavior(block);
164        behavior.is_liquid_container(*self)
165    }
166
167    fn can_be_replaced_by_fluid(&self, fluid: FluidRef) -> bool {
168        let block = self.get_block();
169        let behavior = BLOCK_BEHAVIORS.get_behavior(block);
170        behavior.can_be_replaced_by_fluid(*self, fluid)
171    }
172
173    fn can_be_replaced(&self, context: &BlockPlaceContext<'_>) -> bool {
174        let block = self.get_block();
175        let behavior = BLOCK_BEHAVIORS.get_behavior(block);
176        behavior.can_be_replaced(*self, context)
177    }
178
179    fn is_pathfindable(&self, computation_type: PathComputationType) -> bool {
180        let block = self.get_block();
181        let behavior = BLOCK_BEHAVIORS.get_behavior(block);
182        behavior.is_pathfindable(*self, computation_type)
183    }
184
185    fn is_bed(&self) -> bool {
186        let block = self.get_block();
187        BLOCK_BEHAVIORS.get_behavior(block).is_bed()
188    }
189
190    fn is_liquid_block(&self) -> bool {
191        let block = self.get_block();
192        BLOCK_BEHAVIORS.get_behavior(block).is_liquid_block()
193    }
194
195    fn is_possible_to_respawn_in_this(&self) -> bool {
196        let block = self.get_block();
197        let behavior = BLOCK_BEHAVIORS.get_behavior(block);
198        behavior.is_possible_to_respawn_in_this(*self)
199    }
200}
201
202/// Global block behavior registry.
203///
204/// Access behaviors directly via deref: `BLOCK_BEHAVIORS.get_behavior(block)`
205pub static BLOCK_BEHAVIORS: BlockBehaviorLock = BlockBehaviorLock(OnceLock::new());
206
207/// Global item behavior registry.
208///
209/// Access behaviors directly via deref: `ITEM_BEHAVIORS.get_behavior(item)`
210pub static ITEM_BEHAVIORS: ItemBehaviorLock = ItemBehaviorLock(OnceLock::new());
211
212/// Initializes the global behavior registries.
213///
214/// This should be called after the main registry is frozen. Repeated calls are a no-op.
215pub fn init_behaviors() {
216    BLOCK_BEHAVIORS.0.get_or_init(|| {
217        let mut block_behaviors = BlockBehaviorRegistry::new();
218        register_block_behaviors(&mut block_behaviors);
219        block_behaviors
220    });
221
222    FLUID_BEHAVIORS.0.get_or_init(|| {
223        let mut fluid_behaviors = FluidBehaviorRegistry::new();
224
225        // Water: WaterFluid implements FluidBehavior directly
226        let water_behavior: Box<dyn FluidBehavior> = Box::new(WaterFluid);
227        // Both WATER and FLOWING_WATER share the same behavior
228        fluid_behaviors.set_behavior(&vanilla_fluids::WATER, water_behavior);
229        fluid_behaviors.set_behavior(&vanilla_fluids::FLOWING_WATER, Box::new(WaterFluid));
230
231        // Lava: LavaFluid implements FluidBehavior directly
232        let lava_behavior: Box<dyn FluidBehavior> = Box::new(LavaFluid);
233        fluid_behaviors.set_behavior(&vanilla_fluids::LAVA, lava_behavior);
234        fluid_behaviors.set_behavior(&vanilla_fluids::FLOWING_LAVA, Box::new(LavaFluid));
235
236        fluid_behaviors
237    });
238
239    ITEM_BEHAVIORS.0.get_or_init(|| {
240        let mut item_behaviors = ItemBehaviorRegistry::new();
241        register_item_behaviors(&mut item_behaviors);
242        item_behaviors
243    });
244
245    CONSUME_EFFECT_BEHAVIORS.0.get_or_init(|| {
246        let mut consume_effect_behaviors = ConsumeEffectBehaviorRegistry::new();
247
248        let apply_effects: Box<dyn ConsumeEffectBehavior> = Box::new(ApplyEffectsBehavior);
249        consume_effect_behaviors
250            .set_behavior(&vanilla_consume_effect_types::APPLY_EFFECTS, apply_effects);
251
252        let remove_effects: Box<dyn ConsumeEffectBehavior> = Box::new(RemoveEffectsBehavior);
253        consume_effect_behaviors.set_behavior(
254            &vanilla_consume_effect_types::REMOVE_EFFECTS,
255            remove_effects,
256        );
257
258        let clear_all_effects: Box<dyn ConsumeEffectBehavior> = Box::new(ClearAllEffectsBehavior);
259        consume_effect_behaviors.set_behavior(
260            &vanilla_consume_effect_types::CLEAR_ALL_EFFECTS,
261            clear_all_effects,
262        );
263
264        let play_sound: Box<dyn ConsumeEffectBehavior> = Box::new(PlaySoundBehavior);
265        consume_effect_behaviors
266            .set_behavior(&vanilla_consume_effect_types::PLAY_SOUND, play_sound);
267
268        let teleport_randomly: Box<dyn ConsumeEffectBehavior> = Box::new(TeleportRandomlyBehavior);
269        consume_effect_behaviors.set_behavior(
270            &vanilla_consume_effect_types::TELEPORT_RANDOMLY,
271            teleport_randomly,
272        );
273
274        consume_effect_behaviors
275    });
276
277    MOB_EFFECT_BEHAVIORS.0.get_or_init(|| {
278        let mut mob_effect_behaviors = MobEffectBehaviorRegistry::new();
279
280        // HealOrHarmMobEffect backs both Instant Health and Instant Damage.
281        let instant_health: Box<dyn MobEffectBehavior> =
282            Box::new(HealOrHarmBehavior { is_harm: false });
283        mob_effect_behaviors.set_behavior(vanilla_mob_effects::INSTANT_HEALTH, instant_health);
284
285        let instant_damage: Box<dyn MobEffectBehavior> =
286            Box::new(HealOrHarmBehavior { is_harm: true });
287        mob_effect_behaviors.set_behavior(vanilla_mob_effects::INSTANT_DAMAGE, instant_damage);
288
289        let saturation: Box<dyn MobEffectBehavior> = Box::new(SaturationBehavior);
290        mob_effect_behaviors.set_behavior(vanilla_mob_effects::SATURATION, saturation);
291
292        let wither: Box<dyn MobEffectBehavior> = Box::new(WitherBehavior);
293        mob_effect_behaviors.set_behavior(vanilla_mob_effects::WITHER, wither);
294
295        let poison: Box<dyn MobEffectBehavior> = Box::new(PoisonBehavior);
296        mob_effect_behaviors.set_behavior(vanilla_mob_effects::POISON, poison);
297
298        let regeneration: Box<dyn MobEffectBehavior> = Box::new(RegenerationBehavior);
299        mob_effect_behaviors.set_behavior(vanilla_mob_effects::REGENERATION, regeneration);
300
301        let hunger: Box<dyn MobEffectBehavior> = Box::new(HungerBehavior);
302        mob_effect_behaviors.set_behavior(vanilla_mob_effects::HUNGER, hunger);
303
304        let absorption: Box<dyn MobEffectBehavior> = Box::new(AbsorptionBehavior);
305        mob_effect_behaviors.set_behavior(vanilla_mob_effects::ABSORPTION, absorption);
306
307        let bad_omen: Box<dyn MobEffectBehavior> = Box::new(BadOmenBehavior);
308        mob_effect_behaviors.set_behavior(vanilla_mob_effects::BAD_OMEN, bad_omen);
309
310        let raid_omen: Box<dyn MobEffectBehavior> = Box::new(RaidOmenBehavior);
311        mob_effect_behaviors.set_behavior(vanilla_mob_effects::RAID_OMEN, raid_omen);
312
313        let oozing: Box<dyn MobEffectBehavior> = Box::new(OozingBehavior);
314        mob_effect_behaviors.set_behavior(vanilla_mob_effects::OOZING, oozing);
315
316        let weaving: Box<dyn MobEffectBehavior> = Box::new(WeavingBehavior);
317        mob_effect_behaviors.set_behavior(vanilla_mob_effects::WEAVING, weaving);
318
319        let wind_charged: Box<dyn MobEffectBehavior> = Box::new(WindChargedBehavior);
320        mob_effect_behaviors.set_behavior(vanilla_mob_effects::WIND_CHARGED, wind_charged);
321
322        let infested: Box<dyn MobEffectBehavior> = Box::new(InfestedBehavior);
323        mob_effect_behaviors.set_behavior(vanilla_mob_effects::INFESTED, infested);
324
325        mob_effect_behaviors
326    });
327}