steel_core/behavior/blocks/vegetation/
weeping_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;
9use crate::behavior::blocks::vegetation::growing_plant_body_block::GrowingPlantBodyBlock;
10use crate::behavior::context::BlockPlaceContext;
11use crate::behavior::{block::BlockBehavior, blocks::vegetation::bonemealable::Bonemealable};
12use crate::block_entity::SharedBlockEntity;
13use crate::world::{LevelReader, ScheduledTickAccess, World};
14
15use super::BlockRef;
16
17#[block_behavior]
19pub struct WeepingVinesPlantBlock {
20 base: GrowingPlantBodyBlock,
21}
22
23impl WeepingVinesPlantBlock {
24 #[must_use]
26 pub const fn new(block: BlockRef) -> Self {
27 Self {
28 base: GrowingPlantBodyBlock::new(
29 block,
30 Direction::Down,
31 false,
32 &vanilla_blocks::WEEPING_VINES,
33 Self::can_grow_into,
34 ),
35 }
36 }
37
38 fn can_grow_into(state: BlockStateId) -> bool {
39 state.is_air()
40 }
41}
42
43impl BlockBehavior for WeepingVinesPlantBlock {
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 fn tick(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
65 self.base.tick(state, world, pos);
66 }
67
68 fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
69 self.base.get_state_for_placement(context)
70 }
71
72 fn get_clone_item_stack(
73 &self,
74 _block: BlockRef,
75 _state: BlockStateId,
76 _block_entity: Option<SharedBlockEntity>,
77 _include_data: bool,
78 ) -> Option<ItemStack> {
79 Some(ItemStack::new(&vanilla_items::WEEPING_VINES))
80 }
81
82 fn as_bonemealable(&self) -> Option<&dyn Bonemealable> {
83 Some(self)
84 }
85}
86impl Bonemealable for WeepingVinesPlantBlock {
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_follows_connected_head() {
120 init_vanilla_registry();
121
122 let behavior = WeepingVinesPlantBlock::new(&vanilla_blocks::WEEPING_VINES_PLANT);
123 let state = vanilla_blocks::WEEPING_VINES_PLANT.default_state();
124 let open_level = TestLevel::default().with_block(
125 BlockPos::ZERO.below(),
126 vanilla_blocks::WEEPING_VINES.default_state(),
127 );
128 assert!(behavior.is_valid_bonemeal_target(state, &open_level, BlockPos::ZERO));
129
130 let blocked_level = open_level.with_block(
131 BlockPos::ZERO.below().below(),
132 vanilla_blocks::NETHERRACK.default_state(),
133 );
134 assert!(!behavior.is_valid_bonemeal_target(state, &blocked_level, BlockPos::ZERO));
135 }
136}