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 a terminal menu removal completed synchronously.
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31#[must_use]
32pub enum MenuRemovalStatus {
33    /// Both the base inventory menu and any external menu were removed.
34    Complete,
35    /// A callback or in-flight open operation owns menu state; removal will
36    /// finish when it unwinds.
37    Pending,
38}
39
40#[derive(Clone, Copy, PartialEq, Eq)]
41pub(super) enum MenuItemDisposition {
42    ReturnToInventory,
43    Drop,
44}
45
46impl MenuItemDisposition {
47    const fn combine(self, other: Self) -> Self {
48        if matches!(self, Self::Drop) || matches!(other, Self::Drop) {
49            Self::Drop
50        } else {
51            Self::ReturnToInventory
52        }
53    }
54}
55
56pub(super) struct OpenMenuState {
57    menu: Option<Menu>,
58    dispatch: Option<OpenMenuDispatch>,
59    terminal_removal: Option<TerminalMenuRemoval>,
60    active_open_operations: usize,
61}
62
63pub(super) struct PlayerInventorySyncState {
64    pending_slots: [bool; PlayerInventory::CONTAINER_SIZE],
65}
66
67impl PlayerInventorySyncState {
68    pub(super) const fn new() -> Self {
69        Self {
70            pending_slots: [false; PlayerInventory::CONTAINER_SIZE],
71        }
72    }
73
74    fn request(&mut self, slots: impl IntoIterator<Item = usize>) {
75        for slot in slots {
76            assert!(
77                slot < PlayerInventory::CONTAINER_SIZE,
78                "logical player inventory slot {slot} is out of bounds"
79            );
80            self.pending_slots[slot] = true;
81        }
82    }
83
84    fn take_ready(&mut self, overrides_player_slots: bool) -> Vec<usize> {
85        self.pending_slots
86            .iter_mut()
87            .enumerate()
88            .filter_map(|(slot, pending)| {
89                if !*pending || (overrides_player_slots && slot < PlayerInventory::INVENTORY_SIZE) {
90                    return None;
91                }
92                *pending = false;
93                Some(slot)
94            })
95            .collect()
96    }
97}
98
99struct OpenMenuDispatch {
100    container_id: u8,
101    overrides_player_slots: bool,
102    actions: Vec<DeferredMenuAction>,
103}
104
105struct TerminalMenuRemoval {
106    disposition: MenuItemDisposition,
107    main_cleanup_complete: bool,
108    pending_cleanup_in_progress: bool,
109    pending_menus: Vec<Menu>,
110}
111
112enum DeferredMenuAction {
113    Close { send_packet: bool },
114    Open(Box<PendingMenuOpen>),
115    Install(Box<PreparedMenu>),
116}
117
118type MenuFactory = Box<dyn for<'a> FnOnce(MenuOpenContext<'a>) -> Menu + Send + 'static>;
119
120struct PendingMenuOpen {
121    title: TextComponent,
122    create: MenuFactory,
123}
124
125struct PreparedMenu {
126    title: TextComponent,
127    menu: Menu,
128}
129
130enum OpenMenuUnavailable {
131    Closed,
132    Unavailable,
133}
134
135impl OpenMenuState {
136    pub(super) const fn new() -> Self {
137        Self {
138            menu: None,
139            dispatch: None,
140            terminal_removal: None,
141            active_open_operations: 0,
142        }
143    }
144}
145
146#[cfg(test)]
147mod tests;