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