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 std::sync::Arc;
10
11use crate::inventory::container::{CraftingContainer, ResultContainer};
12use crate::inventory::prelude::*;
13use crate::inventory::slots::CraftingHandler;
14use crate::player::player_inventory::PlayerInventory;
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(
29        Arc::clone(&crafting_container),
30        Arc::clone(&result_container),
31        3,
32    );
33
34    let mut builder = MenuBuilder::new(&vanilla_menu_types::CRAFTING, container_id);
35    let result = builder.crafting_result_slot(handler.clone());
36    let grid = builder.section_all(crafting_container);
37    let player = builder.player_inventory(&inventory);
38
39    builder.route(result, player.all(), FillDirection::Backward);
40    builder.route(grid, player.all(), FillDirection::Forward);
41    builder.route(
42        player.main(),
43        [grid, player.hotbar()],
44        FillDirection::Forward,
45    );
46    builder.route(
47        player.hotbar(),
48        [grid, player.main()],
49        FillDirection::Forward,
50    );
51    builder.drain(grid);
52
53    builder.build(CraftingKind {
54        result_container,
55        result,
56        block_pos,
57        handler,
58    })
59}
60
61/// Per-menu crafting state: result container, table position, and recipe handler.
62pub struct CraftingKind {
63    /// The result container.
64    result_container: Shared<ResultContainer>,
65    /// The result (slot 0).
66    result: Section,
67    /// The crafting table block position.
68    block_pos: BlockPos,
69    handler: CraftingHandler,
70}
71
72// SAFETY: This Steel-owned key uniquely identifies the concrete menu kind
73// within the process.
74unsafe impl steel_utils::DowncastType for CraftingKind {
75    const TYPE_KEY: steel_utils::DowncastTypeKey =
76        steel_utils::DowncastTypeKey::new("steel:menu/crafting");
77}
78
79impl MenuKind for CraftingKind {
80    /// Prevents taking from the result slot during pickup-all.
81    fn can_take_item_for_pick_all(&self, _carried: &ItemStack, slot_index: usize) -> bool {
82        !self.result.contains(slot_index)
83    }
84
85    fn still_valid(&self, _behavior: &MenuBehavior, player: &Player) -> bool {
86        still_valid_block(
87            &player.get_world(),
88            self.block_pos,
89            &vanilla_blocks::CRAFTING_TABLE,
90            player,
91        )
92    }
93
94    /// Clears the virtual result on close. The grid is drained by [`Menu::removed`].
95    fn removed(&mut self, _behavior: &mut MenuBehavior, _player: &Player) {
96        self.result_container.lock().set_item(0, ItemStack::empty());
97    }
98
99    fn slots_changed(
100        &mut self,
101        _behavior: &mut MenuBehavior,
102        guard: &mut ContainerLockGuard,
103        _player: &Player,
104    ) {
105        self.handler.update_result(guard);
106    }
107}
108
109#[cfg(test)]
110mod tests;