steel_core/behavior/blocks/vegetation/
tall_grass_block.rs1use std::sync::Arc;
2
3use steel_macros::block_behavior;
4use steel_registry::{
5 blocks::{
6 BlockRef,
7 block_state_ext::BlockStateExt,
8 properties::{BlockStateProperties, DoubleBlockHalf},
9 },
10 vanilla_blocks,
11};
12use steel_utils::{BlockPos, BlockStateId, Direction, types::UpdateFlags};
13
14use crate::{
15 behavior::{
16 BlockBehavior, BlockPlaceContext,
17 blocks::vegetation::{
18 Vegetation,
19 bonemealable::Bonemealable,
20 default_surviving_state,
21 vegetation_block::{
22 double_plant_can_survive, survival_update_shape, vegetation_can_survive,
23 },
24 },
25 },
26 world::{LevelReader, ScheduledTickAccess, World},
27};
28
29#[block_behavior]
31pub struct TallGrassBlock {
32 block: BlockRef,
33}
34
35impl TallGrassBlock {
36 #[must_use]
38 pub const fn new(block: BlockRef) -> Self {
39 Self { block }
40 }
41
42 fn large_variant(state: BlockStateId) -> BlockRef {
43 if state.get_block() == &vanilla_blocks::FERN {
44 &vanilla_blocks::LARGE_FERN
45 } else {
46 &vanilla_blocks::TALL_GRASS
47 }
48 }
49}
50
51impl BlockBehavior for TallGrassBlock {
52 fn update_shape(
53 &self,
54 state: BlockStateId,
55 world: &dyn ScheduledTickAccess,
56 pos: BlockPos,
57 _direction: Direction,
58 _neighbor_pos: BlockPos,
59 _neighbor_state: BlockStateId,
60 ) -> BlockStateId {
61 survival_update_shape(self, state, world, pos)
62 }
63
64 fn can_survive(&self, state: BlockStateId, world: &dyn LevelReader, pos: BlockPos) -> bool {
65 vegetation_can_survive(self, state, world, pos)
66 }
67
68 fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
69 default_surviving_state(self.block, self, context)
70 }
71
72 fn as_bonemealable(&self) -> Option<&dyn Bonemealable> {
73 Some(self)
74 }
75}
76
77impl Vegetation for TallGrassBlock {}
78
79impl Bonemealable for TallGrassBlock {
80 fn is_valid_bonemeal_target(
81 &self,
82 state: BlockStateId,
83 world: &dyn LevelReader,
84 pos: BlockPos,
85 ) -> bool {
86 let above_pos = pos.above();
87 let lower_state = Self::large_variant(state).default_state().set_value(
88 &BlockStateProperties::DOUBLE_BLOCK_HALF,
89 DoubleBlockHalf::Lower,
90 );
91 !world.is_outside_build_height(above_pos.y())
92 && double_plant_can_survive(self, lower_state, world, pos)
93 && world.get_block_state(above_pos).is_air()
94 }
95
96 fn perform_bonemeal(
97 &self,
98 state: BlockStateId,
99 world: &Arc<World>,
100 _rng: &mut dyn rand::Rng,
101 pos: BlockPos,
102 ) {
103 let base_state = Self::large_variant(state).default_state();
104 let waterlogged_state = state
105 .try_get_value(&BlockStateProperties::WATERLOGGED)
106 .map_or(base_state, |waterlogged| {
107 base_state.set_value(&BlockStateProperties::WATERLOGGED, waterlogged)
108 });
109
110 world.set_block(
111 pos,
112 waterlogged_state.set_value(
113 &BlockStateProperties::DOUBLE_BLOCK_HALF,
114 DoubleBlockHalf::Lower,
115 ),
116 UpdateFlags::UPDATE_CLIENTS,
117 );
118 world.set_block(
119 pos.above(),
120 waterlogged_state.set_value(
121 &BlockStateProperties::DOUBLE_BLOCK_HALF,
122 DoubleBlockHalf::Upper,
123 ),
124 UpdateFlags::UPDATE_CLIENTS,
125 );
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use steel_registry::init_vanilla_registry;
132
133 use crate::test_support::TestLevel;
134
135 use super::*;
136
137 #[test]
138 fn tall_grass_bonemeal_rejects_top_build_height() {
139 init_vanilla_registry();
140 let behavior = TallGrassBlock::new(&vanilla_blocks::SHORT_GRASS);
141 let state = vanilla_blocks::SHORT_GRASS.default_state();
142 let level = TestLevel::default()
143 .with_min_y(0)
144 .with_height(1)
145 .with_block(BlockPos::ZERO.below(), vanilla_blocks::DIRT.default_state());
146
147 assert!(!behavior.is_valid_bonemeal_target(state, &level, BlockPos::ZERO));
148 }
149}