Skip to main content

steel_core/inventory/menu/kinds/
chest_menu.rs

1//! Chest menu for chest-like containers (chests, barrels, ender chests, shulker boxes).
2//!
3//! 1-6 rows of 9 slots. Layout:
4//! - Slots 0 to `rows * 9 - 1`: Container
5//! - Slots `rows * 9` to `rows * 9 + 26`: Main inventory (27)
6//! - Slots `rows * 9 + 27` to `rows * 9 + 35`: Hotbar (9)
7
8use steel_registry::menu_type::MenuTypeRef;
9use steel_registry::vanilla_menu_types;
10
11use crate::inventory::prelude::*;
12use crate::player::player_inventory::PlayerInventory;
13
14/// Builds a chest-like menu with `rows` rows of 9 slots plus the player inventory.
15///
16/// # Panics
17/// Panics if `rows` is 0 or greater than 6.
18#[must_use]
19pub fn chest(
20    inventory: Shared<PlayerInventory>,
21    container_id: u8,
22    container: impl Into<ContainerRef>,
23    rows: usize,
24) -> Menu {
25    let container = container.into();
26    assert!(
27        (1..=6).contains(&rows),
28        "Chest rows must be between 1 and 6"
29    );
30
31    let mut builder = MenuBuilder::new(menu_type_for_rows(rows), container_id);
32    let chest = builder.section(&container, rows * 9);
33    let player = builder.player_inventory(&inventory);
34
35    builder.route(chest, player.all(), FillDirection::Backward);
36    builder.route(player.all(), chest, FillDirection::Forward);
37
38    builder.build(ChestKind { container })
39}
40
41/// Menu type for a chest of `rows` rows.
42///
43/// # Panics
44/// Panics if `rows` is 0 or greater than 6.
45#[must_use]
46pub fn menu_type_for_rows(rows: usize) -> MenuTypeRef {
47    match rows {
48        1 => &vanilla_menu_types::GENERIC_9X1,
49        2 => &vanilla_menu_types::GENERIC_9X2,
50        3 => &vanilla_menu_types::GENERIC_9X3,
51        4 => &vanilla_menu_types::GENERIC_9X4,
52        5 => &vanilla_menu_types::GENERIC_9X5,
53        6 => &vanilla_menu_types::GENERIC_9X6,
54        _ => panic!("Invalid row count: {rows}"),
55    }
56}
57
58/// Per-menu chest state: just the backing container for the validity check.
59pub struct ChestKind {
60    /// The backing container.
61    container: ContainerRef,
62}
63
64// SAFETY: This Steel-owned key uniquely identifies the concrete menu kind
65// within the process.
66unsafe impl steel_utils::DowncastType for ChestKind {
67    const TYPE_KEY: steel_utils::DowncastTypeKey =
68        steel_utils::DowncastTypeKey::new("steel:menu/chest");
69}
70
71impl MenuKind for ChestKind {
72    /// Returns true if the backing container is still valid for the player.
73    fn still_valid(&self, _behavior: &MenuBehavior, player: &Player) -> bool {
74        self.container.still_valid(player)
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use steel_utils::locks::IntoShared as _;
81
82    use super::*;
83    use crate::inventory::container::SimpleContainer;
84
85    #[test]
86    fn chest_uses_exactly_the_rows_requested_from_oversized_container() {
87        let inventory = PlayerInventory::new().into_shared();
88        let container = SimpleContainer::new(18).into_shared();
89
90        let menu = chest(inventory, 1, container, 1);
91
92        assert_eq!(menu.behavior().slot_count(), 9 + 36);
93    }
94}