Skip to main content

steel_core/behavior/blocks/vegetation/
twisting_vines_block.rs

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