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::MultifaceBlock;
5use crate::behavior::blocks::vegetation::bonemealable::{BonemealAction, Bonemealable};
6use crate::behavior::context::BlockPlaceContext;
7use crate::world::LevelReader;
8use crate::world::{ScheduledTickAccess, World};
9use rand::Rng;
10use steel_macros::block_behavior;
11use steel_registry::blocks::block_state_ext::BlockStateExt;
12use steel_registry::blocks::properties::{BlockStateProperties, BoolProperty};
13use steel_utils::types::UpdateFlags;
14use steel_utils::{BlockPos, BlockStateId, Direction};
15
16use super::{BlockRef, default_surviving_state};
17
18const TIP: &BoolProperty = &BlockStateProperties::TIP;
19
20/// Vanilla `HangingMossBlock` survival (e.g. `pale_hanging_moss`).
21#[block_behavior]
22pub struct HangingMossBlock {
23    block: BlockRef,
24}
25
26impl HangingMossBlock {
27    /// Creates a new hanging moss block behavior.
28    #[must_use]
29    pub const fn new(block: BlockRef) -> Self {
30        Self { block }
31    }
32    /// Finds the tip (bottom-most block) of a hanging moss chain.
33    /// Returns the position of the lowest hanging moss block in the chain.
34    fn get_tip(&self, world: &dyn LevelReader, pos: BlockPos) -> BlockPos {
35        let mut forward_pos = pos;
36        let mut forward_state;
37
38        loop {
39            forward_pos = forward_pos.below();
40            forward_state = world.get_block_state(forward_pos);
41
42            if forward_state.get_block() != self.block {
43                break;
44            }
45        }
46        forward_pos.above()
47    }
48    fn can_grow_into(state: BlockStateId) -> bool {
49        state.is_air()
50    }
51}
52
53impl BlockBehavior for HangingMossBlock {
54    fn can_survive(&self, _state: BlockStateId, world: &dyn LevelReader, pos: BlockPos) -> bool {
55        // Vanilla `canStayAtPosition`: the block above either attaches via the
56        // multiface rule (support OR collision face full) or is more hanging
57        // moss of the same kind.
58        let above_pos = pos.above();
59        let above_state = world.get_block_state(above_pos);
60        MultifaceBlock::can_attach_to_state(world, Direction::Up, above_pos, above_state)
61            || above_state.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(tip_pos, state.set_value(TIP, true), UpdateFlags::UPDATE_ALL);
113        }
114    }
115
116    fn bonemeal_action_type(&self) -> BonemealAction {
117        BonemealAction::Grower
118    }
119}