1use std::{f32::consts::TAU, mem, sync::Arc};
2
3use glam::DVec3;
4use steel_protocol::packets::game::{
5 CContainerClose, COpenScreen, CSetPlayerInventory, ClickType, SContainerButtonClick,
6 SContainerClick, SContainerClose, SContainerSlotStateChanged, SRenameItem, SSetCarriedItem,
7 SSetCreativeModeSlot,
8};
9use steel_registry::item_stack::ItemStack;
10use steel_utils::{
11 Downcast as _,
12 locks::Shared,
13 types::{GameType, InteractionHand},
14};
15use text_components::TextComponent;
16
17use crate::{
18 entity::{Entity, LivingEntity as _, RemovalReason, entities::ItemEntity},
19 inventory::{
20 click::Click,
21 container::{Container, CraftingContainer, clear_or_count_matching_stack},
22 lock::{ContainerId, ContainerLockGuard},
23 menu::{
24 Menu,
25 kinds::{INVENTORY_MENU_CONTAINER_ID, InventoryKind},
26 },
27 slots::CraftingHandler,
28 },
29 player::{Player, connection::NetworkConnection as _},
30};
31
32use super::{
33 DeferredMenuAction, MenuItemDisposition, MenuOpenContext, MenuRemovalStatus, OpenMenuDispatch,
34 OpenMenuUnavailable, PendingMenuOpen, PlayerInventory, PreparedMenu, TerminalMenuRemoval,
35};
36
37impl Player {
38 fn take_open_menu_for_callback(
39 &self,
40 expected_container_id: Option<i32>,
41 ) -> Result<Menu, OpenMenuUnavailable> {
42 let mut open_menu = self.open_menu.lock();
43 if open_menu.dispatch.is_some() {
44 return Err(OpenMenuUnavailable::Unavailable);
45 }
46
47 let Some(menu) = open_menu.menu.as_ref() else {
48 return Err(OpenMenuUnavailable::Closed);
49 };
50 if expected_container_id.is_some_and(|expected| i32::from(menu.container_id()) != expected)
51 {
52 return Err(OpenMenuUnavailable::Unavailable);
53 }
54
55 let container_id = menu.container_id();
56 let overrides_player_slots = menu.overrides_player_slots();
57 let Some(menu) = open_menu.menu.take() else {
58 return Err(OpenMenuUnavailable::Unavailable);
59 };
60 open_menu.dispatch = Some(OpenMenuDispatch {
61 container_id,
62 overrides_player_slots,
63 actions: Vec::new(),
64 });
65 Ok(menu)
66 }
67
68 fn finish_open_menu_callback(&self, menu: Menu) {
69 let actions = {
70 let mut open_menu = self.open_menu.lock();
71 let Some(dispatch) = open_menu.dispatch.take() else {
72 open_menu.menu = Some(menu);
73 return;
74 };
75 if let Some(terminal_removal) = open_menu.terminal_removal.as_mut() {
76 Self::queue_deferred_menus(terminal_removal, dispatch.actions);
77 drop(open_menu);
78 self.finish_terminal_menu_main_cleanup(Some(menu));
79 return;
80 }
81 open_menu.menu = Some(menu);
82 open_menu.active_open_operations += 1;
83 dispatch.actions
84 };
85
86 self.run_deferred_menu_actions(actions);
87 }
88
89 fn finish_open_menu_removal(&self) {
90 let actions = {
91 let mut open_menu = self.open_menu.lock();
92 let Some(dispatch) = open_menu.dispatch.take() else {
93 return;
94 };
95 if let Some(terminal_removal) = open_menu.terminal_removal.as_mut() {
96 Self::queue_deferred_menus(terminal_removal, dispatch.actions);
97 drop(open_menu);
98 self.finish_terminal_menu_main_cleanup(None);
99 return;
100 }
101 open_menu.active_open_operations += 1;
102 dispatch.actions
103 };
104
105 self.run_deferred_menu_actions(actions);
106 }
107
108 fn run_deferred_menu_actions(&self, actions: Vec<DeferredMenuAction>) {
109 for action in actions {
110 match action {
111 DeferredMenuAction::Close { send_packet } => {
112 if send_packet {
113 self.close_container();
114 } else {
115 self.do_close_container();
116 }
117 }
118 DeferredMenuAction::Open(prepared) => {
119 self.execute_menu_open(*prepared);
120 }
121 DeferredMenuAction::Install(prepared) => {
122 let PreparedMenu { title, menu } = *prepared;
123 self.open_prepared_menu(title, menu);
124 }
125 }
126 }
127
128 self.finish_menu_open_operation();
129 }
130
131 fn queue_deferred_menus(
132 terminal_removal: &mut TerminalMenuRemoval,
133 actions: Vec<DeferredMenuAction>,
134 ) {
135 terminal_removal
136 .pending_menus
137 .extend(actions.into_iter().filter_map(|action| match action {
138 DeferredMenuAction::Install(prepared) => Some(prepared.menu),
139 DeferredMenuAction::Close { .. } | DeferredMenuAction::Open(_) => None,
140 }));
141 }
142
143 fn begin_menu_open_operation(&self) -> bool {
144 let mut open_menu = self.open_menu.lock();
145 if open_menu.terminal_removal.is_some() {
146 return false;
147 }
148 open_menu.active_open_operations += 1;
149 true
150 }
151
152 fn finish_menu_open_operation(&self) {
153 {
154 let mut open_menu = self.open_menu.lock();
155 debug_assert!(open_menu.active_open_operations > 0);
156 open_menu.active_open_operations -= 1;
157 }
158 self.try_finish_terminal_menu_removal();
159 }
160
161 pub(in crate::player) fn touch_nearby_items(&self) {
167 if self.game_mode() == GameType::Spectator {
168 return;
169 }
170
171 let pickup_area = self.bounding_box().inflate_xyz(1.0, 0.5, 1.0);
172 let world = self.get_world();
173 let entities = world.get_entities_in_aabb(&pickup_area);
174
175 let Some(player_arc) = world.players.get_by_entity_id(self.id()) else {
176 return;
177 };
178
179 for entity in entities {
180 if entity.id() == self.id() || entity.is_removed() {
181 continue;
182 }
183
184 entity.player_touch(&player_arc);
185 }
186 }
187
188 pub fn handle_container_button_click(&self, packet: SContainerButtonClick) {
190 log::debug!(
191 "Player {} clicked button {} in container {}",
192 self.gameprofile.name,
193 packet.button_id,
194 packet.container_id
195 );
196 }
203
204 pub fn handle_container_click(&self, packet: SContainerClick) {
206 match self.take_open_menu_for_callback(Some(packet.container_id)) {
207 Ok(mut menu) => {
208 self.process_container_click(&mut menu, packet);
209 self.finish_open_menu_callback(menu);
210 }
211 Err(OpenMenuUnavailable::Closed) => {
212 let mut menu = self.inventory_menu.lock();
213 if i32::from(menu.behavior().container_id()) == packet.container_id {
214 self.process_container_click(&mut menu, packet);
215 }
216 }
217 Err(OpenMenuUnavailable::Unavailable) => {}
218 }
219 }
220
221 fn process_container_click(&self, menu: &mut Menu, packet: SContainerClick) {
226 if self.game_mode() == GameType::Spectator || self.get_health() <= 0.0 {
227 menu.behavior_mut()
228 .send_all_data_to_remote(&self.connection);
229 return;
230 }
231
232 if !menu.still_valid(self) {
233 log::debug!(
234 "Player {} interacted with invalid menu",
235 self.gameprofile.name
236 );
237 return;
238 }
239
240 let slot_count = menu.behavior().slot_count();
244 let packet_slot_is_valid = packet.slot_num < 0
245 || usize::try_from(packet.slot_num).is_ok_and(|slot| slot < slot_count);
246 if !packet_slot_is_valid {
247 log::debug!(
248 "Player {} clicked invalid slot index: {}, available slots: {}",
249 self.gameprofile.name,
250 packet.slot_num,
251 slot_count
252 );
253 return;
254 }
255
256 let click = Click::parse(
260 packet.slot_num,
261 packet.button_num,
262 packet.click_type,
263 slot_count,
264 );
265 if click.is_none() {
266 log::debug!(
267 "Player {} sent malformed container click (slot {}, button {}, {:?})",
268 self.gameprofile.name,
269 packet.slot_num,
270 packet.button_num,
271 packet.click_type
272 );
273 let quick_craft_header = packet.button_num & 3;
277 let quick_craft_type = (packet.button_num >> 2) & 3;
278 if packet.click_type != ClickType::QuickCraft
279 || quick_craft_header == 3
280 || (quick_craft_header == 0 && quick_craft_type == 3)
281 {
282 menu.behavior_mut().reset_quick_craft();
283 }
284 }
285
286 let full_resync_needed = packet.state_id as u32 != menu.behavior().state_id();
287
288 menu.behavior_mut().suppress_remote_updates();
289
290 if let Some(click) = click {
291 menu.clicked(click, self);
292 }
293
294 for (slot, hash) in packet.changed_slots {
295 let slot = slot as usize;
296 if menu
302 .behavior()
303 .slots()
304 .get(slot)
305 .is_some_and(|slot| slot.is_fake())
306 {
307 menu.behavior_mut().mark_remote_slot_unknown(slot);
308 continue;
309 }
310 menu.behavior_mut().set_remote_slot(slot, hash);
311 }
312
313 menu.behavior_mut().set_remote_carried(packet.carried_item);
314 menu.behavior_mut().resume_remote_updates();
315
316 if full_resync_needed {
317 menu.behavior_mut()
318 .send_all_data_to_remote(&self.connection);
319 } else {
320 menu.behavior_mut().broadcast_changes(&self.connection);
321 }
322 }
323
324 pub fn handle_container_close(&self, packet: SContainerClose) {
328 log::debug!(
329 "Player {} closed container {}",
330 self.gameprofile.name,
331 packet.container_id
332 );
333
334 let open_menu = self.open_menu.lock();
335 let closes_open_menu = open_menu
336 .menu
337 .as_ref()
338 .is_some_and(|menu| i32::from(menu.container_id()) == packet.container_id)
339 || open_menu
340 .dispatch
341 .as_ref()
342 .is_some_and(|dispatch| i32::from(dispatch.container_id) == packet.container_id);
343 drop(open_menu);
344
345 if closes_open_menu {
346 self.do_close_container();
347 return;
348 }
349
350 if packet.container_id == i32::from(INVENTORY_MENU_CONTAINER_ID) {
351 let mut menu = self.inventory_menu.lock();
352 menu.removed(self);
353 }
354 }
355
356 pub fn handle_rename_item(self: &Arc<Self>, packet: SRenameItem) {
358 match self.take_open_menu_for_callback(None) {
359 Ok(mut menu) => {
360 if menu.still_valid(self) {
361 menu.set_item_name(packet.name, self);
362 }
363 self.finish_open_menu_callback(menu);
364 }
365 Err(OpenMenuUnavailable::Closed) => {
366 log::debug!("rename item without an open menu");
367 }
368 Err(OpenMenuUnavailable::Unavailable) => {}
369 }
370 }
371
372 pub fn handle_container_slot_state_changed(&self, packet: SContainerSlotStateChanged) {
374 log::debug!(
375 "Player {} changed slot {} state to {} in container {}",
376 self.gameprofile.name,
377 packet.slot_id,
378 packet.new_state,
379 packet.container_id
380 );
381 }
384
385 pub fn handle_set_creative_mode_slot(&self, packet: SSetCreativeModeSlot) {
387 if self.game_mode() != GameType::Creative {
388 return;
389 }
390
391 let drop = packet.slot_num < 0;
392 let item_stack = packet.item_stack;
393
394 let valid_slot = packet.slot_num >= 1 && packet.slot_num <= 45;
395 let valid_data = item_stack.is_empty() || item_stack.count <= item_stack.max_stack_size();
396
397 if valid_slot && valid_data {
398 let mut menu = self.inventory_menu.lock();
399 let slot_index = packet.slot_num as usize;
400
401 {
402 let mut guard = menu.behavior().lock_all_containers();
403 if let Some(slot) = menu.behavior().slots().get(slot_index) {
404 let previous = slot.get_item(&guard).clone();
405 slot.set_by_player(&mut guard, item_stack.clone(), &previous);
406 }
407 }
408 if (1..=4).contains(&slot_index) {
409 menu.update_crafting_result();
410 }
411 menu.behavior_mut()
412 .set_remote_slot_known(slot_index, &item_stack);
413 menu.behavior_mut().broadcast_changes(&self.connection);
414 } else if drop && valid_data {
415 if !item_stack.is_empty() {
418 log::debug!(
420 "Player {} would drop {:?} in creative mode",
421 self.gameprofile.name,
422 item_stack
423 );
424 }
425 }
426 }
427
428 pub fn handle_set_carried_item(&self, packet: SSetCarriedItem) {
430 if self
431 .inventory
432 .lock()
433 .try_set_selected_slot_from_packet(packet.slot)
434 .is_err()
435 {
436 log::warn!(
437 "{} tried to set an invalid carried item",
438 self.gameprofile.name
439 );
440 }
441 }
442
443 pub fn send_inventory_to_remote(&self) {
446 self.inventory_menu
447 .lock()
448 .behavior_mut()
449 .send_all_data_to_remote(&self.connection);
450 }
451
452 fn next_container_counter(&self) -> u8 {
456 self.container_counter.lock().next()
457 }
458
459 pub fn open_menu(
474 &self,
475 title: impl Into<TextComponent>,
476 create: impl for<'a> FnOnce(MenuOpenContext<'a>) -> Menu + Send + 'static,
477 ) {
478 if !self.begin_menu_open_operation() {
479 return;
480 }
481 self.open_menu_inner(PendingMenuOpen {
482 title: title.into(),
483 create: Box::new(create),
484 });
485 self.finish_menu_open_operation();
486 }
487
488 fn open_menu_inner(&self, pending: PendingMenuOpen) {
489 self.do_close_container();
490
491 let mut open_menu = self.open_menu.lock();
492 if open_menu.terminal_removal.is_some() {
493 return;
494 }
495 if let Some(dispatch) = open_menu.dispatch.as_mut() {
496 dispatch
497 .actions
498 .push(DeferredMenuAction::Open(Box::new(pending)));
499 return;
500 }
501 drop(open_menu);
502
503 self.execute_menu_open(pending);
504 }
505
506 fn execute_menu_open(&self, pending: PendingMenuOpen) {
507 {
508 let mut open_menu = self.open_menu.lock();
509 if open_menu.terminal_removal.is_some() {
510 return;
511 }
512 if let Some(dispatch) = open_menu.dispatch.as_mut() {
513 dispatch
514 .actions
515 .push(DeferredMenuAction::Open(Box::new(pending)));
516 return;
517 }
518 }
519
520 let PendingMenuOpen { title, create } = pending;
521 let container_id = self.next_container_counter();
522 let world = self.get_world();
523 let menu = create(MenuOpenContext {
524 container_id,
525 player: self,
526 world: &world,
527 });
528 assert_eq!(
529 menu.container_id(),
530 container_id,
531 "open_menu factory returned container id {}, but {} was allocated",
532 menu.container_id(),
533 container_id,
534 );
535 self.open_prepared_menu(title, menu);
536 }
537
538 fn open_prepared_menu(&self, title: TextComponent, mut menu: Menu) {
539 loop {
540 {
541 let mut open_menu = self.open_menu.lock();
542 if let Some(terminal_removal) = open_menu.terminal_removal.as_mut() {
543 terminal_removal.pending_menus.push(menu);
544 return;
545 }
546 }
547
548 self.do_close_container();
551
552 let mut open_menu = self.open_menu.lock();
553 if let Some(terminal_removal) = open_menu.terminal_removal.as_mut() {
554 terminal_removal.pending_menus.push(menu);
555 return;
556 }
557 if let Some(dispatch) = open_menu.dispatch.as_mut() {
558 dispatch
559 .actions
560 .push(DeferredMenuAction::Install(Box::new(PreparedMenu {
561 title,
562 menu,
563 })));
564 return;
565 }
566 if open_menu.menu.is_some() {
567 continue;
568 }
569 open_menu.dispatch = Some(OpenMenuDispatch {
570 container_id: menu.container_id(),
571 overrides_player_slots: menu.overrides_player_slots(),
572 actions: Vec::new(),
573 });
574 break;
575 }
576
577 self.send_packet(COpenScreen {
578 container_id: i32::from(menu.container_id()),
579 menu_type: menu
580 .menu_type()
581 .expect("a menu opened via open_menu must declare a menu type"),
582 title,
583 });
584
585 menu.on_open(self);
588
589 menu.behavior_mut()
590 .send_all_data_to_remote(&self.connection);
591
592 self.finish_open_menu_callback(menu);
593 }
594
595 pub fn crafting_container(&self) -> Shared<CraftingContainer> {
598 let menu = self.inventory_menu.lock();
599 let Some(kind) = menu.kind().downcast_ref::<InventoryKind>() else {
600 unreachable!("a player's inventory_menu is always the Inventory kind");
601 };
602 kind.crafting_container()
603 }
604
605 pub(crate) fn inventory_crafting_handler(&self) -> CraftingHandler {
608 let menu = self.inventory_menu.lock();
609 let Some(kind) = menu.kind().downcast_ref::<InventoryKind>() else {
610 unreachable!("a player's inventory_menu is always the Inventory kind");
611 };
612 kind.crafting_handler()
613 }
614
615 pub fn close_container(&self) {
620 self.close_open_menu(true);
621 }
622
623 pub fn do_close_container(&self) {
628 self.close_open_menu(false);
629 }
630
631 pub fn remove_all_menus(&self) -> MenuRemovalStatus {
638 self.remove_all_menus_with_disposition(self.default_menu_item_disposition())
639 }
640
641 pub(in crate::player) fn remove_all_menus_with_disposition(
642 &self,
643 disposition: MenuItemDisposition,
644 ) -> MenuRemovalStatus {
645 let menu = {
646 let mut open_menu = self.open_menu.lock();
647 if let Some(terminal_removal) = open_menu.terminal_removal.as_mut() {
648 terminal_removal.disposition = terminal_removal.disposition.combine(disposition);
649 return MenuRemovalStatus::Pending;
650 }
651
652 open_menu.terminal_removal = Some(TerminalMenuRemoval {
653 disposition,
654 main_cleanup_complete: false,
655 pending_cleanup_in_progress: false,
656 pending_menus: Vec::new(),
657 });
658 if open_menu.dispatch.is_some() {
659 return MenuRemovalStatus::Pending;
660 }
661
662 open_menu.menu.take()
663 };
664
665 self.finish_terminal_menu_main_cleanup(menu);
666 if self.open_menu.lock().terminal_removal.is_none() {
667 MenuRemovalStatus::Complete
668 } else {
669 MenuRemovalStatus::Pending
670 }
671 }
672
673 fn finish_terminal_menu_main_cleanup(&self, mut menu: Option<Menu>) {
674 self.inventory_menu.lock().removed(self);
675 if let Some(menu) = menu.as_mut() {
676 self.remove_open_menu(menu);
677 }
678
679 {
680 let mut open_menu = self.open_menu.lock();
681 let Some(terminal_removal) = open_menu.terminal_removal.as_mut() else {
682 return;
683 };
684 terminal_removal.main_cleanup_complete = true;
685 }
686 self.try_finish_terminal_menu_removal();
687 }
688
689 fn try_finish_terminal_menu_removal(&self) {
690 loop {
691 let pending_menus = {
692 let mut open_menu = self.open_menu.lock();
693 if open_menu.active_open_operations != 0 {
694 return;
695 }
696 let Some(terminal_removal) = open_menu.terminal_removal.as_mut() else {
697 return;
698 };
699 if !terminal_removal.main_cleanup_complete {
700 return;
701 }
702 if terminal_removal.pending_cleanup_in_progress {
703 return;
704 }
705 if terminal_removal.pending_menus.is_empty() {
706 open_menu.terminal_removal = None;
707 debug_assert!(open_menu.menu.is_none());
708 return;
709 }
710 terminal_removal.pending_cleanup_in_progress = true;
711 mem::take(&mut terminal_removal.pending_menus)
712 };
713
714 for mut pending_menu in pending_menus {
715 pending_menu.removed(self);
716 }
717
718 let mut open_menu = self.open_menu.lock();
719 let Some(terminal_removal) = open_menu.terminal_removal.as_mut() else {
720 return;
721 };
722 terminal_removal.pending_cleanup_in_progress = false;
723 }
724 }
725
726 #[cfg(test)]
727 pub(in crate::player) fn retry_terminal_menu_removal_for_test(&self) {
728 self.try_finish_terminal_menu_removal();
729 }
730
731 fn close_open_menu(&self, send_packet: bool) {
732 let menu = {
733 let mut open_menu = self.open_menu.lock();
734 if open_menu.terminal_removal.is_some() {
735 return;
736 }
737 if let Some(dispatch) = open_menu.dispatch.as_mut() {
738 dispatch
739 .actions
740 .push(DeferredMenuAction::Close { send_packet });
741 return;
742 }
743 let Some(menu) = open_menu.menu.take() else {
744 return;
745 };
746 open_menu.dispatch = Some(OpenMenuDispatch {
747 container_id: menu.container_id(),
748 overrides_player_slots: menu.overrides_player_slots(),
749 actions: Vec::new(),
750 });
751 menu
752 };
753
754 let mut menu = menu;
755 if send_packet {
756 self.send_packet(CContainerClose {
757 container_id: i32::from(menu.container_id()),
758 });
759 }
760 self.remove_open_menu(&mut menu);
761 self.finish_open_menu_removal();
762 }
763
764 fn remove_open_menu(&self, menu: &mut Menu) {
765 let overrides_player_slots = menu.overrides_player_slots();
766 menu.removed(self);
767 if overrides_player_slots {
768 self.request_inventory_resync(0..PlayerInventory::INVENTORY_SIZE);
769 } else {
770 self.inventory_menu
771 .lock()
772 .behavior_mut()
773 .transfer_state(menu.behavior());
774 }
775 }
776
777 #[must_use]
779 pub fn has_container_open(&self) -> bool {
780 let open_menu = self.open_menu.lock();
781 open_menu.menu.is_some() || open_menu.dispatch.is_some()
782 }
783
784 pub fn tick_open_menu(&self) {
789 let Ok(mut menu) = self.take_open_menu_for_callback(None) else {
790 return;
791 };
792 if !menu.still_valid(self) {
793 self.close_container();
794 self.finish_open_menu_callback(menu);
795 return;
796 }
797 menu.on_tick(self);
798 self.finish_open_menu_callback(menu);
799 }
800
801 pub fn broadcast_inventory_changes(&self) {
804 let mut open_menu = self.open_menu.lock();
805 if let Some(menu) = open_menu.menu.as_mut() {
806 menu.behavior_mut().broadcast_changes(&self.connection);
807 return;
808 }
809 if open_menu.dispatch.is_none() {
810 drop(open_menu);
811 self.inventory_menu
812 .lock()
813 .behavior_mut()
814 .broadcast_changes(&self.connection);
815 }
816 }
817
818 pub(crate) fn request_inventory_resync(&self, slots: impl IntoIterator<Item = usize>) {
820 self.inventory_sync.lock().request(slots);
821 }
822
823 pub(in crate::player) fn flush_inventory_resync(&self) {
825 let overrides_player_slots = {
826 let open_menu = self.open_menu.lock();
827 open_menu
828 .menu
829 .as_ref()
830 .is_some_and(Menu::overrides_player_slots)
831 || open_menu
832 .dispatch
833 .as_ref()
834 .is_some_and(|dispatch| dispatch.overrides_player_slots)
835 };
836 let slots = self
837 .inventory_sync
838 .lock()
839 .take_ready(overrides_player_slots);
840 if slots.is_empty() {
841 return;
842 }
843
844 let packets = {
845 let inventory = self.inventory.lock();
846 slots
847 .into_iter()
848 .map(|slot| CSetPlayerInventory {
849 slot: slot as i32,
850 item_stack: inventory.get_item(slot).clone(),
851 })
852 .collect::<Vec<_>>()
853 };
854 for packet in packets {
855 self.send_packet(packet);
856 }
857 }
858
859 pub(crate) fn clear_or_count_matching_items(
861 &self,
862 predicate: &dyn Fn(&ItemStack) -> bool,
863 amount_to_remove: i32,
864 ) -> i32 {
865 let counting_only = amount_to_remove == 0;
866 let mut count = self.inventory.lock().clear_or_count_matching_items(
867 predicate,
868 amount_to_remove,
869 counting_only,
870 );
871
872 count += self.inventory_menu.lock().clear_or_count_crafting_items(
873 predicate,
874 amount_to_remove - count,
875 counting_only,
876 );
877
878 let has_open_menu = {
879 let mut open_menu = self.open_menu.lock();
880 if let Some(menu) = open_menu.menu.as_mut() {
881 let behavior = menu.behavior_mut();
882 count += clear_or_count_matching_stack(
883 behavior.carried_mut(),
884 predicate,
885 amount_to_remove - count,
886 counting_only,
887 );
888 if behavior.carried().is_empty() {
889 *behavior.carried_mut() = ItemStack::empty();
890 }
891 true
892 } else {
893 open_menu.dispatch.is_some()
894 }
895 };
896 if !has_open_menu {
897 let mut inventory_menu = self.inventory_menu.lock();
898 let behavior = inventory_menu.behavior_mut();
899 count += clear_or_count_matching_stack(
900 behavior.carried_mut(),
901 predicate,
902 amount_to_remove - count,
903 counting_only,
904 );
905 if behavior.carried().is_empty() {
906 *behavior.carried_mut() = ItemStack::empty();
907 }
908 }
909
910 self.inventory_menu.lock().update_crafting_result();
911 self.broadcast_inventory_changes();
912 count
913 }
914
915 pub fn drop_from_selected(&self, all: bool) {
921 if !self.can_drop_items() {
922 return;
923 }
924
925 let removed = {
926 let mut inventory = self.inventory.lock();
927 let selected_count = inventory.get_selected_item().count();
928 if selected_count == 0 {
929 return;
930 }
931 inventory.split_item_in_hand(
932 InteractionHand::MainHand,
933 if all { selected_count } else { 1 },
934 )
935 };
936
937 let _ = self.drop_item(removed, false, true);
938 }
939
940 #[must_use]
948 pub fn drop_item(
949 &self,
950 item: ItemStack,
951 throw_randomly: bool,
952 thrown_from_hand: bool,
953 ) -> Option<Arc<ItemEntity>> {
954 if item.is_empty() {
955 return None;
956 }
957
958 let pos = self.position();
959 let (yaw, pitch) = self.rotation();
960
961 let spawn_y = self.get_eye_y() - 0.3;
962
963 let velocity = if throw_randomly {
964 let power = rand::random::<f32>() * 0.5;
965 let angle = rand::random::<f32>() * TAU;
966 DVec3::new(
967 f64::from(-angle.sin() * power),
968 0.2,
969 f64::from(angle.cos() * power),
970 )
971 } else {
972 let pitch_rad = pitch.to_radians();
973 let yaw_rad = yaw.to_radians();
974
975 let sin_pitch = pitch_rad.sin();
976 let cos_pitch = pitch_rad.cos();
977 let sin_yaw = yaw_rad.sin();
978 let cos_yaw = yaw_rad.cos();
979
980 let angle_offset = rand::random::<f32>() * TAU;
981 let power_offset = 0.02 * rand::random::<f32>();
982
983 DVec3::new(
984 f64::from(-sin_yaw * cos_pitch * 0.3)
985 + f64::from(angle_offset.cos() * power_offset),
986 f64::from(-sin_pitch * 0.3 + 0.1)
987 + f64::from((rand::random::<f32>() - rand::random::<f32>()) * 0.1),
988 f64::from(cos_yaw * cos_pitch * 0.3) + f64::from(angle_offset.sin() * power_offset),
989 )
990 };
991
992 let spawn_pos = DVec3::new(pos.x, spawn_y, pos.z);
993
994 let entity = self
995 .get_world()
996 .spawn_item_with_velocity(spawn_pos, item, velocity)?;
997 entity.set_pickup_delay(40);
998 if thrown_from_hand {
999 entity.set_thrower(self.gameprofile.id);
1000 }
1001 Some(entity)
1002 }
1003
1004 #[must_use]
1009 pub fn can_drop_items(&self) -> bool {
1010 !self.is_removed()
1011 }
1013
1014 #[must_use]
1022 pub fn returns_menu_items_to_inventory(&self) -> bool {
1023 if let Some(disposition) = self
1024 .open_menu
1025 .lock()
1026 .terminal_removal
1027 .as_ref()
1028 .map(|terminal_removal| terminal_removal.disposition)
1029 {
1030 return disposition == MenuItemDisposition::ReturnToInventory;
1031 }
1032
1033 self.default_menu_item_disposition() == MenuItemDisposition::ReturnToInventory
1034 }
1035
1036 fn default_menu_item_disposition(&self) -> MenuItemDisposition {
1037 let removed_outside_world_change =
1038 self.is_removed() && self.removal_reason() != Some(RemovalReason::ChangedWorld);
1039 if removed_outside_world_change || self.connection.closed() || self.get_health() <= 0.0 {
1040 MenuItemDisposition::Drop
1041 } else {
1042 MenuItemDisposition::ReturnToInventory
1043 }
1044 }
1045
1046 pub fn add_item_or_drop(&self, mut item: ItemStack) {
1050 if item.is_empty() {
1051 return;
1052 }
1053
1054 let added = self.inventory.lock().add(&mut item);
1055 if !added || !item.is_empty() {
1056 let _ = self.drop_item(item, false, false);
1057 }
1058 }
1059
1060 pub fn add_item_or_drop_with_guard(&self, guard: &mut ContainerLockGuard, mut item: ItemStack) {
1066 if item.is_empty() {
1067 return;
1068 }
1069
1070 let inv_id = ContainerId::from_arc(&self.inventory);
1071 let should_drop = if let Some(inv) = guard.get_mut(inv_id) {
1072 let added = inv.add(&mut item);
1073 !added || !item.is_empty()
1074 } else {
1075 true
1076 };
1077 if should_drop {
1078 let _ = guard.run_unlocked(|| self.drop_item(item, false, false));
1079 }
1080 }
1081}