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_registry::items::ItemRef;
9use steel_utils::BlockPos;
10use steel_utils::locks::Shared;
11use steel_utils::types::InteractionHand;
12
13use crate::behavior::{BlockStateBehaviorExt, ITEM_BEHAVIORS};
14use crate::entity::{Entity, LivingEntity};
15use crate::fluid::FluidStateExt;
16use crate::inventory::equipment::EquipmentSlot;
17use crate::inventory::lock::{ContainerLockGuard, ContainerRef};
18use crate::player::Player;
19use crate::player::player_inventory::PlayerInventory;
20use crate::world::World;
21pub use steel_registry::items::item::BlockHitResult;
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum InteractionResult {
26 Success,
28 SuccessWithoutItem,
30 SuccessServer,
32 Consume,
34 Fail,
36 Pass,
38 TryEmptyHandInteraction,
40}
41
42impl InteractionResult {
43 #[must_use]
46 pub const fn consumes_action(self) -> bool {
47 matches!(
48 self,
49 InteractionResult::Success
50 | InteractionResult::SuccessWithoutItem
51 | InteractionResult::SuccessServer
52 | InteractionResult::Consume
53 | InteractionResult::Fail
54 )
55 }
56
57 #[must_use]
59 pub const fn should_swing(self) -> bool {
60 matches!(
61 self,
62 InteractionResult::Success
63 | InteractionResult::SuccessWithoutItem
64 | InteractionResult::SuccessServer
65 )
66 }
67
68 #[must_use]
71 pub const fn should_apply_item_use_side_effects(self) -> bool {
72 matches!(
73 self,
74 InteractionResult::Success
75 | InteractionResult::SuccessServer
76 | InteractionResult::Consume
77 )
78 }
79}
80
81pub struct BlockPlaceContext<'a> {
92 hit_pos: BlockPos,
96 clicked_face: Direction,
98 click_location: DVec3,
100 inside: bool,
102 place_pos: BlockPos,
108 replaces_clicked_block: bool,
112 pub world: &'a Arc<World>,
114 source: PlacementSource<'a>,
115 mode: PlacementMode,
116}
117
118impl<'a> BlockPlaceContext<'a> {
119 #[must_use]
121 pub fn new(
122 world: &'a Arc<World>,
123 source: PlacementSource<'a>,
124 hit_result: &BlockHitResult,
125 ) -> Self {
126 Self::with_mode(world, source, hit_result, PlacementMode::Standard)
127 }
128
129 fn with_mode(
130 world: &'a Arc<World>,
131 source: PlacementSource<'a>,
132 hit_result: &BlockHitResult,
133 mode: PlacementMode,
134 ) -> Self {
135 let hit_pos = hit_result.block_pos;
136 let mut context = Self {
137 hit_pos,
138 clicked_face: hit_result.direction,
139 click_location: hit_result.location,
140 inside: hit_result.inside,
141 place_pos: hit_pos,
142 replaces_clicked_block: true,
143 world,
144 source,
145 mode,
146 };
147 context.resolve_placement_geometry();
148 context
149 }
150
151 #[must_use]
153 pub fn directional(
154 world: &'a Arc<World>,
155 pos: BlockPos,
156 direction: Direction,
157 stack: &'a mut ItemStack,
158 clicked_face: Direction,
159 ) -> Self {
160 let hit_result = BlockHitResult {
161 location: DVec3::new(
162 f64::from(pos.x()) + 0.5,
163 f64::from(pos.y()),
164 f64::from(pos.z()) + 0.5,
165 ),
166 direction: clicked_face,
167 block_pos: pos,
168 miss: false,
169 inside: false,
170 world_border_hit: false,
171 };
172 let source = PlacementSource::direct(
173 None,
174 InteractionHand::MainHand,
175 stack,
176 PlacementOrientation::Directional { direction },
177 false,
178 );
179 Self::with_mode(world, source, &hit_result, PlacementMode::Directional)
180 }
181
182 fn resolve_placement_geometry(&mut self) {
183 self.place_pos = self.hit_pos;
184 self.replaces_clicked_block = true;
185 self.replaces_clicked_block = self
186 .world
187 .get_block_state(self.hit_pos)
188 .can_be_replaced(self);
189 if self.mode == PlacementMode::Standard && !self.replaces_clicked_block {
190 self.place_pos = self.clicked_face.relative(self.hit_pos);
191 }
192 }
193
194 #[must_use]
196 pub fn can_place(&self) -> bool {
197 if self.mode == PlacementMode::Directional {
198 return self
199 .world
200 .get_block_state(self.hit_pos)
201 .can_be_replaced(self);
202 }
203
204 self.replaces_clicked_block
205 || self
206 .world
207 .get_block_state(self.place_pos)
208 .can_be_replaced(self)
209 }
210
211 #[must_use]
213 pub fn at(mut self, pos: BlockPos, direction: Direction) -> Self {
214 let (step_x, step_y, step_z) = direction.offset();
215 self.hit_pos = pos;
216 self.clicked_face = direction;
217 self.click_location = DVec3::new(
218 f64::from(pos.x()) + 0.5 + f64::from(step_x) * 0.5,
219 f64::from(pos.y()) + 0.5 + f64::from(step_y) * 0.5,
220 f64::from(pos.z()) + 0.5 + f64::from(step_z) * 0.5,
221 );
222 self.inside = false;
223 self.mode = PlacementMode::Standard;
224 self.resolve_placement_geometry();
225 self
226 }
227
228 #[must_use]
230 pub const fn hit_pos(&self) -> BlockPos {
231 self.hit_pos
232 }
233
234 #[must_use]
236 pub const fn clicked_face(&self) -> Direction {
237 self.clicked_face
238 }
239
240 #[must_use]
242 pub const fn click_location(&self) -> DVec3 {
243 self.click_location
244 }
245
246 #[must_use]
248 pub const fn is_inside(&self) -> bool {
249 self.inside
250 }
251
252 #[must_use]
254 pub const fn place_pos(&self) -> BlockPos {
255 self.place_pos
256 }
257
258 #[must_use]
260 pub fn replaces_clicked_block(&self) -> bool {
261 if self.mode == PlacementMode::Directional {
262 self.can_place()
263 } else {
264 self.replaces_clicked_block
265 }
266 }
267
268 #[must_use]
270 pub const fn player(&self) -> Option<&Player> {
271 self.source.player()
272 }
273
274 #[must_use]
276 pub const fn hand(&self) -> InteractionHand {
277 self.source.hand()
278 }
279
280 pub fn with_item<R>(&self, f: impl FnOnce(&ItemStack) -> R) -> R {
282 self.source.with_item(f)
283 }
284
285 pub fn with_item_mut<R>(&mut self, f: impl FnOnce(&mut ItemStack) -> R) -> R {
287 self.source.with_item_mut(f)
288 }
289
290 #[must_use]
292 pub const fn source(&self) -> &PlacementSource<'a> {
293 &self.source
294 }
295
296 #[must_use]
298 pub fn horizontal_direction(&self) -> Direction {
299 self.source.orientation.horizontal_direction()
300 }
301
302 #[must_use]
304 pub const fn rotation(&self) -> f32 {
305 self.source.orientation.rotation()
306 }
307
308 #[must_use]
310 pub const fn is_secondary_use_active(&self) -> bool {
311 self.source.is_secondary_use_active
312 }
313
314 #[must_use]
323 pub fn get_nearest_looking_direction(&self) -> Direction {
324 self.source.orientation.nearest_looking_direction()
325 }
326
327 #[must_use]
331 pub const fn get_nearest_looking_vertical_direction(&self) -> Direction {
332 self.source.orientation.nearest_vertical_direction()
333 }
334 #[must_use]
340 pub fn get_nearest_looking_directions(&self) -> [Direction; 6] {
341 let (mut directions, adjust_for_replacement) = self.source.orientation.directions();
342
343 if adjust_for_replacement && !self.replaces_clicked_block {
345 let clicked_opposite = self.clicked_face.opposite();
346 if let Some(index) = directions.iter().position(|&d| d == clicked_opposite)
347 && index > 0
348 {
349 directions.copy_within(0..index, 1);
350 directions[0] = clicked_opposite;
351 }
352 }
353
354 directions
355 }
356
357 #[must_use]
359 pub fn is_water_source(&self) -> bool {
360 use crate::fluid::get_fluid_state;
361 let fluid_state = get_fluid_state(self.world, self.place_pos);
362 fluid_state.is_source() && fluid_state.is_water()
363 }
364
365 #[must_use]
367 pub fn is_full_water(&self) -> bool {
368 use crate::fluid::get_fluid_state;
369 let fluid_state = get_fluid_state(self.world, self.place_pos);
370 fluid_state.is_full() && fluid_state.is_water()
371 }
372}
373
374#[derive(Clone, Copy, Debug, Eq, PartialEq)]
375enum PlacementMode {
376 Standard,
377 Directional,
378}
379
380#[derive(Clone, Copy, Debug)]
382pub enum PlacementOrientation {
383 Player {
385 rotation: f32,
387 pitch: f32,
389 },
390 Directional {
392 direction: Direction,
394 },
395}
396
397impl PlacementOrientation {
398 fn nearest_looking_direction(self) -> Direction {
399 match self {
400 Self::Player { rotation, pitch } => Direction::ordered_by_nearest(rotation, pitch)[0],
401 Self::Directional { .. } => Direction::Down,
402 }
403 }
404
405 fn horizontal_direction(self) -> Direction {
406 match self {
407 Self::Player { rotation, .. } => Direction::from_yaw(rotation),
408 Self::Directional { direction } if direction.is_horizontal() => direction,
409 Self::Directional { .. } => Direction::North,
410 }
411 }
412
413 const fn rotation(self) -> f32 {
414 match self {
415 Self::Player { rotation, .. } => rotation,
416 Self::Directional { direction } => match direction {
417 Direction::Down | Direction::Up => -DEGREE_90,
418 Direction::South => 0.0,
419 Direction::West => DEGREE_90,
420 Direction::North => DEGREE_180,
421 Direction::East => DEGREE_270,
422 },
423 }
424 }
425
426 const fn nearest_vertical_direction(self) -> Direction {
427 match self {
428 Self::Player { pitch, .. } if pitch < 0.0 => Direction::Up,
429 Self::Player { .. } | Self::Directional { .. } => Direction::Down,
430 }
431 }
432
433 fn directions(self) -> ([Direction; 6], bool) {
434 match self {
435 Self::Player { rotation, pitch } => {
436 (Direction::ordered_by_nearest(rotation, pitch), true)
437 }
438 Self::Directional { direction } => (directional_placement_directions(direction), false),
439 }
440 }
441}
442
443const fn directional_placement_directions(direction: Direction) -> [Direction; 6] {
444 match direction {
445 Direction::Down => [
446 Direction::Down,
447 Direction::North,
448 Direction::East,
449 Direction::South,
450 Direction::West,
451 Direction::Up,
452 ],
453 Direction::Up => [
454 Direction::Down,
455 Direction::Up,
456 Direction::North,
457 Direction::East,
458 Direction::South,
459 Direction::West,
460 ],
461 Direction::North => [
462 Direction::Down,
463 Direction::North,
464 Direction::East,
465 Direction::West,
466 Direction::Up,
467 Direction::South,
468 ],
469 Direction::South => [
470 Direction::Down,
471 Direction::South,
472 Direction::East,
473 Direction::West,
474 Direction::Up,
475 Direction::North,
476 ],
477 Direction::West => [
478 Direction::Down,
479 Direction::West,
480 Direction::South,
481 Direction::Up,
482 Direction::North,
483 Direction::East,
484 ],
485 Direction::East => [
486 Direction::Down,
487 Direction::East,
488 Direction::South,
489 Direction::Up,
490 Direction::North,
491 Direction::West,
492 ],
493 }
494}
495
496enum PlacementItemSource<'a> {
497 PlayerHand(InventoryAccess),
498 Direct(&'a mut ItemStack),
499}
500
501pub struct PlacementSource<'a> {
509 player: Option<&'a Player>,
510 hand: InteractionHand,
511 item: PlacementItemSource<'a>,
512 orientation: PlacementOrientation,
513 is_secondary_use_active: bool,
514}
515
516impl<'a> PlacementSource<'a> {
517 #[must_use]
519 pub fn player_hand(player: &'a Player, inv: &InventoryAccess) -> Self {
520 let (rotation, pitch) = player.rotation();
521 let hand = inv.hand;
522 Self {
523 player: Some(player),
524 hand,
525 item: PlacementItemSource::PlayerHand(InventoryAccess::new(
526 Arc::clone(&inv.inventory),
527 hand,
528 )),
529 orientation: PlacementOrientation::Player { rotation, pitch },
530 is_secondary_use_active: player.is_secondary_use_active(),
531 }
532 }
533
534 #[must_use]
536 pub const fn direct(
537 player: Option<&'a Player>,
538 hand: InteractionHand,
539 stack: &'a mut ItemStack,
540 orientation: PlacementOrientation,
541 is_secondary_use_active: bool,
542 ) -> Self {
543 Self {
544 player,
545 hand,
546 item: PlacementItemSource::Direct(stack),
547 orientation,
548 is_secondary_use_active,
549 }
550 }
551
552 #[must_use]
554 pub const fn player(&self) -> Option<&Player> {
555 self.player
556 }
557
558 #[must_use]
560 pub const fn hand(&self) -> InteractionHand {
561 self.hand
562 }
563
564 pub fn with_item<R>(&self, f: impl FnOnce(&ItemStack) -> R) -> R {
566 match &self.item {
567 PlacementItemSource::PlayerHand(inv) => inv.with_item(|item| f(item)),
568 PlacementItemSource::Direct(item) => f(item),
569 }
570 }
571
572 pub fn with_item_mut<R>(&mut self, f: impl FnOnce(&mut ItemStack) -> R) -> R {
574 match &mut self.item {
575 PlacementItemSource::PlayerHand(inv) => inv.with_item(f),
576 PlacementItemSource::Direct(item) => f(item),
577 }
578 }
579}
580
581#[derive(Clone)]
587pub struct InventoryAccess {
588 inventory: Shared<PlayerInventory>,
589 hand: InteractionHand,
590}
591
592impl InventoryAccess {
593 pub const fn new(inventory: Shared<PlayerInventory>, hand: InteractionHand) -> Self {
595 Self { inventory, hand }
596 }
597
598 pub fn with_item<R>(&self, f: impl FnOnce(&mut ItemStack) -> R) -> R {
600 let mut inventory = self.inventory.lock();
601 inventory.mutate_item_in_hand(self.hand, f)
602 }
603
604 pub fn with_inventory<R>(&self, f: impl FnOnce(&mut PlayerInventory) -> R) -> R {
606 let mut inventory = self.inventory.lock();
607 f(&mut inventory)
608 }
609
610 pub fn with_guard<R>(&self, f: impl FnOnce(&mut ContainerLockGuard) -> R) -> R {
615 let inv_ref = ContainerRef::from(Arc::clone(&self.inventory));
616 let mut guard = ContainerLockGuard::lock_all(&[&inv_ref]);
617 f(&mut guard)
618 }
619}
620
621pub struct UseOnContext<'a> {
627 pub player: &'a Arc<Player>,
629 pub hand: InteractionHand,
631 pub hit_result: BlockHitResult,
633 pub world: &'a Arc<World>,
635 pub inv: InventoryAccess,
637}
638
639impl<'a> UseOnContext<'a> {
640 #[must_use]
642 pub const fn new(
643 player: &'a Arc<Player>,
644 hand: InteractionHand,
645 hit_result: BlockHitResult,
646 world: &'a Arc<World>,
647 inventory: Shared<PlayerInventory>,
648 ) -> Self {
649 Self {
650 player,
651 hand,
652 hit_result,
653 world,
654 inv: InventoryAccess::new(inventory, hand),
655 }
656 }
657
658 #[must_use]
660 pub fn build_place_context(&self) -> BlockPlaceContext<'a> {
661 BlockPlaceContext::new(
662 self.world,
663 PlacementSource::player_hand(self.player, &self.inv),
664 &self.hit_result,
665 )
666 }
667}
668
669pub struct UseItemContext<'a> {
674 pub player: &'a Arc<Player>,
676 pub hand: InteractionHand,
678 pub world: &'a Arc<World>,
680 pub inv: InventoryAccess,
682}
683
684impl<'a> UseItemContext<'a> {
685 #[must_use]
687 pub const fn new(
688 player: &'a Arc<Player>,
689 hand: InteractionHand,
690 world: &'a Arc<World>,
691 inventory: Shared<PlayerInventory>,
692 ) -> Self {
693 Self {
694 player,
695 hand,
696 world,
697 inv: InventoryAccess::new(inventory, hand),
698 }
699 }
700}
701
702pub struct InventoryTickContext<'a> {
704 pub world: &'a Arc<World>,
706 pub owner: &'a dyn Entity,
708 pub slot: Option<EquipmentSlot>,
710 item: ItemRef,
711 source: InventoryTickSource<'a>,
712}
713
714enum InventoryTickSource<'a> {
715 PlayerInventory {
716 inventory: &'a Shared<PlayerInventory>,
717 index: usize,
718 },
719 Equipment {
720 owner: &'a dyn LivingEntity,
721 slot: EquipmentSlot,
722 },
723}
724
725impl<'a> InventoryTickContext<'a> {
726 pub(crate) fn tick_player_inventory(world: &'a Arc<World>, player: &'a Player) {
727 for index in 0..PlayerInventory::INVENTORY_SIZE {
728 let (item, selected) = {
729 let inventory = player.inventory.lock();
730 let stack = &inventory.get_items()[index];
731 if stack.is_empty() {
732 continue;
733 }
734 (
735 stack.item(),
736 index == usize::from(inventory.get_selected_slot()),
737 )
738 };
739
740 let mut context = Self {
741 world,
742 owner: player,
743 slot: selected.then_some(EquipmentSlot::MainHand),
744 item,
745 source: InventoryTickSource::PlayerInventory {
746 inventory: &player.inventory,
747 index,
748 },
749 };
750 ITEM_BEHAVIORS
751 .get_behavior(item)
752 .inventory_tick(&mut context);
753 }
754 }
755
756 pub(crate) fn tick_equipment(
757 world: &'a Arc<World>,
758 owner: &'a dyn LivingEntity,
759 slots: impl IntoIterator<Item = EquipmentSlot>,
760 ) {
761 for slot in slots {
762 let mut item = None;
763 owner.with_equipment_slot(slot, &mut |stack| {
764 if !stack.is_empty() {
765 item = Some(stack.item());
766 }
767 });
768 let Some(item) = item else {
769 continue;
770 };
771
772 let mut context = Self {
773 world,
774 owner,
775 slot: Some(slot),
776 item,
777 source: InventoryTickSource::Equipment { owner, slot },
778 };
779 ITEM_BEHAVIORS
780 .get_behavior(item)
781 .inventory_tick(&mut context);
782 }
783 }
784
785 pub fn with_item<R>(&self, f: impl FnOnce(&mut ItemStack) -> R) -> Option<R> {
787 match self.source {
788 InventoryTickSource::PlayerInventory { inventory, index } => inventory
789 .lock()
790 .with_item_mut(index, |stack| stack.is(self.item).then(|| f(stack))),
791 InventoryTickSource::Equipment { owner, slot } => {
792 let mut f = Some(f);
793 let mut result = None;
794 owner.with_equipment_slot_mut(slot, &mut |stack| {
795 if stack.is(self.item)
796 && let Some(f) = f.take()
797 {
798 result = Some(f(stack));
799 }
800 });
801 result
802 }
803 }
804 }
805}
806
807#[cfg(test)]
808mod tests {
809 use std::sync::Arc;
810
811 use steel_registry::data_components::vanilla_components::BLOCK_STATE;
812 use steel_registry::init_vanilla_registry;
813 use steel_registry::vanilla_items;
814 use steel_utils::locks::SyncMutex;
815
816 use super::*;
817 use crate::behavior::init_behaviors;
818 use crate::inventory::container::Container;
819 use crate::player::player_inventory::PlayerInventory;
820 use crate::test_support::test_world;
821
822 #[test]
823 fn item_use_side_effects_apply_to_all_success_variants() {
824 assert!(InteractionResult::Success.should_apply_item_use_side_effects());
825 assert!(InteractionResult::SuccessServer.should_apply_item_use_side_effects());
826 assert!(InteractionResult::Consume.should_apply_item_use_side_effects());
827 assert!(InteractionResult::SuccessWithoutItem.consumes_action());
828 assert!(!InteractionResult::SuccessWithoutItem.should_apply_item_use_side_effects());
829 assert!(InteractionResult::SuccessWithoutItem.should_swing());
830 assert!(!InteractionResult::Fail.should_apply_item_use_side_effects());
831 assert!(!InteractionResult::Pass.should_apply_item_use_side_effects());
832 assert!(!InteractionResult::TryEmptyHandInteraction.should_apply_item_use_side_effects());
833 }
834
835 #[test]
836 fn player_hand_source_reads_current_components_and_mutates_the_hand() {
837 init_vanilla_registry();
838
839 let inventory = Arc::new(SyncMutex::new(PlayerInventory::new()));
840 inventory
841 .lock()
842 .set_item(0, ItemStack::with_count(&vanilla_items::LIGHT, 2));
843 let access = InventoryAccess::new(Arc::clone(&inventory), InteractionHand::MainHand);
844 let mut source = PlacementSource {
845 player: None,
846 hand: InteractionHand::MainHand,
847 item: PlacementItemSource::PlayerHand(access),
848 orientation: PlacementOrientation::Player {
849 rotation: 0.0,
850 pitch: 0.0,
851 },
852 is_secondary_use_active: false,
853 };
854
855 assert!(source.with_item(|item| item.get(BLOCK_STATE).is_some()));
856 source.with_item_mut(ItemStack::shrink_one);
857 assert_eq!(
858 inventory
859 .lock()
860 .get_item_in_hand(InteractionHand::MainHand)
861 .count(),
862 1
863 );
864 }
865
866 #[test]
867 fn replacement_dispatch_does_not_hold_the_inventory_lock() {
868 init_vanilla_registry();
869 init_behaviors();
870
871 let inventory = Arc::new(SyncMutex::new(PlayerInventory::new()));
872 inventory
873 .lock()
874 .set_item(0, ItemStack::new(&vanilla_items::STONE));
875 let source = PlacementSource {
876 player: None,
877 hand: InteractionHand::MainHand,
878 item: PlacementItemSource::PlayerHand(InventoryAccess::new(
879 inventory,
880 InteractionHand::MainHand,
881 )),
882 orientation: PlacementOrientation::Player {
883 rotation: 0.0,
884 pitch: 0.0,
885 },
886 is_secondary_use_active: false,
887 };
888 let hit_result = BlockHitResult {
889 location: DVec3::ZERO,
890 direction: Direction::Up,
891 block_pos: BlockPos::new(0, 80, 0),
892 miss: false,
893 inside: false,
894 world_border_hit: false,
895 };
896
897 let context = BlockPlaceContext::new(test_world(), source, &hit_result);
900 assert!(context.replaces_clicked_block());
901 }
902
903 #[test]
904 fn direct_source_mutates_the_callers_exact_stack() {
905 init_vanilla_registry();
906
907 let mut stack = ItemStack::with_count(&vanilla_items::LIGHT, 2);
908 {
909 let mut source = PlacementSource::direct(
910 None,
911 InteractionHand::MainHand,
912 &mut stack,
913 PlacementOrientation::Directional {
914 direction: Direction::North,
915 },
916 false,
917 );
918 assert!(source.with_item(|item| item.get(BLOCK_STATE).is_some()));
919 source.with_item_mut(ItemStack::shrink_one);
920 }
921 assert_eq!(stack.count(), 1);
922 }
923
924 #[test]
925 fn at_changes_geometry_and_retains_the_direct_source() {
926 init_vanilla_registry();
927 init_behaviors();
928
929 let mut stack = ItemStack::new(&vanilla_items::STONE);
930 let hit_result = BlockHitResult {
931 location: DVec3::ZERO,
932 direction: Direction::Up,
933 block_pos: BlockPos::new(0, 80, 0),
934 miss: false,
935 inside: true,
936 world_border_hit: false,
937 };
938 let source = PlacementSource::direct(
939 None,
940 InteractionHand::MainHand,
941 &mut stack,
942 PlacementOrientation::Player {
943 rotation: 0.0,
944 pitch: 0.0,
945 },
946 false,
947 );
948 let context = BlockPlaceContext::new(test_world(), source, &hit_result);
949 let shifted_pos = BlockPos::new(4, 90, 7);
950 let mut shifted = context.at(shifted_pos, Direction::East);
951
952 assert_eq!(shifted.hit_pos(), shifted_pos);
953 assert_eq!(shifted.place_pos(), shifted_pos);
954 assert_eq!(shifted.clicked_face(), Direction::East);
955 assert_eq!(shifted.click_location(), DVec3::new(5.0, 90.5, 7.5));
956 assert!(!shifted.is_inside());
957 assert!(shifted.with_item(|item| item.is(&vanilla_items::STONE)));
958 shifted.with_item_mut(ItemStack::shrink_one);
959 drop(shifted);
960 assert!(stack.is_empty());
961 }
962
963 #[test]
964 fn directional_context_uses_vanilla_direction_order() {
965 init_vanilla_registry();
966 init_behaviors();
967
968 let mut stack = ItemStack::new(&vanilla_items::STONE);
969 let context = BlockPlaceContext::directional(
970 test_world(),
971 BlockPos::new(2, 80, 3),
972 Direction::West,
973 &mut stack,
974 Direction::Up,
975 );
976
977 assert!(context.player().is_none());
978 assert_eq!(context.horizontal_direction(), Direction::West);
979 assert_eq!(
980 context.get_nearest_looking_directions(),
981 [
982 Direction::Down,
983 Direction::West,
984 Direction::South,
985 Direction::Up,
986 Direction::North,
987 Direction::East,
988 ]
989 );
990 }
991
992 #[test]
993 fn singular_look_direction_is_not_reordered_around_clicked_face() {
994 init_vanilla_registry();
995 init_behaviors();
996
997 let mut stack = ItemStack::new(&vanilla_items::PISTON);
998 let source = PlacementSource::direct(
999 None,
1000 InteractionHand::MainHand,
1001 &mut stack,
1002 PlacementOrientation::Player {
1003 rotation: 0.0,
1004 pitch: 80.0,
1005 },
1006 false,
1007 );
1008 let hit_result = BlockHitResult {
1009 location: DVec3::ZERO,
1010 direction: Direction::East,
1011 block_pos: BlockPos::new(10, 80, 10),
1012 miss: false,
1013 inside: false,
1014 world_border_hit: false,
1015 };
1016 let mut context = BlockPlaceContext::new(test_world(), source, &hit_result);
1017 context.replaces_clicked_block = false;
1018
1019 assert_eq!(context.get_nearest_looking_direction(), Direction::Down);
1020 assert_eq!(context.get_nearest_looking_directions()[0], Direction::West);
1021 }
1022}