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