steel_core/behavior/blocks/vegetation/
double_plant_block.rs1use 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#[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 #[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 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}