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::player::Player;
18use crate::world::{LevelReader, ScheduledTickAccess};
19use crate::{behavior::block::BlockBehavior, world::World};
20
21use super::BlockRef;
22
23/// Vanilla `CaveVinesPlantBlock` (body) survival.
24#[block_behavior]
25pub struct CaveVinesPlantBlock {
26    base: GrowingPlantBodyBlock,
27}
28
29const BERRIES: &BoolProperty = &BlockStateProperties::BERRIES;
30
31impl CaveVinesPlantBlock {
32    /// Creates a new cave vines plant (body) block behavior.
33    #[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}