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