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