Skip to main content

steel_core/player/player_inventory/
mod.rs

1//! Player inventory management.
2
3use std::sync::Arc;
4
5use text_components::TextComponent;
6
7use crate::{inventory::menu::Menu, player::Player, world::World};
8
9mod container;
10mod core;
11mod equipment;
12mod player_handlers;
13
14pub use container::InvalidHotbarSlot;
15pub(crate) use container::armor_equipment;
16pub use core::PlayerInventory;
17pub use equipment::EquipmentSwapResult;
18
19/// Inputs supplied when an external menu factory is safe to execute.
20pub struct MenuOpenContext<'a> {
21    /// Wire container id allocated for this menu.
22    pub container_id: u8,
23    /// Player opening the menu.
24    pub player: &'a Player,
25    /// Player's world at factory execution time.
26    pub world: &'a Arc<World>,
27}
28
29/// Whether the client already predicted a side effect such as the drop swing.
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum Prediction {
32    /// The client predicted it; only tracking players need the update.
33    Predicted,
34    /// Only the server knows; the acting player needs the update too.
35    ServerOnly,
36}
37
38/// Whether a terminal menu removal completed synchronously.
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40#[must_use]
41pub enum MenuRemovalStatus {
42    /// Both the base inventory menu and any external menu were removed.
43    Complete,
44    /// A callback or in-flight open operation owns menu state; removal will
45    /// finish when it unwinds.
46    Pending,
47}
48
49#[derive(Clone, Copy, PartialEq, Eq)]
50pub(super) enum MenuItemDisposition {
51    ReturnToInventory,
52    Drop,
53}
54
55impl MenuItemDisposition {
56    const fn combine(self, other: Self) -> Self {
57        if matches!(self, Self::Drop) || matches!(other, Self::Drop) {
58            Self::Drop
59        } else {
60            Self::ReturnToInventory
61        }
62    }
63}
64
65pub(super) struct OpenMenuState {
66    menu: Option<Menu>,
67    dispatch: Option<OpenMenuDispatch>,
68    terminal_removal: Option<TerminalMenuRemoval>,
69    active_open_operations: usize,
70}
71
72pub(super) struct PlayerInventorySyncState {
73    pending_slots: [bool; PlayerInventory::CONTAINER_SIZE],
74}
75
76impl PlayerInventorySyncState {
77    pub(super) const fn new() -> Self {
78        Self {
79            pending_slots: [false; PlayerInventory::CONTAINER_SIZE],
80        }
81    }
82
83    fn request(&mut self, slots: impl IntoIterator<Item = usize>) {
84        for slot in slots {
85            assert!(
86                slot < PlayerInventory::CONTAINER_SIZE,
87                "logical player inventory slot {slot} is out of bounds"
88            );
89            self.pending_slots[slot] = true;
90        }
91    }
92
93    fn take_ready(&mut self, overrides_player_slots: bool) -> Vec<usize> {
94        self.pending_slots
95            .iter_mut()
96            .enumerate()
97            .filter_map(|(slot, pending)| {
98                if !*pending || (overrides_player_slots && slot < PlayerInventory::INVENTORY_SIZE) {
99                    return None;
100                }
101                *pending = false;
102                Some(slot)
103            })
104            .collect()
105    }
106}
107
108struct OpenMenuDispatch {
109    container_id: u8,
110    overrides_player_slots: bool,
111    actions: Vec<DeferredMenuAction>,
112}
113
114struct TerminalMenuRemoval {
115    disposition: MenuItemDisposition,
116    main_cleanup_complete: bool,
117    pending_cleanup_in_progress: bool,
118    pending_menus: Vec<Menu>,
119}
120
121enum DeferredMenuAction {
122    Close { send_packet: bool },
123    Open(Box<PendingMenuOpen>),
124    Install(Box<PreparedMenu>),
125}
126
127type MenuFactory = Box<dyn for<'a> FnOnce(MenuOpenContext<'a>) -> Menu + Send + 'static>;
128
129struct PendingMenuOpen {
130    title: TextComponent,
131    create: MenuFactory,
132}
133
134struct PreparedMenu {
135    title: TextComponent,
136    menu: Menu,
137}
138
139enum OpenMenuUnavailable {
140    Closed,
141    Unavailable,
142}
143
144impl OpenMenuState {
145    pub(super) const fn new() -> Self {
146        Self {
147            menu: None,
148            dispatch: None,
149            terminal_removal: None,
150            active_open_operations: 0,
151        }
152    }
153}
154
155#[cfg(test)]
156mod tests;