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