Skip to main content

steel_core/behavior/blocks/falling/
falling_block.rs

1use std::sync::Arc;
2
3use steel_macros::block_behavior;
4use steel_registry::blocks::properties::Direction;
5use steel_registry::blocks::{BlockRef, block_state_ext::BlockStateExt as _};
6use steel_registry::vanilla_block_tags::BlockTag;
7use steel_utils::{BlockPos, BlockStateId};
8
9use crate::behavior::{BlockBehavior, BlockPlaceContext, Fallable};
10use crate::entity::entities::FallingBlockEntity;
11use crate::world::{ScheduledTickAccess, World};
12
13const FALL_DELAY: i32 = 2;
14
15/// Shared server-side behavior of vanilla `FallingBlock`.
16///
17/// This also implements `ColoredFallingBlock`, whose only additional vanilla behavior is the
18/// client-local falling-dust particle.
19#[block_behavior(class = "ColoredFallingBlock")]
20pub struct FallingBlock {
21    block: BlockRef,
22}
23
24impl FallingBlock {
25    /// Creates the shared behavior for one registered falling block.
26    #[must_use]
27    pub const fn new(block: BlockRef) -> Self {
28        Self { block }
29    }
30
31    /// Returns this behavior's registered block.
32    #[must_use]
33    pub const fn block(&self) -> BlockRef {
34        self.block
35    }
36
37    /// Schedules the vanilla two-tick fall check after placement.
38    pub fn on_place(&self, world: &Arc<World>, pos: BlockPos) {
39        let _ = world.schedule_block_tick_default(pos, self.block, FALL_DELAY);
40    }
41
42    /// Schedules the vanilla two-tick fall check after every shape update.
43    #[must_use]
44    pub fn update_shape(
45        &self,
46        state: BlockStateId,
47        ticks: &dyn ScheduledTickAccess,
48        pos: BlockPos,
49    ) -> BlockStateId {
50        let _ = ticks.schedule_block_tick_default(pos, self.block, FALL_DELAY);
51        state
52    }
53
54    /// Runs the scheduled fall check and returns the spawned entity, if any.
55    #[must_use]
56    pub fn tick(
57        state: BlockStateId,
58        world: &Arc<World>,
59        pos: BlockPos,
60    ) -> Option<Arc<FallingBlockEntity>> {
61        if pos.y() < world.get_min_y() || !Self::is_free(world.get_block_state(pos.below())) {
62            return None;
63        }
64
65        Some(FallingBlockEntity::fall(world, pos, state))
66    }
67
68    /// Vanilla `FallingBlock.isFree`.
69    #[must_use]
70    pub fn is_free(state: BlockStateId) -> bool {
71        let block = state.get_block();
72        state.is_air()
73            || block.has_tag(&BlockTag::FIRE)
74            || block.config.liquid
75            || state.is_replaceable()
76    }
77}
78
79impl Fallable for FallingBlock {}
80
81impl BlockBehavior for FallingBlock {
82    fn get_state_for_placement(&self, _context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
83        Some(self.block.default_state())
84    }
85
86    fn on_place(
87        &self,
88        _state: BlockStateId,
89        world: &Arc<World>,
90        pos: BlockPos,
91        _old_state: BlockStateId,
92        _moved_by_piston: bool,
93    ) {
94        self.on_place(world, pos);
95    }
96
97    fn update_shape(
98        &self,
99        state: BlockStateId,
100        ticks: &dyn ScheduledTickAccess,
101        pos: BlockPos,
102        _direction: Direction,
103        _neighbor_pos: BlockPos,
104        _neighbor_state: BlockStateId,
105    ) -> BlockStateId {
106        self.update_shape(state, ticks, pos)
107    }
108
109    fn tick(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
110        let _ = Self::tick(state, world, pos);
111    }
112
113    fn as_fallable(&self) -> Option<&dyn Fallable> {
114        Some(self)
115    }
116}