1use glam::DVec3;
4use std::sync::Arc;
5use steel_registry::blocks::properties::Direction;
6use steel_registry::item_stack::ItemStack;
7use steel_utils::BlockPos;
8use steel_utils::locks::Shared;
9use steel_utils::types::InteractionHand;
10
11use crate::behavior::BlockStateBehaviorExt;
12use crate::entity::Entity;
13use crate::fluid::FluidStateExt;
14use crate::inventory::lock::{ContainerLockGuard, ContainerRef};
15use crate::player::Player;
16use crate::player::player_inventory::PlayerInventory;
17use crate::world::World;
18pub use steel_registry::items::item::BlockHitResult;
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum InteractionResult {
23 Success,
25 SuccessServer,
27 Consume,
29 Fail,
31 Pass,
33 TryEmptyHandInteraction,
35}
36
37impl InteractionResult {
38 #[must_use]
41 pub const fn consumes_action(self) -> bool {
42 matches!(
43 self,
44 InteractionResult::Success
45 | InteractionResult::SuccessServer
46 | InteractionResult::Consume
47 | InteractionResult::Fail
48 )
49 }
50
51 #[must_use]
53 pub const fn should_swing_server(self) -> bool {
54 matches!(self, InteractionResult::SuccessServer)
55 }
56
57 #[must_use]
60 pub const fn should_apply_item_use_side_effects(self) -> bool {
61 matches!(
62 self,
63 InteractionResult::Success
64 | InteractionResult::SuccessServer
65 | InteractionResult::Consume
66 )
67 }
68}
69
70pub struct BlockPlaceContext<'a> {
81 hit_pos: BlockPos,
85 clicked_face: Direction,
87 click_location: DVec3,
89 inside: bool,
91 place_pos: BlockPos,
97 replaces_clicked_block: bool,
101 pub world: &'a Arc<World>,
103 source: PlacementSource<'a>,
104 mode: PlacementMode,
105}
106
107impl<'a> BlockPlaceContext<'a> {
108 #[must_use]
110 pub fn new(
111 world: &'a Arc<World>,
112 source: PlacementSource<'a>,
113 hit_result: &BlockHitResult,
114 ) -> Self {
115 Self::with_mode(world, source, hit_result, PlacementMode::Standard)
116 }
117
118 fn with_mode(
119 world: &'a Arc<World>,
120 source: PlacementSource<'a>,
121 hit_result: &BlockHitResult,
122 mode: PlacementMode,
123 ) -> Self {
124 let hit_pos = hit_result.block_pos;
125 let mut context = Self {
126 hit_pos,
127 clicked_face: hit_result.direction,
128 click_location: hit_result.location,
129 inside: hit_result.inside,
130 place_pos: hit_pos,
131 replaces_clicked_block: true,
132 world,
133 source,
134 mode,
135 };
136 context.resolve_placement_geometry();
137 context
138 }
139
140 #[must_use]
142 pub fn directional(
143 world: &'a Arc<World>,
144 pos: BlockPos,
145 direction: Direction,
146 stack: &'a mut ItemStack,
147 clicked_face: Direction,
148 ) -> Self {
149 let hit_result = BlockHitResult {
150 location: DVec3::new(
151 f64::from(pos.x()) + 0.5,
152 f64::from(pos.y()),
153 f64::from(pos.z()) + 0.5,
154 ),
155 direction: clicked_face,
156 block_pos: pos,
157 miss: false,
158 inside: false,
159 world_border_hit: false,
160 };
161 let source = PlacementSource::direct(
162 None,
163 InteractionHand::MainHand,
164 stack,
165 PlacementOrientation::Directional { direction },
166 false,
167 );
168 Self::with_mode(world, source, &hit_result, PlacementMode::Directional)
169 }
170
171 fn resolve_placement_geometry(&mut self) {
172 self.place_pos = self.hit_pos;
173 self.replaces_clicked_block = true;
174 self.replaces_clicked_block = self
175 .world
176 .get_block_state(self.hit_pos)
177 .can_be_replaced(self);
178 if self.mode == PlacementMode::Standard && !self.replaces_clicked_block {
179 self.place_pos = self.clicked_face.relative(self.hit_pos);
180 }
181 }
182
183 #[must_use]
185 pub fn can_place(&self) -> bool {
186 if self.mode == PlacementMode::Directional {
187 return self
188 .world
189 .get_block_state(self.hit_pos)
190 .can_be_replaced(self);
191 }
192
193 self.replaces_clicked_block
194 || self
195 .world
196 .get_block_state(self.place_pos)
197 .can_be_replaced(self)
198 }
199
200 #[must_use]
202 pub fn at(mut self, pos: BlockPos, direction: Direction) -> Self {
203 let (step_x, step_y, step_z) = direction.offset();
204 self.hit_pos = pos;
205 self.clicked_face = direction;
206 self.click_location = DVec3::new(
207 f64::from(pos.x()) + 0.5 + f64::from(step_x) * 0.5,
208 f64::from(pos.y()) + 0.5 + f64::from(step_y) * 0.5,
209 f64::from(pos.z()) + 0.5 + f64::from(step_z) * 0.5,
210 );
211 self.inside = false;
212 self.mode = PlacementMode::Standard;
213 self.resolve_placement_geometry();
214 self
215 }
216
217 #[must_use]
219 pub const fn hit_pos(&self) -> BlockPos {
220 self.hit_pos
221 }
222
223 #[must_use]
225 pub const fn clicked_face(&self) -> Direction {
226 self.clicked_face
227 }
228
229 #[must_use]
231 pub const fn click_location(&self) -> DVec3 {
232 self.click_location
233 }
234
235 #[must_use]
237 pub const fn is_inside(&self) -> bool {
238 self.inside
239 }
240
241 #[must_use]
243 pub const fn place_pos(&self) -> BlockPos {
244 self.place_pos
245 }
246
247 #[must_use]
249 pub fn replaces_clicked_block(&self) -> bool {
250 if self.mode == PlacementMode::Directional {
251 self.can_place()
252 } else {
253 self.replaces_clicked_block
254 }
255 }
256
257 #[must_use]
259 pub const fn player(&self) -> Option<&Player> {
260 self.source.player()
261 }
262
263 #[must_use]
265 pub const fn hand(&self) -> InteractionHand {
266 self.source.hand()
267 }
268
269 pub fn with_item<R>(&self, f: impl FnOnce(&ItemStack) -> R) -> R {
271 self.source.with_item(f)
272 }
273
274 pub fn with_item_mut<R>(&mut self, f: impl FnOnce(&mut ItemStack) -> R) -> R {
276 self.source.with_item_mut(f)
277 }
278
279 #[must_use]
281 pub const fn source(&self) -> &PlacementSource<'a> {
282 &self.source
283 }
284
285 #[must_use]
287 pub fn horizontal_direction(&self) -> Direction {
288 self.source.orientation.horizontal_direction()
289 }
290
291 #[must_use]
293 pub const fn rotation(&self) -> f32 {
294 self.source.orientation.rotation()
295 }
296
297 #[must_use]
299 pub const fn is_secondary_use_active(&self) -> bool {
300 self.source.is_secondary_use_active
301 }
302
303 #[must_use]
312 pub fn get_nearest_looking_direction(&self) -> Direction {
313 self.source.orientation.nearest_looking_direction()
314 }
315
316 #[must_use]
320 pub const fn get_nearest_looking_vertical_direction(&self) -> Direction {
321 self.source.orientation.nearest_vertical_direction()
322 }
323 #[must_use]
329 pub fn get_nearest_looking_directions(&self) -> [Direction; 6] {
330 let (mut directions, adjust_for_replacement) = self.source.orientation.directions();
331
332 if adjust_for_replacement && !self.replaces_clicked_block {
334 let clicked_opposite = self.clicked_face.opposite();
335 if let Some(index) = directions.iter().position(|&d| d == clicked_opposite)
336 && index > 0
337 {
338 directions.copy_within(0..index, 1);
339 directions[0] = clicked_opposite;
340 }
341 }
342
343 directions
344 }
345
346 #[must_use]
348 pub fn is_water_source(&self) -> bool {
349 use crate::fluid::get_fluid_state;
350 let fluid_state = get_fluid_state(self.world, self.place_pos);
351 fluid_state.is_source() && fluid_state.is_water()
352 }
353
354 #[must_use]
356 pub fn is_full_water(&self) -> bool {
357 use crate::fluid::get_fluid_state;
358 let fluid_state = get_fluid_state(self.world, self.place_pos);
359 fluid_state.is_full() && fluid_state.is_water()
360 }
361}
362
363#[derive(Clone, Copy, Debug, Eq, PartialEq)]
364enum PlacementMode {
365 Standard,
366 Directional,
367}
368
369#[derive(Clone, Copy, Debug)]
371pub enum PlacementOrientation {
372 Player {
374 rotation: f32,
376 pitch: f32,
378 },
379 Directional {
381 direction: Direction,
383 },
384}
385
386impl PlacementOrientation {
387 fn nearest_looking_direction(self) -> Direction {
388 match self {
389 Self::Player { rotation, pitch } => Direction::ordered_by_nearest(rotation, pitch)[0],
390 Self::Directional { .. } => Direction::Down,
391 }
392 }
393
394 fn horizontal_direction(self) -> Direction {
395 match self {
396 Self::Player { rotation, .. } => Direction::from_yaw(rotation),
397 Self::Directional { direction } if direction.is_horizontal() => direction,
398 Self::Directional { .. } => Direction::North,
399 }
400 }
401
402 const fn rotation(self) -> f32 {
403 match self {
404 Self::Player { rotation, .. } => rotation,
405 Self::Directional { direction } => match direction {
406 Direction::Down | Direction::Up => -90.0,
407 Direction::South => 0.0,
408 Direction::West => 90.0,
409 Direction::North => 180.0,
410 Direction::East => 270.0,
411 },
412 }
413 }
414
415 const fn nearest_vertical_direction(self) -> Direction {
416 match self {
417 Self::Player { pitch, .. } if pitch < 0.0 => Direction::Up,
418 Self::Player { .. } | Self::Directional { .. } => Direction::Down,
419 }
420 }
421
422 fn directions(self) -> ([Direction; 6], bool) {
423 match self {
424 Self::Player { rotation, pitch } => {
425 (Direction::ordered_by_nearest(rotation, pitch), true)
426 }
427 Self::Directional { direction } => (directional_placement_directions(direction), false),
428 }
429 }
430}
431
432const fn directional_placement_directions(direction: Direction) -> [Direction; 6] {
433 match direction {
434 Direction::Down => [
435 Direction::Down,
436 Direction::North,
437 Direction::East,
438 Direction::South,
439 Direction::West,
440 Direction::Up,
441 ],
442 Direction::Up => [
443 Direction::Down,
444 Direction::Up,
445 Direction::North,
446 Direction::East,
447 Direction::South,
448 Direction::West,
449 ],
450 Direction::North => [
451 Direction::Down,
452 Direction::North,
453 Direction::East,
454 Direction::West,
455 Direction::Up,
456 Direction::South,
457 ],
458 Direction::South => [
459 Direction::Down,
460 Direction::South,
461 Direction::East,
462 Direction::West,
463 Direction::Up,
464 Direction::North,
465 ],
466 Direction::West => [
467 Direction::Down,
468 Direction::West,
469 Direction::South,
470 Direction::Up,
471 Direction::North,
472 Direction::East,
473 ],
474 Direction::East => [
475 Direction::Down,
476 Direction::East,
477 Direction::South,
478 Direction::Up,
479 Direction::North,
480 Direction::West,
481 ],
482 }
483}
484
485enum PlacementItemSource<'a> {
486 PlayerHand(InventoryAccess),
487 Direct(&'a mut ItemStack),
488}
489
490pub struct PlacementSource<'a> {
498 player: Option<&'a Player>,
499 hand: InteractionHand,
500 item: PlacementItemSource<'a>,
501 orientation: PlacementOrientation,
502 is_secondary_use_active: bool,
503}
504
505impl<'a> PlacementSource<'a> {
506 #[must_use]
508 pub fn player_hand(player: &'a Player, inv: &InventoryAccess) -> Self {
509 let (rotation, pitch) = player.rotation();
510 let hand = inv.hand;
511 Self {
512 player: Some(player),
513 hand,
514 item: PlacementItemSource::PlayerHand(InventoryAccess::new(
515 Arc::clone(&inv.inventory),
516 hand,
517 )),
518 orientation: PlacementOrientation::Player { rotation, pitch },
519 is_secondary_use_active: player.is_secondary_use_active(),
520 }
521 }
522
523 #[must_use]
525 pub const fn direct(
526 player: Option<&'a Player>,
527 hand: InteractionHand,
528 stack: &'a mut ItemStack,
529 orientation: PlacementOrientation,
530 is_secondary_use_active: bool,
531 ) -> Self {
532 Self {
533 player,
534 hand,
535 item: PlacementItemSource::Direct(stack),
536 orientation,
537 is_secondary_use_active,
538 }
539 }
540
541 #[must_use]
543 pub const fn player(&self) -> Option<&Player> {
544 self.player
545 }
546
547 #[must_use]
549 pub const fn hand(&self) -> InteractionHand {
550 self.hand
551 }
552
553 pub fn with_item<R>(&self, f: impl FnOnce(&ItemStack) -> R) -> R {
555 match &self.item {
556 PlacementItemSource::PlayerHand(inv) => inv.with_item(|item| f(item)),
557 PlacementItemSource::Direct(item) => f(item),
558 }
559 }
560
561 pub fn with_item_mut<R>(&mut self, f: impl FnOnce(&mut ItemStack) -> R) -> R {
563 match &mut self.item {
564 PlacementItemSource::PlayerHand(inv) => inv.with_item(f),
565 PlacementItemSource::Direct(item) => f(item),
566 }
567 }
568}
569
570#[derive(Clone)]
576pub struct InventoryAccess {
577 inventory: Shared<PlayerInventory>,
578 hand: InteractionHand,
579}
580
581impl InventoryAccess {
582 pub const fn new(inventory: Shared<PlayerInventory>, hand: InteractionHand) -> Self {
584 Self { inventory, hand }
585 }
586
587 pub fn with_item<R>(&self, f: impl FnOnce(&mut ItemStack) -> R) -> R {
589 let mut inventory = self.inventory.lock();
590 inventory.mutate_item_in_hand(self.hand, f)
591 }
592
593 pub fn with_inventory<R>(&self, f: impl FnOnce(&mut PlayerInventory) -> R) -> R {
595 let mut inventory = self.inventory.lock();
596 f(&mut inventory)
597 }
598
599 pub fn with_guard<R>(&self, f: impl FnOnce(&mut ContainerLockGuard) -> R) -> R {
604 let inv_ref = ContainerRef::from(self.inventory.clone());
605 let mut guard = ContainerLockGuard::lock_all(&[&inv_ref]);
606 f(&mut guard)
607 }
608}
609
610pub struct UseOnContext<'a> {
616 pub player: &'a Player,
618 pub hand: InteractionHand,
620 pub hit_result: BlockHitResult,
622 pub world: &'a Arc<World>,
624 pub inv: InventoryAccess,
626}
627
628impl<'a> UseOnContext<'a> {
629 #[must_use]
631 pub const fn new(
632 player: &'a Player,
633 hand: InteractionHand,
634 hit_result: BlockHitResult,
635 world: &'a Arc<World>,
636 inventory: Shared<PlayerInventory>,
637 ) -> Self {
638 Self {
639 player,
640 hand,
641 hit_result,
642 world,
643 inv: InventoryAccess::new(inventory, hand),
644 }
645 }
646
647 #[must_use]
649 pub fn build_place_context(&self) -> BlockPlaceContext<'a> {
650 BlockPlaceContext::new(
651 self.world,
652 PlacementSource::player_hand(self.player, &self.inv),
653 &self.hit_result,
654 )
655 }
656}
657
658pub struct UseItemContext<'a> {
663 pub player: &'a Player,
665 pub hand: InteractionHand,
667 pub world: &'a Arc<World>,
669 pub inv: InventoryAccess,
671}
672
673impl<'a> UseItemContext<'a> {
674 #[must_use]
676 pub const fn new(
677 player: &'a Player,
678 hand: InteractionHand,
679 world: &'a Arc<World>,
680 inventory: Shared<PlayerInventory>,
681 ) -> Self {
682 Self {
683 player,
684 hand,
685 world,
686 inv: InventoryAccess::new(inventory, hand),
687 }
688 }
689}
690
691#[cfg(test)]
692mod tests {
693 use std::sync::Arc;
694
695 use steel_registry::data_components::vanilla_components::BLOCK_STATE;
696 use steel_registry::init_vanilla_registry;
697 use steel_registry::vanilla_items;
698 use steel_utils::locks::SyncMutex;
699
700 use super::*;
701 use crate::behavior::init_behaviors;
702 use crate::inventory::container::Container;
703 use crate::player::player_inventory::PlayerInventory;
704 use crate::test_support::test_world;
705
706 #[test]
707 fn item_use_side_effects_apply_to_all_success_variants() {
708 assert!(InteractionResult::Success.should_apply_item_use_side_effects());
709 assert!(InteractionResult::SuccessServer.should_apply_item_use_side_effects());
710 assert!(InteractionResult::Consume.should_apply_item_use_side_effects());
711 assert!(!InteractionResult::Fail.should_apply_item_use_side_effects());
712 assert!(!InteractionResult::Pass.should_apply_item_use_side_effects());
713 assert!(!InteractionResult::TryEmptyHandInteraction.should_apply_item_use_side_effects());
714 }
715
716 #[test]
717 fn player_hand_source_reads_current_components_and_mutates_the_hand() {
718 init_vanilla_registry();
719
720 let inventory = Arc::new(SyncMutex::new(PlayerInventory::new()));
721 inventory
722 .lock()
723 .set_item(0, ItemStack::with_count(&vanilla_items::LIGHT, 2));
724 let access = InventoryAccess::new(inventory.clone(), InteractionHand::MainHand);
725 let mut source = PlacementSource {
726 player: None,
727 hand: InteractionHand::MainHand,
728 item: PlacementItemSource::PlayerHand(access),
729 orientation: PlacementOrientation::Player {
730 rotation: 0.0,
731 pitch: 0.0,
732 },
733 is_secondary_use_active: false,
734 };
735
736 assert!(source.with_item(|item| item.get(BLOCK_STATE).is_some()));
737 source.with_item_mut(|item| item.shrink(1));
738 assert_eq!(
739 inventory
740 .lock()
741 .get_item_in_hand(InteractionHand::MainHand)
742 .count(),
743 1
744 );
745 }
746
747 #[test]
748 fn replacement_dispatch_does_not_hold_the_inventory_lock() {
749 init_vanilla_registry();
750 init_behaviors();
751
752 let inventory = Arc::new(SyncMutex::new(PlayerInventory::new()));
753 inventory
754 .lock()
755 .set_item(0, ItemStack::new(&vanilla_items::STONE));
756 let source = PlacementSource {
757 player: None,
758 hand: InteractionHand::MainHand,
759 item: PlacementItemSource::PlayerHand(InventoryAccess::new(
760 inventory,
761 InteractionHand::MainHand,
762 )),
763 orientation: PlacementOrientation::Player {
764 rotation: 0.0,
765 pitch: 0.0,
766 },
767 is_secondary_use_active: false,
768 };
769 let hit_result = BlockHitResult {
770 location: DVec3::ZERO,
771 direction: Direction::Up,
772 block_pos: BlockPos::new(0, 80, 0),
773 miss: false,
774 inside: false,
775 world_border_hit: false,
776 };
777
778 let context = BlockPlaceContext::new(test_world(), source, &hit_result);
781 assert!(context.replaces_clicked_block());
782 }
783
784 #[test]
785 fn direct_source_mutates_the_callers_exact_stack() {
786 init_vanilla_registry();
787
788 let mut stack = ItemStack::with_count(&vanilla_items::LIGHT, 2);
789 {
790 let mut source = PlacementSource::direct(
791 None,
792 InteractionHand::MainHand,
793 &mut stack,
794 PlacementOrientation::Directional {
795 direction: Direction::North,
796 },
797 false,
798 );
799 assert!(source.with_item(|item| item.get(BLOCK_STATE).is_some()));
800 source.with_item_mut(|item| item.shrink(1));
801 }
802 assert_eq!(stack.count(), 1);
803 }
804
805 #[test]
806 fn at_changes_geometry_and_retains_the_direct_source() {
807 init_vanilla_registry();
808 init_behaviors();
809
810 let mut stack = ItemStack::new(&vanilla_items::STONE);
811 let hit_result = BlockHitResult {
812 location: DVec3::ZERO,
813 direction: Direction::Up,
814 block_pos: BlockPos::new(0, 80, 0),
815 miss: false,
816 inside: true,
817 world_border_hit: false,
818 };
819 let source = PlacementSource::direct(
820 None,
821 InteractionHand::MainHand,
822 &mut stack,
823 PlacementOrientation::Player {
824 rotation: 0.0,
825 pitch: 0.0,
826 },
827 false,
828 );
829 let context = BlockPlaceContext::new(test_world(), source, &hit_result);
830 let shifted_pos = BlockPos::new(4, 90, 7);
831 let mut shifted = context.at(shifted_pos, Direction::East);
832
833 assert_eq!(shifted.hit_pos(), shifted_pos);
834 assert_eq!(shifted.place_pos(), shifted_pos);
835 assert_eq!(shifted.clicked_face(), Direction::East);
836 assert_eq!(shifted.click_location(), DVec3::new(5.0, 90.5, 7.5));
837 assert!(!shifted.is_inside());
838 assert!(shifted.with_item(|item| item.is(&vanilla_items::STONE)));
839 shifted.with_item_mut(|item| item.shrink(1));
840 drop(shifted);
841 assert!(stack.is_empty());
842 }
843
844 #[test]
845 fn directional_context_uses_vanilla_direction_order() {
846 init_vanilla_registry();
847 init_behaviors();
848
849 let mut stack = ItemStack::new(&vanilla_items::STONE);
850 let context = BlockPlaceContext::directional(
851 test_world(),
852 BlockPos::new(2, 80, 3),
853 Direction::West,
854 &mut stack,
855 Direction::Up,
856 );
857
858 assert!(context.player().is_none());
859 assert_eq!(context.horizontal_direction(), Direction::West);
860 assert_eq!(
861 context.get_nearest_looking_directions(),
862 [
863 Direction::Down,
864 Direction::West,
865 Direction::South,
866 Direction::Up,
867 Direction::North,
868 Direction::East,
869 ]
870 );
871 }
872
873 #[test]
874 fn singular_look_direction_is_not_reordered_around_clicked_face() {
875 init_vanilla_registry();
876 init_behaviors();
877
878 let mut stack = ItemStack::new(&vanilla_items::PISTON);
879 let source = PlacementSource::direct(
880 None,
881 InteractionHand::MainHand,
882 &mut stack,
883 PlacementOrientation::Player {
884 rotation: 0.0,
885 pitch: 80.0,
886 },
887 false,
888 );
889 let hit_result = BlockHitResult {
890 location: DVec3::ZERO,
891 direction: Direction::East,
892 block_pos: BlockPos::new(10, 80, 10),
893 miss: false,
894 inside: false,
895 world_border_hit: false,
896 };
897 let mut context = BlockPlaceContext::new(test_world(), source, &hit_result);
898 context.replaces_clicked_block = false;
899
900 assert_eq!(context.get_nearest_looking_direction(), Direction::Down);
901 assert_eq!(context.get_nearest_looking_directions()[0], Direction::West);
902 }
903}