Skip to main content

steel_core/inventory/menu/kinds/
crafting_menu.rs

1//! Crafting table menu (3x3 grid).
2//!
3//! Slot layout (46 total):
4//! - Slot 0: Result
5//! - Slots 1-9: 3x3 grid
6//! - Slots 10-36: Main inventory (27)
7//! - Slots 37-45: Hotbar (9)
8
9use crate::inventory::container::CraftingContainer;
10use crate::inventory::container::ResultContainer;
11use crate::inventory::prelude::*;
12use crate::inventory::slots::CraftingHandler;
13use crate::player::player_inventory::PlayerInventory;
14use steel_registry::blocks::block_state_ext::BlockStateExt;
15use steel_registry::item_stack::ItemStack;
16use steel_registry::vanilla_blocks;
17use steel_registry::vanilla_menu_types;
18use steel_utils::BlockPos;
19use steel_utils::locks::IntoShared;
20use steel_utils::locks::Shared;
21
22/// Builds the crafting table menu with a 3x3 grid.
23#[must_use]
24pub fn crafting(inventory: Shared<PlayerInventory>, container_id: u8, block_pos: BlockPos) -> Menu {
25    let crafting_container = CraftingContainer::new(3, 3).into_shared();
26    let result_container = ResultContainer::new().into_shared();
27
28    let handler = CraftingHandler::new(crafting_container.clone(), result_container.clone(), 3);
29
30    let mut builder = MenuBuilder::new(&vanilla_menu_types::CRAFTING, container_id);
31    let result = builder.result_slot(handler.clone());
32    let grid = builder.section_all(crafting_container);
33    let player = builder.player_inventory(&inventory);
34
35    builder.route(result, player.all(), FillDirection::Backward);
36    builder.route(grid, player.all(), FillDirection::Forward);
37    builder.route(
38        player.main(),
39        [grid, player.hotbar()],
40        FillDirection::Forward,
41    );
42    builder.route(
43        player.hotbar(),
44        [grid, player.main()],
45        FillDirection::Forward,
46    );
47    builder.drain(grid);
48
49    builder.build(CraftingKind {
50        result_container,
51        result,
52        block_pos,
53        handler,
54    })
55}
56
57/// Per-menu crafting state: result container, table position, and recipe handler.
58pub struct CraftingKind {
59    /// The result container.
60    result_container: Shared<ResultContainer>,
61    /// The result (slot 0).
62    result: Section,
63    /// The crafting table block position.
64    block_pos: BlockPos,
65    handler: CraftingHandler,
66}
67
68// SAFETY: This Steel-owned key uniquely identifies the concrete menu kind
69// within the process.
70unsafe impl steel_utils::DowncastType for CraftingKind {
71    const TYPE_KEY: steel_utils::DowncastTypeKey =
72        steel_utils::DowncastTypeKey::new("steel:menu/crafting");
73}
74
75impl MenuKind for CraftingKind {
76    /// Prevents taking from the result slot during pickup-all.
77    fn can_take_item_for_pick_all(&self, _carried: &ItemStack, slot_index: usize) -> bool {
78        !self.result.contains(slot_index)
79    }
80
81    /// Returns true if the block is still a crafting table and the player is in
82    /// range (plus a 4.0 buffer).
83    fn still_valid(&self, _behavior: &MenuBehavior, player: &Player) -> bool {
84        let world = player.get_world();
85        world.get_block_state(self.block_pos).get_block() == &vanilla_blocks::CRAFTING_TABLE
86            && player.is_within_block_interaction_range_with_buffer(self.block_pos, 4.0)
87    }
88
89    /// Clears the virtual result on close. The grid is drained by [`Menu::removed`].
90    fn removed(&mut self, _behavior: &mut MenuBehavior, _player: &Player) {
91        self.result_container.lock().set_item(0, ItemStack::empty());
92    }
93
94    fn slots_changed(
95        &mut self,
96        _behavior: &mut MenuBehavior,
97        guard: &mut ContainerLockGuard,
98        _player: &Player,
99    ) {
100        self.handler.update_result(guard);
101    }
102}
103
104#[cfg(test)]
105mod tests;