1use super::{
2 Arc, BLOCK_BEHAVIORS, BlockHitResult, Entity, GameType, ITEM_BEHAVIORS, InteractionHand,
3 InteractionResult, InventoryAccess, LivingEntity, Player, REGISTRY, SUseItem, UseOnContext,
4 World, adventure_mode, wrap_degrees,
5};
6use steel_registry::data_components::vanilla_components::USE_REMAINDER;
7use steel_registry::stat::vanilla_stat_types;
8
9pub fn use_item_on(
21 player: &Arc<Player>,
22 world: &Arc<World>,
23 hand: InteractionHand,
24 hit_result: &BlockHitResult,
25) -> InteractionResult {
26 let pos = hit_result.block_pos;
27 let state = world.get_block_state(pos);
28
29 if player.game_mode() == GameType::Spectator {
32 return InteractionResult::Pass;
33 }
34
35 let have_something = {
37 let inv = player.inventory.lock();
38 !inv.get_item_in_hand(InteractionHand::MainHand).is_empty()
39 || !inv.get_item_in_hand(InteractionHand::OffHand).is_empty()
40 };
41
42 let suppress_block_use = player.is_secondary_use_active() && have_something;
43
44 let block_behaviors = &*BLOCK_BEHAVIORS;
46 let item_behaviors = &*ITEM_BEHAVIORS;
47
48 if !suppress_block_use {
51 let Some(block) = REGISTRY.blocks.by_state_id(state) else {
52 return InteractionResult::Pass;
53 };
54 let behavior = block_behaviors.get_behavior(block);
55
56 let mut inventory_access = InventoryAccess::new(Arc::clone(&player.inventory), hand);
57
58 let block_result = behavior.use_item_on(
59 state,
60 world,
61 pos,
62 player,
63 hand,
64 hit_result,
65 &mut inventory_access,
66 );
67
68 if block_result.consumes_action() {
69 return block_result;
70 }
71
72 if matches!(block_result, InteractionResult::TryEmptyHandInteraction)
73 && hand == InteractionHand::MainHand
74 {
75 let empty_result = behavior.use_without_item(
76 state,
77 world,
78 pos,
79 player,
80 hit_result,
81 &mut inventory_access,
82 );
83
84 if empty_result.consumes_action() {
85 return empty_result;
86 }
87 }
88 }
89
90 let inventory_access = InventoryAccess::new(Arc::clone(&player.inventory), hand);
91 let (is_empty, original_count, item_ref, stack_before_use) =
92 inventory_access.with_item(|item| (item.is_empty(), item.count, item.item, item.clone()));
93
94 if !is_empty {
95 if player.is_item_on_cooldown(&stack_before_use) {
96 return InteractionResult::Pass;
97 }
98 if !player.abilities.lock().may_build
99 && !adventure_mode::can_place_on(&stack_before_use, world, pos)
100 {
101 return InteractionResult::Pass;
102 }
103
104 let mut context = UseOnContext::new(
105 player,
106 hand,
107 hit_result.clone(),
108 world,
109 Arc::clone(&player.inventory),
110 );
111 let item_behavior = item_behaviors.get_behavior(item_ref);
112 let result = item_behavior.use_on(&mut context);
113
114 if result.should_apply_item_use_side_effects() {
115 player.award_stat(&vanilla_stat_types::ITEM_USED, item_ref);
116
117 let extra = stack_before_use.get(USE_REMAINDER).and_then(|remainder| {
118 context.inv.with_item(|item| {
119 remainder.convert_into_remainder(
120 item,
121 stack_before_use.count(),
122 player.has_infinite_materials(),
123 )
124 })
125 });
126
127 if let Some(extra) = extra {
128 player.handle_extra_items_created_on_use(extra);
129 }
130
131 player.apply_item_use_cooldown(&stack_before_use);
132 }
133
134 if player.has_infinite_materials() {
137 context.inv.with_item(|item| item.count = original_count);
138 }
139
140 return result;
141 }
142
143 InteractionResult::Pass
144}
145
146#[cfg(test)]
147#[path = "item_interaction/use_on_tests.rs"]
148mod use_on_tests;
149
150pub fn use_item(
154 player: &Arc<Player>,
155 world: &Arc<World>,
156 hand: InteractionHand,
157) -> InteractionResult {
158 if player.game_mode() == GameType::Spectator {
160 return InteractionResult::Pass;
161 }
162
163 let inventory_access = InventoryAccess::new(Arc::clone(&player.inventory), hand);
164 let (is_empty, item_ref, stack_before_use) =
165 inventory_access.with_item(|item| (item.is_empty(), item.item, item.clone()));
166
167 if !is_empty {
168 if player.is_item_on_cooldown(&stack_before_use) {
169 return InteractionResult::Pass;
170 }
171
172 let mut context = crate::behavior::UseItemContext::new(
173 player,
174 hand,
175 world,
176 Arc::clone(&player.inventory),
177 );
178
179 let item_behaviors = &*ITEM_BEHAVIORS;
181 let item_behavior = item_behaviors.get_behavior(item_ref);
182 let is_instantly_used =
183 item_behavior.get_use_duration(&stack_before_use, player.as_ref()) <= 0;
184
185 let result = item_behavior.use_item(&mut context);
186
187 if is_instantly_used && result.should_apply_item_use_side_effects() {
188 player.apply_item_use_cooldown(&stack_before_use);
189 }
190
191 return result;
192 }
193
194 InteractionResult::Pass
195}
196
197impl Player {
198 pub fn handle_use_item(self: &Arc<Self>, packet: SUseItem) {
200 if !self.has_client_loaded() {
201 return;
202 }
203
204 self.reset_last_action_time();
205
206 log::debug!(
207 "Player {} used {:?} (sequence: {}, yaw: {}, pitch: {})",
208 self.gameprofile.name(),
209 packet.hand,
210 packet.sequence,
211 packet.y_rot,
212 packet.x_rot
213 );
214
215 self.ack_block_changes_up_to(packet.sequence);
216
217 let item_stack_is_empty = {
218 let inventory = self.inventory.lock();
219 inventory.get_item_in_hand(packet.hand).is_empty()
220 };
221 if item_stack_is_empty {
222 return;
223 }
224
225 let current_rotation = self.rotation();
226 let target_component = |value: f32, current: f32| {
228 if value.is_finite() {
229 wrap_degrees(value)
230 } else {
231 current
232 }
233 };
234 let target_rotation = (
235 target_component(packet.y_rot, current_rotation.0),
236 target_component(packet.x_rot, current_rotation.1),
237 );
238 if target_rotation != current_rotation {
239 self.set_rotation(target_rotation);
240 }
241
242 let world = self.get_world();
243 let animation = self.interact_animation(packet.hand);
244 let result = use_item(self, &world, packet.hand);
245 self.swing_after_interaction(packet.hand, animation, result);
246
247 self.broadcast_inventory_changes();
248 }
249}
250
251#[cfg(test)]
252mod tests {
253 use std::sync::Arc;
254
255 use super::use_item;
256 use crate::behavior::{InteractionResult, init_behaviors};
257 use crate::entity::Entity as _;
258 use crate::player::connection::NetworkConnection as _;
259 use crate::test_support::{TestPlayerBuilder, fresh_test_world, insert_ready_full_chunk};
260 use glam::DVec3;
261 use steel_protocol::packets::game::SUseItem;
262 use steel_registry::{
263 RegistryHolderSet,
264 blocks::properties::Direction,
265 data_component_predicate::DataComponentMatchers,
266 data_components::{
267 AdventureModePredicate, BlockPredicate, vanilla_components::CAN_PLACE_ON,
268 },
269 item_stack::ItemStack,
270 items::item::BlockHitResult,
271 vanilla_blocks, vanilla_items,
272 };
273 use steel_utils::{
274 BlockPos, ChunkPos,
275 types::{GameType, InteractionHand, UpdateFlags},
276 };
277
278 use super::use_item_on;
279
280 #[test]
281 fn use_item_discards_non_finite_rotation_components() {
282 let world_fixture = fresh_test_world("use_item_non_finite_rotation");
283 let world = &world_fixture.world;
284 init_behaviors();
285 let player = TestPlayerBuilder::new(Arc::clone(world), "TestPlayer", 1).build();
286 player.set_client_loaded(true);
287 player
288 .inventory
289 .lock()
290 .set_selected_item(ItemStack::new(&vanilla_items::STICK));
291 player.set_rotation((10.0, 20.0));
292
293 for (sequence, y_rot, x_rot, expected) in [
294 (1, f32::NAN, 30.0, (10.0, 30.0)),
295 (2, 40.0, f32::INFINITY, (40.0, 30.0)),
296 (3, f32::NEG_INFINITY, f32::NAN, (40.0, 30.0)),
297 ] {
298 player.handle_use_item(SUseItem {
299 hand: InteractionHand::MainHand,
300 sequence,
301 y_rot,
302 x_rot,
303 });
304 assert_eq!(player.rotation(), expected);
305 }
306 assert!(!player.connection.closed());
307 }
308
309 #[test]
310 fn adventure_mode_requires_can_place_on_before_item_use() {
311 let world_fixture = fresh_test_world("adventure_mode_can_place_on");
312 let world = &world_fixture.world;
313 init_behaviors();
314 let pos = BlockPos::new(1, 64, 0);
315 insert_ready_full_chunk(world, ChunkPos::from_block_pos(pos));
316 assert!(world.set_block(
317 pos,
318 vanilla_blocks::STONE.default_state(),
319 UpdateFlags::UPDATE_ALL,
320 ));
321
322 let player = TestPlayerBuilder::new(Arc::clone(world), "TestPlayer", 1).build();
323 player.restore_game_modes(GameType::Adventure, None);
324 player
325 .abilities
326 .lock()
327 .update_for_game_mode(GameType::Adventure);
328
329 let hit_result = BlockHitResult {
330 location: DVec3::new(1.5, 64.5, 0.5),
331 direction: Direction::Up,
332 block_pos: pos,
333 miss: false,
334 inside: false,
335 world_border_hit: false,
336 };
337
338 player
339 .inventory
340 .lock()
341 .set_selected_item(ItemStack::new(&vanilla_items::DIRT));
342 assert!(
343 !use_item_on(&player, world, InteractionHand::MainHand, &hit_result).consumes_action()
344 );
345 assert_eq!(
346 world.get_block_state(pos.above()),
347 vanilla_blocks::AIR.default_state()
348 );
349 assert_eq!(player.inventory.lock().get_selected_item().count(), 1);
350
351 let predicate = BlockPredicate::new(
352 Some(RegistryHolderSet::Direct(vec![&vanilla_blocks::STONE])),
353 None,
354 None,
355 DataComponentMatchers::ANY,
356 );
357 let can_place_on =
358 AdventureModePredicate::new(vec![predicate]).expect("one block predicate is valid");
359 let mut dirt = ItemStack::new(&vanilla_items::DIRT);
360 dirt.set(CAN_PLACE_ON, can_place_on);
361 player.inventory.lock().set_selected_item(dirt);
362
363 assert!(
364 use_item_on(&player, world, InteractionHand::MainHand, &hit_result).consumes_action()
365 );
366 assert_eq!(
367 world.get_block_state(pos.above()),
368 vanilla_blocks::DIRT.default_state()
369 );
370 assert_eq!(player.inventory.lock().get_selected_item().count(), 0);
371 }
372
373 #[test]
377 fn use_item_refuses_normal_food_at_full_hunger() {
378 let world_fixture = fresh_test_world("use_item_full_hunger_normal_food");
379 let world = &world_fixture.world;
380 init_behaviors();
381 let player = TestPlayerBuilder::new(Arc::clone(world), "TestPlayer", 1).build();
382 player.set_client_loaded(true);
383 player
384 .inventory
385 .lock()
386 .set_selected_item(ItemStack::new(&vanilla_items::APPLE));
387
388 let result = use_item(&player, world, InteractionHand::MainHand);
389
390 assert_eq!(result, InteractionResult::Fail);
391 assert_eq!(player.active_item_use_hand(), None);
392 }
393
394 #[test]
397 fn use_item_allows_always_edible_food_at_full_hunger() {
398 let world_fixture = fresh_test_world("use_item_full_hunger_always_edible_food");
399 let world = &world_fixture.world;
400 init_behaviors();
401 let player = TestPlayerBuilder::new(Arc::clone(world), "TestPlayer", 1).build();
402 player.set_client_loaded(true);
403 player
404 .inventory
405 .lock()
406 .set_selected_item(ItemStack::new(&vanilla_items::GOLDEN_APPLE));
407
408 let result = use_item(&player, world, InteractionHand::MainHand);
409
410 assert_eq!(result, InteractionResult::Consume);
411 assert_eq!(
412 player.active_item_use_hand(),
413 Some(InteractionHand::MainHand)
414 );
415 }
416}