Skip to main content

steel_core/behavior/items/
bonemeal.rs

1use std::sync::Arc;
2
3use rand::RngExt;
4use steel_macros::item_behavior;
5use steel_registry::{
6    blocks::{block_state_ext::BlockStateExt, shapes::is_offset_shape_full_block},
7    level_events, vanilla_blocks,
8};
9use steel_utils::{BlockPos, Direction, types::UpdateFlags};
10
11use crate::{
12    behavior::{BLOCK_BEHAVIORS, InteractionResult, ItemBehavior, UseOnContext},
13    world::{LevelReader as _, World},
14};
15
16/// Behavior for the Bonemeal item.
17#[item_behavior]
18pub struct BoneMealItem;
19
20impl BoneMealItem {
21    fn grow(world: &Arc<World>, pos: BlockPos) -> bool {
22        let state = world.get_block_state(pos);
23        let Some(behavior) = BLOCK_BEHAVIORS.get_behavior_for_state(state) else {
24            return false;
25        };
26        if let Some(bonemealable) = behavior.as_bonemealable() {
27            if !bonemealable.is_valid_bonemeal_target(state, world.as_ref(), pos) {
28                return false;
29            }
30
31            let mut rng = rand::rng();
32            if bonemealable.is_bonemeal_success(state, world, &mut rng, pos) {
33                bonemealable.perform_bonemeal(state, world, &mut rng, pos);
34            }
35
36            return true;
37        }
38        false
39    }
40
41    fn grow_water_plant(world: &Arc<World>, pos: BlockPos, _clicked_face: Direction) -> bool {
42        let state = world.get_block_state(pos);
43        if state.get_block() != &vanilla_blocks::WATER || state.get_fluid_state().amount != 8 {
44            return false;
45        }
46
47        let Some(bonemealable) = BLOCK_BEHAVIORS
48            .get_behavior(&vanilla_blocks::SEAGRASS)
49            .as_bonemealable()
50        else {
51            return false;
52        };
53
54        let mut rng = rand::rng();
55
56        'outer: for i in 0..128 {
57            let mut new_pos = pos;
58            let new_state = vanilla_blocks::SEAGRASS.default_state();
59
60            for _ in 0..(i / 16) {
61                new_pos = new_pos.offset(
62                    rng.random_range(0i32..3) - 1,
63                    (rng.random_range(0i32..3) - 1) * rng.random_range(0i32..3) / 2,
64                    rng.random_range(0i32..3) - 1,
65                );
66
67                if is_offset_shape_full_block(
68                    world
69                        .get_block_state(new_pos)
70                        .get_collision_shape_at(new_pos),
71                ) {
72                    continue 'outer;
73                }
74            }
75
76            // TODO: implement coral and underwater bonemeal tag selection.
77
78            let Some(behavior) = BLOCK_BEHAVIORS.get_behavior_for_state(new_state) else {
79                return false;
80            };
81
82            if behavior.can_survive(new_state, world, new_pos) {
83                let current_state = world.get_block_state(new_pos);
84                if current_state.get_block() == &vanilla_blocks::WATER
85                    && current_state.get_fluid_state().amount == 8
86                {
87                    world.set_block(new_pos, new_state, UpdateFlags::UPDATE_ALL);
88                } else if current_state.get_block() == &vanilla_blocks::SEAGRASS
89                    && bonemealable.is_valid_bonemeal_target(current_state, world.as_ref(), new_pos)
90                    && rng.random_range(0..10) == 0
91                {
92                    bonemealable.perform_bonemeal(current_state, world, &mut rng, new_pos);
93                }
94            }
95        }
96
97        true
98    }
99}
100
101impl ItemBehavior for BoneMealItem {
102    fn use_on(&self, context: &mut UseOnContext) -> InteractionResult {
103        if Self::grow(context.world, context.hit_result.block_pos) {
104            context.inv.with_item(|item| item.shrink(1));
105            context.world.level_event(
106                level_events::PARTICLES_AND_SOUND_PLANT_GROWTH,
107                context.hit_result.block_pos,
108                15,
109                None,
110            );
111            return InteractionResult::Success;
112        }
113        let state = context.world.get_block_state(context.hit_result.block_pos);
114        let is_clicked_face_sturdy = context.world.is_face_sturdy(
115            state,
116            context.hit_result.block_pos,
117            context.hit_result.direction,
118        );
119        if is_clicked_face_sturdy
120            && Self::grow_water_plant(
121                context.world,
122                context
123                    .hit_result
124                    .block_pos
125                    .relative(context.hit_result.direction),
126                context.hit_result.direction,
127            )
128        {
129            context.inv.with_item(|item| item.shrink(1));
130            context.world.level_event(
131                level_events::PARTICLES_AND_SOUND_PLANT_GROWTH,
132                context
133                    .hit_result
134                    .block_pos
135                    .relative(context.hit_result.direction),
136                15,
137                None,
138            );
139            return InteractionResult::Success;
140        }
141        InteractionResult::Pass
142    }
143}