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