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