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 data_components::vanilla_components::USE_EFFECTS,
8 item_stack::ItemStack,
9 level_events, vanilla_blocks, vanilla_game_events,
10};
11use steel_utils::{BlockPos, Direction, types::UpdateFlags};
12
13use crate::{
14 behavior::{BLOCK_BEHAVIORS, InteractionResult, ItemBehavior, UseOnContext},
15 entity::Entity,
16 world::{LevelReader as _, World},
17};
18
19#[item_behavior]
21pub struct BoneMealItem;
22
23impl BoneMealItem {
24 fn cause_finish_use_vibration(context: &UseOnContext<'_>) {
25 let interact_vibrations = context.inv.with_item(|item| {
26 item.get(USE_EFFECTS)
27 .is_some_and(|effects| effects.interact_vibrations)
28 });
29 if interact_vibrations {
30 context
31 .player
32 .game_event(&vanilla_game_events::ITEM_INTERACT_FINISH);
33 }
34 }
35
36 fn grow(world: &Arc<World>, pos: BlockPos) -> bool {
37 let state = world.get_block_state(pos);
38 let Some(behavior) = BLOCK_BEHAVIORS.get_behavior_for_state(state) else {
39 return false;
40 };
41 if let Some(bonemealable) = behavior.as_bonemealable() {
42 if !bonemealable.is_valid_bonemeal_target(state, world.as_ref(), pos) {
43 return false;
44 }
45
46 let mut rng = rand::rng();
47 if bonemealable.is_bonemeal_success(state, world, &mut rng, pos) {
48 bonemealable.perform_bonemeal(state, world, &mut rng, pos);
49 }
50
51 return true;
52 }
53 false
54 }
55
56 fn grow_water_plant(world: &Arc<World>, pos: BlockPos, _clicked_face: Direction) -> bool {
57 let state = world.get_block_state(pos);
58 if state.get_block() != &vanilla_blocks::WATER || !state.get_fluid_state().is_full() {
59 return false;
60 }
61
62 let Some(bonemealable) = BLOCK_BEHAVIORS
63 .get_behavior(&vanilla_blocks::SEAGRASS)
64 .as_bonemealable()
65 else {
66 return false;
67 };
68
69 let mut rng = rand::rng();
70
71 'outer: for i in 0..128 {
72 let mut new_pos = pos;
73 let new_state = vanilla_blocks::SEAGRASS.default_state();
74
75 for _ in 0..(i / 16) {
76 new_pos = new_pos.offset(
77 rng.random_range(0i32..3) - 1,
78 (rng.random_range(0i32..3) - 1) * rng.random_range(0i32..3) / 2,
79 rng.random_range(0i32..3) - 1,
80 );
81
82 if is_offset_shape_full_block(
83 world
84 .get_block_state(new_pos)
85 .get_collision_shape_at(new_pos),
86 ) {
87 continue 'outer;
88 }
89 }
90
91 let Some(behavior) = BLOCK_BEHAVIORS.get_behavior_for_state(new_state) else {
94 return false;
95 };
96
97 if behavior.can_survive(new_state, world, new_pos) {
98 let current_state = world.get_block_state(new_pos);
99 if current_state.get_block() == &vanilla_blocks::WATER
100 && current_state.get_fluid_state().is_full()
101 {
102 world.set_block(new_pos, new_state, UpdateFlags::UPDATE_ALL);
103 } else if current_state.get_block() == &vanilla_blocks::SEAGRASS
104 && bonemealable.is_valid_bonemeal_target(current_state, world.as_ref(), new_pos)
105 && rng.random_range(0..10) == 0
106 {
107 bonemealable.perform_bonemeal(current_state, world, &mut rng, new_pos);
108 }
109 }
110 }
111
112 true
113 }
114}
115
116impl ItemBehavior for BoneMealItem {
117 fn use_on(&self, context: &mut UseOnContext) -> InteractionResult {
118 if Self::grow(context.world, context.hit_result.block_pos) {
119 context.inv.with_item(ItemStack::shrink_one);
120 Self::cause_finish_use_vibration(context);
121 context.world.level_event(
122 level_events::PARTICLES_AND_SOUND_PLANT_GROWTH,
123 context.hit_result.block_pos,
124 15,
125 None,
126 );
127 return InteractionResult::Success;
128 }
129 let state = context.world.get_block_state(context.hit_result.block_pos);
130 let is_clicked_face_sturdy = context.world.is_face_sturdy(
131 state,
132 context.hit_result.block_pos,
133 context.hit_result.direction,
134 );
135 if is_clicked_face_sturdy
136 && Self::grow_water_plant(
137 context.world,
138 context
139 .hit_result
140 .block_pos
141 .relative(context.hit_result.direction),
142 context.hit_result.direction,
143 )
144 {
145 context.inv.with_item(ItemStack::shrink_one);
146 Self::cause_finish_use_vibration(context);
147 context.world.level_event(
148 level_events::PARTICLES_AND_SOUND_PLANT_GROWTH,
149 context
150 .hit_result
151 .block_pos
152 .relative(context.hit_result.direction),
153 15,
154 None,
155 );
156 return InteractionResult::Success;
157 }
158 InteractionResult::Pass
159 }
160}