Skip to main content

steel_core/behavior/blocks/vegetation/
weeping_vines_block.rs

1use rand::Rng;
2use std::sync::Arc;
3use steel_macros::block_behavior;
4use steel_registry::blocks::block_state_ext::BlockStateExt;
5use steel_registry::vanilla_blocks;
6use steel_utils::{BlockPos, BlockStateId, Direction};
7
8use crate::behavior::blocks::vegetation::bonemealable::{BonemealAction, Bonemealable};
9use crate::behavior::blocks::vegetation::growing_plant_head_block::{
10    GrowingPlantHeadBehavior, GrowingPlantHeadBlock,
11};
12use crate::behavior::blocks::vegetation::nether_vines;
13use crate::behavior::context::BlockPlaceContext;
14use crate::world::{LevelReader, World};
15use crate::{behavior::block::BlockBehavior, world::ScheduledTickAccess};
16
17use super::BlockRef;
18
19/// Vanilla `WeepingVinesBlock` (head) survival.
20#[block_behavior]
21pub struct WeepingVinesBlock {
22    base: GrowingPlantHeadBlock,
23}
24
25impl WeepingVinesBlock {
26    /// Creates a new weeping vines (head) block behavior.
27    #[must_use]
28    pub const fn new(block: BlockRef) -> Self {
29        Self {
30            base: GrowingPlantHeadBlock::new(
31                block,
32                Direction::Down,
33                false,
34                0.1,
35                &vanilla_blocks::WEEPING_VINES_PLANT,
36                Some(nether_vines::get_blocks_to_grow_when_bonemealed),
37                Self::can_grow_into,
38            ),
39        }
40    }
41
42    fn can_grow_into(state: BlockStateId) -> bool {
43        state.is_air()
44    }
45}
46
47impl BlockBehavior for WeepingVinesBlock {
48    fn can_survive(&self, state: BlockStateId, world: &dyn LevelReader, pos: BlockPos) -> bool {
49        self.base.can_survive(state, world, pos)
50    }
51
52    fn random_tick(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
53        self.base.random_tick(state, world, pos);
54    }
55
56    fn update_shape(
57        &self,
58        state: BlockStateId,
59        world: &dyn ScheduledTickAccess,
60        pos: BlockPos,
61        direction: Direction,
62        neighbor_pos: BlockPos,
63        neighbor_state: BlockStateId,
64    ) -> BlockStateId {
65        self.base
66            .update_shape(state, world, pos, direction, neighbor_pos, neighbor_state)
67    }
68
69    fn tick(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
70        self.base.tick(state, world, pos);
71    }
72
73    fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
74        self.base.get_state_for_placement(context)
75    }
76
77    fn as_bonemealable(&self) -> Option<&dyn Bonemealable> {
78        Some(self)
79    }
80
81    fn as_growing_plant_head(&self) -> Option<&dyn GrowingPlantHeadBehavior> {
82        Some(&self.base)
83    }
84}
85impl Bonemealable for WeepingVinesBlock {
86    fn is_valid_bonemeal_target(
87        &self,
88        state: BlockStateId,
89        world: &dyn LevelReader,
90        pos: BlockPos,
91    ) -> bool {
92        self.base.is_valid_bonemeal_target(state, world, pos)
93    }
94
95    fn perform_bonemeal(
96        &self,
97        state: BlockStateId,
98        world: &Arc<World>,
99        rng: &mut dyn Rng,
100        pos: BlockPos,
101    ) {
102        self.base.perform_bonemeal(state, world, rng, pos);
103    }
104
105    fn bonemeal_action_type(&self) -> BonemealAction {
106        BonemealAction::Grower
107    }
108}
109
110#[cfg(test)]
111mod tests {
112    use steel_registry::init_vanilla_registry;
113
114    use super::*;
115    use crate::test_support::TestLevel;
116
117    #[test]
118    fn bonemeal_target_requires_open_growth_position() {
119        init_vanilla_registry();
120
121        let behavior = WeepingVinesBlock::new(&vanilla_blocks::WEEPING_VINES);
122        let state = vanilla_blocks::WEEPING_VINES.default_state();
123        let open_level = TestLevel::default();
124        assert!(behavior.is_valid_bonemeal_target(state, &open_level, BlockPos::ZERO));
125
126        let blocked_level = TestLevel::default().with_block(
127            BlockPos::ZERO.below(),
128            vanilla_blocks::NETHERRACK.default_state(),
129        );
130        assert!(!behavior.is_valid_bonemeal_target(state, &blocked_level, BlockPos::ZERO));
131    }
132
133    #[test]
134    fn connected_head_converts_to_body() {
135        init_vanilla_registry();
136
137        let behavior = WeepingVinesBlock::new(&vanilla_blocks::WEEPING_VINES);
138        let state = vanilla_blocks::WEEPING_VINES.default_state();
139        let level = TestLevel::default();
140        let converted = behavior.update_shape(
141            state,
142            &level,
143            BlockPos::ZERO,
144            Direction::Down,
145            BlockPos::ZERO.below(),
146            vanilla_blocks::WEEPING_VINES_PLANT.default_state(),
147        );
148
149        assert_eq!(
150            converted,
151            vanilla_blocks::WEEPING_VINES_PLANT.default_state()
152        );
153    }
154}