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