Skip to main content

steel_core/behavior/blocks/vegetation/
small_dripleaf_block.rs

1use std::sync::Arc;
2
3use rand::Rng;
4use steel_macros::block_behavior;
5use steel_registry::blocks::block_state_ext::BlockStateExt;
6use steel_registry::blocks::properties::{
7    BlockStateProperties, Direction, DoubleBlockHalf, EnumProperty,
8};
9use steel_registry::vanilla_block_tags::BlockTag;
10use steel_utils::{BlockPos, BlockStateId, types::UpdateFlags};
11
12use crate::behavior::block::{BlockBehavior, schedule_water_tick_if_waterlogged};
13use crate::behavior::blocks::BigDripleafBlock;
14use crate::behavior::blocks::vegetation::Vegetation;
15use crate::behavior::blocks::vegetation::bonemealable::Bonemealable;
16use crate::behavior::context::{BlockPlaceContext, PlacementSource};
17use crate::fluid::{FluidStateExt, get_fluid_state_from_block};
18use crate::world::{LevelReader, ScheduledTickAccess, World};
19
20use super::{BlockRef, DoublePlantBlock};
21
22/// Vanilla `SmallDripleafBlock` survival.
23#[block_behavior]
24pub struct SmallDripleafBlock {
25    block: BlockRef,
26    base: DoublePlantBlock,
27}
28const HALF: EnumProperty<DoubleBlockHalf> = BlockStateProperties::DOUBLE_BLOCK_HALF;
29const FACING: EnumProperty<Direction> = BlockStateProperties::HORIZONTAL_FACING;
30
31impl SmallDripleafBlock {
32    /// Creates a new small dripleaf block behavior.
33    #[must_use]
34    pub const fn new(block: BlockRef) -> Self {
35        Self {
36            block,
37            base: DoublePlantBlock::new(block),
38        }
39    }
40
41    fn may_place_on(&self, state: BlockStateId, world: &dyn LevelReader, pos: BlockPos) -> bool {
42        let fluid = get_fluid_state_from_block(world.get_block_state(pos.above()));
43        state
44            .get_block()
45            .has_tag(&BlockTag::SUPPORTS_SMALL_DRIPLEAF)
46            || (fluid.is_full()
47                && fluid.is_water()
48                && <Self as Vegetation>::may_place_on(self, state, world, pos))
49    }
50}
51
52impl Vegetation for SmallDripleafBlock {}
53
54impl BlockBehavior for SmallDripleafBlock {
55    fn update_shape(
56        &self,
57        state: BlockStateId,
58        world: &dyn ScheduledTickAccess,
59        pos: BlockPos,
60        direction: Direction,
61        neighbor_pos: BlockPos,
62        neighbor_state: BlockStateId,
63    ) -> BlockStateId {
64        schedule_water_tick_if_waterlogged(state, world, pos);
65        self.base
66            .update_shape(state, world, pos, direction, neighbor_pos, neighbor_state)
67    }
68
69    fn can_survive(&self, state: BlockStateId, world: &dyn LevelReader, pos: BlockPos) -> bool {
70        if state.get_value(&HALF) == DoubleBlockHalf::Upper {
71            return self.base.can_survive(state, world, pos);
72        }
73
74        let below_pos = pos.below();
75        let below_state = world.get_block_state(below_pos);
76        self.may_place_on(below_state, world, below_pos)
77    }
78
79    fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
80        let state = self
81            .base
82            .get_state_for_placement(context)?
83            .set_value(&FACING, context.horizontal_direction().opposite());
84        Some(DoublePlantBlock::copy_waterlogged_from(
85            context.world,
86            context.place_pos(),
87            state,
88        ))
89    }
90
91    fn set_placed_by(
92        &self,
93        state: BlockStateId,
94        world: &Arc<World>,
95        pos: BlockPos,
96        _source: &PlacementSource<'_>,
97    ) {
98        let above_pos = pos.above();
99        let block_state = DoublePlantBlock::copy_waterlogged_from(
100            world,
101            above_pos,
102            self.block
103                .default_state()
104                .set_value(&HALF, DoubleBlockHalf::Upper)
105                .set_value(&FACING, state.get_value(&FACING)),
106        );
107        world.set_block(above_pos, block_state, UpdateFlags::UPDATE_ALL);
108    }
109
110    fn as_bonemealable(&self) -> Option<&dyn Bonemealable> {
111        Some(self)
112    }
113}
114impl Bonemealable for SmallDripleafBlock {
115    fn is_valid_bonemeal_target(
116        &self,
117        _state: BlockStateId,
118        _world: &dyn LevelReader,
119        _pos: BlockPos,
120    ) -> bool {
121        true
122    }
123
124    fn perform_bonemeal(
125        &self,
126        state: BlockStateId,
127        world: &Arc<World>,
128        rng: &mut dyn Rng,
129        pos: BlockPos,
130    ) {
131        if state.get_value(&HALF) == DoubleBlockHalf::Lower {
132            let above = pos.above();
133            world.set_block(
134                above,
135                world
136                    .get_block_state(above)
137                    .get_fluid_state()
138                    .create_legacy_block(),
139                UpdateFlags::UPDATE_CLIENTS | UpdateFlags::UPDATE_KNOWN_SHAPE,
140            );
141            BigDripleafBlock::place_with_random_height(world, rng, pos, state.get_value(&FACING));
142            return;
143        }
144        let below_pos = pos.below();
145        Self::perform_bonemeal(
146            self,
147            world.get_block_state(below_pos),
148            world,
149            rng,
150            below_pos,
151        );
152    }
153}
154
155#[cfg(test)]
156mod tests {
157    use steel_registry::blocks::properties::BoolProperty;
158    use steel_registry::{init_vanilla_registry, vanilla_blocks};
159
160    use super::*;
161    use crate::behavior::init_behaviors;
162    use crate::test_support::TestLevel;
163
164    const WATERLOGGED: BoolProperty = BlockStateProperties::WATERLOGGED;
165
166    #[test]
167    fn small_dripleaf_schedules_water_before_double_plant_survival() {
168        init_vanilla_registry();
169        init_behaviors();
170        let behavior = SmallDripleafBlock::new(&vanilla_blocks::SMALL_DRIPLEAF);
171        let state = vanilla_blocks::SMALL_DRIPLEAF
172            .default_state()
173            .set_value(&WATERLOGGED, true)
174            .set_value(&HALF, DoubleBlockHalf::Lower);
175        let level = TestLevel::default();
176
177        assert!(
178            behavior
179                .update_shape(
180                    state,
181                    &level,
182                    BlockPos::ZERO,
183                    Direction::Down,
184                    BlockPos::ZERO.below(),
185                    vanilla_blocks::AIR.default_state(),
186                )
187                .is_air()
188        );
189        assert!(level.scheduled_water_tick());
190    }
191}