steel_core/behavior/items/
bonemeal.rs1use 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#[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 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}