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 block: BlockRef,
27}
28
29const BERRIES: BoolProperty = BlockStateProperties::BERRIES;
30
31impl CaveVinesPlantBlock {
32 #[must_use]
34 pub const fn new(block: BlockRef) -> Self {
35 Self { block }
36 }
37
38 fn can_grow_into(state: BlockStateId) -> bool {
39 state.is_air()
40 }
41
42 const fn growing_plant_body_block(&self) -> GrowingPlantBodyBlock {
43 GrowingPlantBodyBlock::new(
44 self.block,
45 Direction::Down,
46 false,
47 &vanilla_blocks::CAVE_VINES,
48 Self::can_grow_into,
49 )
50 .with_update_head_after_converted_from_body(Self::update_head_after_converted_from_body)
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.growing_plant_body_block()
64 .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.growing_plant_body_block()
84 .can_survive(state, world, pos)
85 }
86 fn update_shape(
87 &self,
88 state: BlockStateId,
89 world: &dyn ScheduledTickAccess,
90 pos: BlockPos,
91 direction: Direction,
92 neighbor_pos: BlockPos,
93 neighbor_state: BlockStateId,
94 ) -> BlockStateId {
95 self.growing_plant_body_block().update_shape(
96 state,
97 world,
98 pos,
99 direction,
100 neighbor_pos,
101 neighbor_state,
102 )
103 }
104 fn tick(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
105 self.growing_plant_body_block().tick(state, world, pos);
106 }
107
108 fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
109 self.growing_plant_body_block()
110 .get_state_for_placement(context)
111 }
112
113 fn get_clone_item_stack(
114 &self,
115 _block: BlockRef,
116 _state: BlockStateId,
117 _include_data: bool,
118 ) -> Option<ItemStack> {
119 Some(ItemStack::new(&vanilla_items::GLOW_BERRIES))
120 }
121}
122impl Bonemealable for CaveVinesPlantBlock {
123 fn is_valid_bonemeal_target(
124 &self,
125 state: BlockStateId,
126 _world: &dyn LevelReader,
127 _pos: BlockPos,
128 ) -> bool {
129 !state.get_value(&BERRIES)
130 }
131
132 fn perform_bonemeal(
133 &self,
134 state: BlockStateId,
135 world: &Arc<World>,
136 _rng: &mut dyn Rng,
137 pos: BlockPos,
138 ) {
139 world.set_block(
140 pos,
141 state.set_value(&BERRIES, true),
142 UpdateFlags::UPDATE_CLIENTS,
143 );
144 }
145
146 fn bonemeal_action_type(&self) -> BonemealAction {
147 BonemealAction::Grower
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use steel_registry::init_vanilla_registry;
154
155 use super::*;
156 use crate::test_support::TestLevel;
157
158 #[test]
159 fn body_conversion_preserves_berries() {
160 init_vanilla_registry();
161
162 let behavior = CaveVinesPlantBlock::new(&vanilla_blocks::CAVE_VINES_PLANT);
163 let state = vanilla_blocks::CAVE_VINES_PLANT
164 .default_state()
165 .set_value(&BERRIES, true);
166 let level = TestLevel::default();
167
168 let converted = behavior.update_shape(
169 state,
170 &level,
171 BlockPos::ZERO,
172 Direction::Down,
173 BlockPos::ZERO.below(),
174 vanilla_blocks::AIR.default_state(),
175 );
176
177 assert_eq!(converted.get_block(), &vanilla_blocks::CAVE_VINES);
178 assert!(converted.get_value(&BERRIES));
179 }
180
181 #[test]
182 fn clone_item_is_glow_berries() {
183 init_vanilla_registry();
184
185 let behavior = CaveVinesPlantBlock::new(&vanilla_blocks::CAVE_VINES_PLANT);
186 let item = behavior
187 .get_clone_item_stack(
188 &vanilla_blocks::CAVE_VINES_PLANT,
189 vanilla_blocks::CAVE_VINES_PLANT.default_state(),
190 false,
191 )
192 .expect("cave vines plants have a vanilla clone item");
193
194 assert!(item.is(&vanilla_items::GLOW_BERRIES));
195 }
196}