Skip to main content

steel_core/behavior/blocks/vegetation/
hanging_moss_block.rs

1use std::sync::Arc;
2
3use crate::behavior::block::BlockBehavior;
4use crate::behavior::blocks::vegetation::bonemealable::{BonemealAction, Bonemealable};
5use crate::behavior::context::BlockPlaceContext;
6use crate::world::LevelReader;
7use crate::world::{ScheduledTickAccess, World};
8use rand::Rng;
9use steel_macros::block_behavior;
10use steel_registry::blocks::block_state_ext::BlockStateExt;
11use steel_registry::blocks::properties::{BlockStateProperties, BoolProperty};
12use steel_utils::types::UpdateFlags;
13use steel_utils::{BlockPos, BlockStateId, Direction};
14
15use super::{BlockRef, can_attach_to_multiface, default_surviving_state};
16
17const TIP: BoolProperty = BlockStateProperties::TIP;
18
19/// Vanilla `HangingMossBlock` survival (e.g. `pale_hanging_moss`).
20#[block_behavior]
21pub struct HangingMossBlock {
22    block: BlockRef,
23}
24
25impl HangingMossBlock {
26    /// Creates a new hanging moss block behavior.
27    #[must_use]
28    pub const fn new(block: BlockRef) -> Self {
29        Self { block }
30    }
31    /// Finds the tip (bottom-most block) of a hanging moss chain.
32    /// Returns the position of the lowest hanging moss block in the chain.
33    fn get_tip(&self, world: &dyn LevelReader, pos: BlockPos) -> BlockPos {
34        let mut forward_pos = pos;
35        let mut forward_state;
36
37        loop {
38            forward_pos = forward_pos.below();
39            forward_state = world.get_block_state(forward_pos);
40
41            if forward_state.get_block() != self.block {
42                break;
43            }
44        }
45        forward_pos.above()
46    }
47    fn can_grow_into(state: BlockStateId) -> bool {
48        state.is_air()
49    }
50}
51
52impl BlockBehavior for HangingMossBlock {
53    fn can_survive(&self, _state: BlockStateId, world: &dyn LevelReader, pos: BlockPos) -> bool {
54        // Vanilla `canStayAtPosition`: the block above either attaches via the
55        // multiface rule (support OR collision face full) or is more hanging
56        // moss of the same kind.
57        let above_pos = pos.above();
58        if can_attach_to_multiface(world, above_pos, Direction::Up) {
59            return true;
60        }
61        world.get_block_state(above_pos).get_block() == self.block
62    }
63    fn update_shape(
64        &self,
65        state: BlockStateId,
66        world: &dyn ScheduledTickAccess,
67        pos: BlockPos,
68        _direction: steel_utils::Direction,
69        _neighbor_pos: BlockPos,
70        _neighbor_state: BlockStateId,
71    ) -> BlockStateId {
72        if !self.can_survive(state, world, pos) {
73            world.schedule_block_tick_default(pos, self.block, 1);
74        }
75        let is_tip = world.get_block_state(pos.below()).get_block() != self.block;
76        state.set_value(&TIP, is_tip)
77    }
78    fn tick(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
79        if !self.can_survive(state, world, pos) {
80            world.destroy_block(pos, true);
81        }
82    }
83
84    fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
85        default_surviving_state(self.block, self, context)
86    }
87    fn as_bonemealable(&self) -> Option<&dyn Bonemealable> {
88        Some(self)
89    }
90}
91impl Bonemealable for HangingMossBlock {
92    fn is_valid_bonemeal_target(
93        &self,
94        _state: BlockStateId,
95        world: &dyn LevelReader,
96        pos: BlockPos,
97    ) -> bool {
98        let grow_pos = self.get_tip(world, pos).below();
99        HangingMossBlock::can_grow_into(world.get_block_state(grow_pos))
100            && !world.is_outside_build_height(grow_pos.y())
101    }
102
103    fn perform_bonemeal(
104        &self,
105        state: BlockStateId,
106        world: &Arc<World>,
107        _rng: &mut dyn Rng,
108        pos: BlockPos,
109    ) {
110        let tip_pos = self.get_tip(world, pos).below();
111        if HangingMossBlock::can_grow_into(world.get_block_state(tip_pos)) {
112            world.set_block(
113                tip_pos,
114                state.set_value(&TIP, true),
115                UpdateFlags::UPDATE_ALL,
116            );
117        }
118    }
119
120    fn bonemeal_action_type(&self) -> BonemealAction {
121        BonemealAction::Grower
122    }
123}