Skip to main content

steel_core/behavior/blocks/vegetation/
cave_vines_plant_block.rs

1use 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/// Vanilla `CaveVinesPlantBlock` (body) survival.
25#[block_behavior]
26pub struct CaveVinesPlantBlock {
27    base: GrowingPlantBodyBlock,
28}
29
30const BERRIES: &BoolProperty = &BlockStateProperties::BERRIES;
31
32impl CaveVinesPlantBlock {
33    /// Creates a new cave vines plant (body) block behavior.
34    #[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}