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