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