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/strippables.rs"]
55pub mod strippables;
56
57#[expect(warnings)]
58#[rustfmt::skip]
59#[path = "generated/waxables.rs"]
60pub mod waxables;
61
62#[expect(warnings)]
63#[rustfmt::skip]
64#[path = "generated/weathering.rs"]
65pub mod weathering;
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, PlacementOrientation,
77    PlacementSource, UseItemContext, UseOnContext,
78};
79pub use fluid::{FLUID_BEHAVIORS, FluidBehaviorRegistry};
80pub(crate) use item::apply_use_remainder;
81pub use item::{ItemBehavior, ItemBehaviorRegistry, ItemUseAnimation};
82use item_behaviors::register_item_behaviors;
83pub use items::{
84    BedItem, BlockItem, BucketItem, DefaultItemBehavior, DoubleHighBlockItem, EnderEyeItem,
85    HangingSignItem, ScaffoldingBlockItem, ShovelItem, SignItem, SolidBucketItem,
86    StandingAndWallBlockItem,
87};
88pub use mob_effect::{MOB_EFFECT_BEHAVIORS, MobEffectBehaviorRegistry};
89use std::ops::Deref;
90use std::sync::OnceLock;
91use steel_registry::blocks::BlockRef;
92use steel_registry::blocks::block_state_ext::BlockStateExt;
93use steel_registry::consume_effect::vanilla_consume_effect_types;
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 belongs to a vanilla `LiquidBlockContainer`.
138    fn is_liquid_container(&self) -> bool;
139
140    /// Returns whether this block state can be replaced by the given fluid block.
141    fn can_be_replaced_by_fluid(&self, fluid_block: BlockRef) -> bool;
142
143    /// Returns whether this block state can be replaced in this placement context.
144    fn can_be_replaced(&self, context: &BlockPlaceContext<'_>) -> bool;
145
146    /// Returns whether this block state is pathfindable for the supplied vanilla computation type.
147    fn is_pathfindable(&self, computation_type: PathComputationType) -> bool;
148
149    /// Returns whether this block state extends `BedBlock`
150    fn is_bed(&self) -> bool;
151
152    /// Returns whether this block state extends `LiquidBlock`.
153    fn is_liquid_block(&self) -> bool;
154
155    /// Returns whether this block state can be occupied by a forced respawn position
156    fn is_possible_to_respawn_in_this(&self) -> bool;
157}
158
159impl BlockStateBehaviorExt for BlockStateId {
160    fn is_liquid_container(&self) -> bool {
161        let block = self.get_block();
162        let behavior = BLOCK_BEHAVIORS.get_behavior(block);
163        behavior.is_liquid_container(*self)
164    }
165
166    fn can_be_replaced_by_fluid(&self, fluid_block: BlockRef) -> bool {
167        let block = self.get_block();
168        let behavior = BLOCK_BEHAVIORS.get_behavior(block);
169        behavior.can_be_replaced_by_fluid(*self, fluid_block)
170    }
171
172    fn can_be_replaced(&self, context: &BlockPlaceContext<'_>) -> bool {
173        let block = self.get_block();
174        let behavior = BLOCK_BEHAVIORS.get_behavior(block);
175        behavior.can_be_replaced(*self, context)
176    }
177
178    fn is_pathfindable(&self, computation_type: PathComputationType) -> bool {
179        let block = self.get_block();
180        let behavior = BLOCK_BEHAVIORS.get_behavior(block);
181        behavior.is_pathfindable(*self, computation_type)
182    }
183
184    fn is_bed(&self) -> bool {
185        let block = self.get_block();
186        BLOCK_BEHAVIORS.get_behavior(block).is_bed()
187    }
188
189    fn is_liquid_block(&self) -> bool {
190        let block = self.get_block();
191        BLOCK_BEHAVIORS.get_behavior(block).is_liquid_block()
192    }
193
194    fn is_possible_to_respawn_in_this(&self) -> bool {
195        let block = self.get_block();
196        let behavior = BLOCK_BEHAVIORS.get_behavior(block);
197        behavior.is_possible_to_respawn_in_this(*self)
198    }
199}
200
201/// Global block behavior registry.
202///
203/// Access behaviors directly via deref: `BLOCK_BEHAVIORS.get_behavior(block)`
204pub static BLOCK_BEHAVIORS: BlockBehaviorLock = BlockBehaviorLock(OnceLock::new());
205
206/// Global item behavior registry.
207///
208/// Access behaviors directly via deref: `ITEM_BEHAVIORS.get_behavior(item)`
209pub static ITEM_BEHAVIORS: ItemBehaviorLock = ItemBehaviorLock(OnceLock::new());
210
211/// Initializes the global behavior registries.
212///
213/// This should be called after the main registry is frozen. Repeated calls are a no-op.
214pub fn init_behaviors() {
215    BLOCK_BEHAVIORS.0.get_or_init(|| {
216        let mut block_behaviors = BlockBehaviorRegistry::new();
217        register_block_behaviors(&mut block_behaviors);
218        block_behaviors
219    });
220
221    FLUID_BEHAVIORS.0.get_or_init(|| {
222        let mut fluid_behaviors = FluidBehaviorRegistry::new();
223
224        // Water: WaterFluid implements FluidBehavior directly
225        let water_behavior: Box<dyn FluidBehavior> = Box::new(WaterFluid);
226        // Both WATER and FLOWING_WATER share the same behavior
227        fluid_behaviors.set_behavior(&vanilla_fluids::WATER, water_behavior);
228        fluid_behaviors.set_behavior(&vanilla_fluids::FLOWING_WATER, Box::new(WaterFluid));
229
230        // Lava: LavaFluid implements FluidBehavior directly
231        let lava_behavior: Box<dyn FluidBehavior> = Box::new(LavaFluid);
232        fluid_behaviors.set_behavior(&vanilla_fluids::LAVA, lava_behavior);
233        fluid_behaviors.set_behavior(&vanilla_fluids::FLOWING_LAVA, Box::new(LavaFluid));
234
235        fluid_behaviors
236    });
237
238    ITEM_BEHAVIORS.0.get_or_init(|| {
239        let mut item_behaviors = ItemBehaviorRegistry::new();
240        register_item_behaviors(&mut item_behaviors);
241        item_behaviors
242    });
243
244    CONSUME_EFFECT_BEHAVIORS.0.get_or_init(|| {
245        let mut consume_effect_behaviors = ConsumeEffectBehaviorRegistry::new();
246
247        let apply_effects: Box<dyn ConsumeEffectBehavior> = Box::new(ApplyEffectsBehavior);
248        consume_effect_behaviors
249            .set_behavior(&vanilla_consume_effect_types::APPLY_EFFECTS, apply_effects);
250
251        let remove_effects: Box<dyn ConsumeEffectBehavior> = Box::new(RemoveEffectsBehavior);
252        consume_effect_behaviors.set_behavior(
253            &vanilla_consume_effect_types::REMOVE_EFFECTS,
254            remove_effects,
255        );
256
257        let clear_all_effects: Box<dyn ConsumeEffectBehavior> = Box::new(ClearAllEffectsBehavior);
258        consume_effect_behaviors.set_behavior(
259            &vanilla_consume_effect_types::CLEAR_ALL_EFFECTS,
260            clear_all_effects,
261        );
262
263        let play_sound: Box<dyn ConsumeEffectBehavior> = Box::new(PlaySoundBehavior);
264        consume_effect_behaviors
265            .set_behavior(&vanilla_consume_effect_types::PLAY_SOUND, play_sound);
266
267        let teleport_randomly: Box<dyn ConsumeEffectBehavior> = Box::new(TeleportRandomlyBehavior);
268        consume_effect_behaviors.set_behavior(
269            &vanilla_consume_effect_types::TELEPORT_RANDOMLY,
270            teleport_randomly,
271        );
272
273        consume_effect_behaviors
274    });
275
276    MOB_EFFECT_BEHAVIORS.0.get_or_init(|| {
277        let mut mob_effect_behaviors = MobEffectBehaviorRegistry::new();
278
279        // HealOrHarmMobEffect backs both Instant Health and Instant Damage.
280        let instant_health: Box<dyn MobEffectBehavior> =
281            Box::new(HealOrHarmBehavior { is_harm: false });
282        mob_effect_behaviors.set_behavior(vanilla_mob_effects::INSTANT_HEALTH, instant_health);
283
284        let instant_damage: Box<dyn MobEffectBehavior> =
285            Box::new(HealOrHarmBehavior { is_harm: true });
286        mob_effect_behaviors.set_behavior(vanilla_mob_effects::INSTANT_DAMAGE, instant_damage);
287
288        let saturation: Box<dyn MobEffectBehavior> = Box::new(SaturationBehavior);
289        mob_effect_behaviors.set_behavior(vanilla_mob_effects::SATURATION, saturation);
290
291        let wither: Box<dyn MobEffectBehavior> = Box::new(WitherBehavior);
292        mob_effect_behaviors.set_behavior(vanilla_mob_effects::WITHER, wither);
293
294        let poison: Box<dyn MobEffectBehavior> = Box::new(PoisonBehavior);
295        mob_effect_behaviors.set_behavior(vanilla_mob_effects::POISON, poison);
296
297        let regeneration: Box<dyn MobEffectBehavior> = Box::new(RegenerationBehavior);
298        mob_effect_behaviors.set_behavior(vanilla_mob_effects::REGENERATION, regeneration);
299
300        let hunger: Box<dyn MobEffectBehavior> = Box::new(HungerBehavior);
301        mob_effect_behaviors.set_behavior(vanilla_mob_effects::HUNGER, hunger);
302
303        let absorption: Box<dyn MobEffectBehavior> = Box::new(AbsorptionBehavior);
304        mob_effect_behaviors.set_behavior(vanilla_mob_effects::ABSORPTION, absorption);
305
306        let bad_omen: Box<dyn MobEffectBehavior> = Box::new(BadOmenBehavior);
307        mob_effect_behaviors.set_behavior(vanilla_mob_effects::BAD_OMEN, bad_omen);
308
309        let raid_omen: Box<dyn MobEffectBehavior> = Box::new(RaidOmenBehavior);
310        mob_effect_behaviors.set_behavior(vanilla_mob_effects::RAID_OMEN, raid_omen);
311
312        let oozing: Box<dyn MobEffectBehavior> = Box::new(OozingBehavior);
313        mob_effect_behaviors.set_behavior(vanilla_mob_effects::OOZING, oozing);
314
315        let weaving: Box<dyn MobEffectBehavior> = Box::new(WeavingBehavior);
316        mob_effect_behaviors.set_behavior(vanilla_mob_effects::WEAVING, weaving);
317
318        let wind_charged: Box<dyn MobEffectBehavior> = Box::new(WindChargedBehavior);
319        mob_effect_behaviors.set_behavior(vanilla_mob_effects::WIND_CHARGED, wind_charged);
320
321        let infested: Box<dyn MobEffectBehavior> = Box::new(InfestedBehavior);
322        mob_effect_behaviors.set_behavior(vanilla_mob_effects::INFESTED, infested);
323
324        mob_effect_behaviors
325    });
326}