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::block_entity::SharedBlockEntity;
14use crate::world::{LevelReader, ScheduledTickAccess, World};
15
16use super::BlockRef;
17
18#[block_behavior]
20pub struct TwistingVinesPlantBlock {
21 base: GrowingPlantBodyBlock,
22}
23
24impl TwistingVinesPlantBlock {
25 #[must_use]
27 pub const fn new(block: BlockRef) -> Self {
28 Self {
29 base: GrowingPlantBodyBlock::new(
30 block,
31 Direction::Up,
32 false,
33 &vanilla_blocks::TWISTING_VINES,
34 Self::can_grow_into,
35 ),
36 }
37 }
38
39 fn can_grow_into(state: BlockStateId) -> bool {
40 state.is_air()
41 }
42}
43impl BlockBehavior for TwistingVinesPlantBlock {
44 fn can_survive(&self, state: BlockStateId, world: &dyn LevelReader, pos: BlockPos) -> bool {
45 self.base.can_survive(state, world, pos)
46 }
47
48 fn random_tick(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
49 self.base.random_tick(state, world, pos);
50 }
51
52 fn update_shape(
53 &self,
54 state: BlockStateId,
55 world: &dyn ScheduledTickAccess,
56 pos: BlockPos,
57 direction: Direction,
58 neighbor_pos: BlockPos,
59 neighbor_state: BlockStateId,
60 ) -> BlockStateId {
61 self.base
62 .update_shape(state, world, pos, direction, neighbor_pos, neighbor_state)
63 }
64
65 fn tick(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
66 self.base.tick(state, world, pos);
67 }
68
69 fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
70 self.base.get_state_for_placement(context)
71 }
72
73 fn get_clone_item_stack(
74 &self,
75 _block: BlockRef,
76 _state: BlockStateId,
77 _block_entity: Option<SharedBlockEntity>,
78 _include_data: bool,
79 ) -> Option<ItemStack> {
80 Some(ItemStack::new(&vanilla_items::TWISTING_VINES))
81 }
82
83 fn as_bonemealable(&self) -> Option<&dyn Bonemealable> {
84 Some(self)
85 }
86}
87impl Bonemealable for TwistingVinesPlantBlock {
88 fn is_valid_bonemeal_target(
89 &self,
90 state: BlockStateId,
91 world: &dyn LevelReader,
92 pos: BlockPos,
93 ) -> bool {
94 self.base.is_valid_bonemeal_target(state, world, pos)
95 }
96
97 fn perform_bonemeal(
98 &self,
99 state: BlockStateId,
100 world: &Arc<World>,
101 rng: &mut dyn Rng,
102 pos: BlockPos,
103 ) {
104 self.base.perform_bonemeal(state, world, rng, pos);
105 }
106
107 fn bonemeal_action_type(&self) -> BonemealAction {
108 BonemealAction::Grower
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use steel_registry::init_vanilla_registry;
115
116 use super::*;
117 use crate::test_support::TestLevel;
118
119 #[test]
120 fn bonemeal_target_follows_connected_head() {
121 init_vanilla_registry();
122
123 let behavior = TwistingVinesPlantBlock::new(&vanilla_blocks::TWISTING_VINES_PLANT);
124 let state = vanilla_blocks::TWISTING_VINES_PLANT.default_state();
125 let open_level = TestLevel::default().with_block(
126 BlockPos::ZERO.above(),
127 vanilla_blocks::TWISTING_VINES.default_state(),
128 );
129 assert!(behavior.is_valid_bonemeal_target(state, &open_level, BlockPos::ZERO));
130
131 let blocked_level = open_level.with_block(
132 BlockPos::ZERO.above().above(),
133 vanilla_blocks::NETHERRACK.default_state(),
134 );
135 assert!(!behavior.is_valid_bonemeal_target(state, &blocked_level, BlockPos::ZERO));
136 }
137}