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