steel_core/behavior/blocks/vegetation/
cave_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::blocks::properties::{BlockStateProperties, BoolProperty};
6use steel_registry::item_stack::ItemStack;
7use steel_registry::items::item::BlockHitResult;
8use steel_registry::{vanilla_blocks, vanilla_items};
9use steel_utils::types::UpdateFlags;
10use steel_utils::{BlockPos, BlockStateId, Direction};
11
12use crate::behavior::blocks::CaveVinesBlock;
13use crate::behavior::blocks::vegetation::bonemealable::{BonemealAction, Bonemealable};
14use crate::behavior::blocks::vegetation::growing_plant_body_block::GrowingPlantBodyBlock;
15use crate::behavior::context::BlockPlaceContext;
16use crate::behavior::{InteractionResult, InventoryAccess};
17use crate::player::Player;
18use crate::world::{LevelReader, ScheduledTickAccess};
19use crate::{behavior::block::BlockBehavior, world::World};
20
21use super::BlockRef;
22
23#[block_behavior]
25pub struct CaveVinesPlantBlock {
26 base: GrowingPlantBodyBlock,
27}
28
29const BERRIES: &BoolProperty = &BlockStateProperties::BERRIES;
30
31impl CaveVinesPlantBlock {
32 #[must_use]
34 pub const fn new(block: BlockRef) -> Self {
35 Self {
36 base: GrowingPlantBodyBlock::new(
37 block,
38 Direction::Down,
39 false,
40 &vanilla_blocks::CAVE_VINES,
41 Self::can_grow_into,
42 )
43 .with_update_head_after_converted_from_body(
44 Self::update_head_after_converted_from_body,
45 ),
46 }
47 }
48
49 fn can_grow_into(state: BlockStateId) -> bool {
50 state.is_air()
51 }
52
53 fn update_head_after_converted_from_body(
54 body_state: BlockStateId,
55 head_state: BlockStateId,
56 ) -> BlockStateId {
57 head_state.set_value(BERRIES, body_state.get_value(BERRIES))
58 }
59}
60
61impl BlockBehavior for CaveVinesPlantBlock {
62 fn can_be_replaced(&self, state: BlockStateId, context: &BlockPlaceContext<'_>) -> bool {
63 self.base.can_be_replaced(state, context)
64 }
65
66 fn use_without_item(
67 &self,
68 state: BlockStateId,
69 world: &Arc<World>,
70 pos: BlockPos,
71 player: &Player,
72 _hit_result: &BlockHitResult,
73 _inv: &mut InventoryAccess,
74 ) -> InteractionResult {
75 CaveVinesBlock::use_block(player, state, world, pos)
76 }
77
78 fn as_bonemealable(&self) -> Option<&dyn Bonemealable> {
79 Some(self)
80 }
81 fn can_survive(&self, state: BlockStateId, world: &dyn LevelReader, pos: BlockPos) -> bool {
82 self.base.can_survive(state, world, pos)
83 }
84 fn update_shape(
85 &self,
86 state: BlockStateId,
87 world: &dyn ScheduledTickAccess,
88 pos: BlockPos,
89 direction: Direction,
90 neighbor_pos: BlockPos,
91 neighbor_state: BlockStateId,
92 ) -> BlockStateId {
93 self.base
94 .update_shape(state, world, pos, direction, neighbor_pos, neighbor_state)
95 }
96 fn tick(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
97 self.base.tick(state, world, pos);
98 }
99
100 fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
101 self.base.get_state_for_placement(context)
102 }
103
104 fn get_clone_item_stack(
105 &self,
106 _block: BlockRef,
107 _state: BlockStateId,
108 _include_data: bool,
109 ) -> Option<ItemStack> {
110 Some(ItemStack::new(&vanilla_items::GLOW_BERRIES))
111 }
112}
113impl Bonemealable for CaveVinesPlantBlock {
114 fn is_valid_bonemeal_target(
115 &self,
116 state: BlockStateId,
117 _world: &dyn LevelReader,
118 _pos: BlockPos,
119 ) -> bool {
120 !state.get_value(BERRIES)
121 }
122
123 fn perform_bonemeal(
124 &self,
125 state: BlockStateId,
126 world: &Arc<World>,
127 _rng: &mut dyn Rng,
128 pos: BlockPos,
129 ) {
130 world.set_block(
131 pos,
132 state.set_value(BERRIES, true),
133 UpdateFlags::UPDATE_CLIENTS,
134 );
135 }
136
137 fn bonemeal_action_type(&self) -> BonemealAction {
138 BonemealAction::Grower
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use steel_registry::init_vanilla_registry;
145
146 use super::*;
147 use crate::test_support::TestLevel;
148
149 #[test]
150 fn body_conversion_preserves_berries() {
151 init_vanilla_registry();
152
153 let behavior = CaveVinesPlantBlock::new(&vanilla_blocks::CAVE_VINES_PLANT);
154 let state = vanilla_blocks::CAVE_VINES_PLANT
155 .default_state()
156 .set_value(BERRIES, true);
157 let level = TestLevel::default();
158
159 let converted = behavior.update_shape(
160 state,
161 &level,
162 BlockPos::ZERO,
163 Direction::Down,
164 BlockPos::ZERO.below(),
165 vanilla_blocks::AIR.default_state(),
166 );
167
168 assert_eq!(converted.get_block(), &vanilla_blocks::CAVE_VINES);
169 assert!(converted.get_value(BERRIES));
170 }
171
172 #[test]
173 fn clone_item_is_glow_berries() {
174 init_vanilla_registry();
175
176 let behavior = CaveVinesPlantBlock::new(&vanilla_blocks::CAVE_VINES_PLANT);
177 let item = behavior
178 .get_clone_item_stack(
179 &vanilla_blocks::CAVE_VINES_PLANT,
180 vanilla_blocks::CAVE_VINES_PLANT.default_state(),
181 false,
182 )
183 .expect("cave vines plants have a vanilla clone item");
184
185 assert!(item.is(&vanilla_items::GLOW_BERRIES));
186 }
187}