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