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, fluid_state_to_block, 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 fluid_state_to_block(world.get_block_state(above).get_fluid_state()),
135 UpdateFlags::UPDATE_CLIENTS | UpdateFlags::UPDATE_KNOWN_SHAPE,
136 );
137 BigDripleafBlock::place_with_random_height(world, rng, pos, state.get_value(FACING));
138 return;
139 }
140 let below_pos = pos.below();
141 Self::perform_bonemeal(
142 self,
143 world.get_block_state(below_pos),
144 world,
145 rng,
146 below_pos,
147 );
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use steel_registry::blocks::properties::BoolProperty;
154 use steel_registry::{init_vanilla_registry, vanilla_blocks};
155
156 use super::*;
157 use crate::behavior::init_behaviors;
158 use crate::test_support::TestLevel;
159
160 const WATERLOGGED: &BoolProperty = &BlockStateProperties::WATERLOGGED;
161
162 #[test]
163 fn small_dripleaf_schedules_water_before_double_plant_survival() {
164 init_vanilla_registry();
165 init_behaviors();
166 let behavior = SmallDripleafBlock::new(&vanilla_blocks::SMALL_DRIPLEAF);
167 let state = vanilla_blocks::SMALL_DRIPLEAF
168 .default_state()
169 .set_value(WATERLOGGED, true)
170 .set_value(HALF, DoubleBlockHalf::Lower);
171 let level = TestLevel::default();
172
173 assert!(
174 behavior
175 .update_shape(
176 state,
177 &level,
178 BlockPos::ZERO,
179 Direction::Down,
180 BlockPos::ZERO.below(),
181 vanilla_blocks::AIR.default_state(),
182 )
183 .is_air()
184 );
185 assert!(level.scheduled_water_tick());
186 }
187}