Skip to main content

steel_core/behavior/blocks/vegetation/
double_plant_block.rs

1use std::sync::Arc;
2
3use steel_macros::block_behavior;
4use steel_registry::blocks::block_state_ext::BlockStateExt;
5use steel_registry::blocks::properties::{BlockStateProperties, Direction, DoubleBlockHalf};
6use steel_registry::vanilla_blocks;
7use steel_utils::{BlockPos, BlockStateId, axis::Axis, types::UpdateFlags};
8
9use crate::behavior::BlockStateBehaviorExt;
10use crate::behavior::block::BlockBehavior;
11use crate::behavior::blocks::vegetation::Vegetation;
12use crate::behavior::blocks::vegetation::vegetation_block::double_plant_can_survive;
13use crate::behavior::context::{BlockPlaceContext, PlacementSource};
14use crate::fluid::{FluidStateExt as _, get_fluid_state};
15use crate::world::{LevelReader, ScheduledTickAccess, World};
16
17use super::BlockRef;
18
19/// Behavior for vanilla two-block-tall plants.
20#[block_behavior]
21pub struct DoublePlantBlock {
22    block: BlockRef,
23}
24
25impl DoublePlantBlock {
26    /// Creates a new double plant block behavior.
27    #[must_use]
28    pub const fn new(block: BlockRef) -> Self {
29        Self { block }
30    }
31
32    pub(super) fn copy_waterlogged_from(
33        world: &Arc<World>,
34        pos: BlockPos,
35        state: BlockStateId,
36    ) -> BlockStateId {
37        if state
38            .try_get_value(&BlockStateProperties::WATERLOGGED)
39            .is_some()
40        {
41            state.set_value(
42                &BlockStateProperties::WATERLOGGED,
43                get_fluid_state(world, pos).is_water(),
44            )
45        } else {
46            state
47        }
48    }
49
50    /// Runs Vanilla `DoublePlantBlock.updateShape` while preserving virtual
51    /// `canSurvive` dispatch for subclasses such as small dripleaf.
52    pub(super) fn update_shape_with_survival(
53        &self,
54        survival_behavior: &dyn BlockBehavior,
55        state: BlockStateId,
56        world: &dyn ScheduledTickAccess,
57        pos: BlockPos,
58        direction: Direction,
59        neighbor_state: BlockStateId,
60    ) -> BlockStateId {
61        let half = state.get_value(&BlockStateProperties::DOUBLE_BLOCK_HALF);
62        let neighbor_is_matching_other_half = neighbor_state.get_block() == self.block
63            && neighbor_state.get_value(&BlockStateProperties::DOUBLE_BLOCK_HALF) != half;
64
65        if direction.get_axis() == Axis::Y
66            && (half == DoubleBlockHalf::Lower) == (direction == Direction::Up)
67            && !neighbor_is_matching_other_half
68        {
69            return vanilla_blocks::AIR.default_state();
70        }
71
72        if half == DoubleBlockHalf::Lower
73            && direction == Direction::Down
74            && !survival_behavior.can_survive(state, world, pos)
75        {
76            return vanilla_blocks::AIR.default_state();
77        }
78
79        state
80    }
81}
82
83impl Vegetation for DoublePlantBlock {}
84
85impl BlockBehavior for DoublePlantBlock {
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.update_shape_with_survival(self, state, world, pos, direction, neighbor_state)
96    }
97
98    fn can_survive(&self, state: BlockStateId, world: &dyn LevelReader, pos: BlockPos) -> bool {
99        double_plant_can_survive(self, state, world, pos)
100    }
101
102    fn set_placed_by(
103        &self,
104        _state: BlockStateId,
105        world: &Arc<World>,
106        pos: BlockPos,
107        _source: &PlacementSource<'_>,
108    ) {
109        let upper_pos = pos.above();
110        let upper_state = Self::copy_waterlogged_from(
111            world,
112            upper_pos,
113            self.block.default_state().set_value(
114                &BlockStateProperties::DOUBLE_BLOCK_HALF,
115                DoubleBlockHalf::Upper,
116            ),
117        );
118        world.set_block(upper_pos, upper_state, UpdateFlags::UPDATE_ALL);
119    }
120
121    fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
122        if context.place_pos().y() >= context.world.max_y_exclusive() - 1 {
123            return None;
124        }
125        if !context
126            .world
127            .get_block_state(context.place_pos().above())
128            .can_be_replaced(context)
129        {
130            return None;
131        }
132        Some(self.block.default_state())
133    }
134}