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