Skip to main content

steel_core/inventory/slots/
crafting_slots.rs

1use steel_registry::item_stack::ItemStack;
2use steel_utils::locks::Shared;
3
4use crate::inventory::container::{CraftingContainer, ResultContainer};
5use crate::{
6    inventory::{
7        container::Container,
8        lock::{ContainerId, ContainerLockGuard, ContainerRef},
9        recipe_manager,
10        slots::result_handler::ResultHandler,
11    },
12    player::Player,
13};
14
15/// A Recipe Handler for Crafting Recipes
16#[derive(Clone)]
17pub struct CraftingHandler {
18    crafting_container: Shared<CraftingContainer>,
19    result_container: Shared<ResultContainer>,
20    grid_size: usize,
21}
22
23impl CraftingHandler {
24    /// Creates a new Crafting Recipe Handler
25    pub const fn new(
26        crafting_container: Shared<CraftingContainer>,
27        result_container: Shared<ResultContainer>,
28        grid_size: usize,
29    ) -> Self {
30        Self {
31            crafting_container,
32            result_container,
33            grid_size,
34        }
35    }
36
37    /// Whether the grid size of the crafting container is a 2x2
38    #[must_use]
39    pub const fn is_2x2(&self) -> bool {
40        self.grid_size == 2
41    }
42
43    /// The `ContainerId` of the crafting container
44    #[must_use]
45    pub fn crafting_id(&self) -> ContainerId {
46        ContainerId::from_arc(&self.crafting_container)
47    }
48
49    /// A shared handle to the crafting container.
50    #[must_use]
51    pub fn crafting_container(&self) -> Shared<CraftingContainer> {
52        self.crafting_container.clone()
53    }
54
55    /// The `ContainerId` of the result container
56    #[must_use]
57    pub fn result_id(&self) -> ContainerId {
58        ContainerId::from_arc(&self.result_container)
59    }
60}
61
62impl ResultHandler for CraftingHandler {
63    fn result_container(&self) -> ContainerRef {
64        ContainerRef::from(self.result_container.clone())
65    }
66
67    fn dependencies(&self) -> Vec<ContainerRef> {
68        vec![ContainerRef::from(self.crafting_container.clone())]
69    }
70
71    fn update_result(&self, guard: &mut ContainerLockGuard) {
72        let crafting = guard
73            .get_typed::<CraftingContainer>(self.crafting_id())
74            .expect("crafting container not locked");
75
76        let result_stack = recipe_manager::find_recipe(crafting, self.is_2x2())
77            .map_or_else(ItemStack::empty, |r| r.assemble());
78
79        let result_container = guard
80            .get_typed_mut::<ResultContainer>(self.result_id())
81            .expect("result container not locked");
82        result_container.set_item(0, result_stack);
83        result_container.set_changed();
84    }
85
86    fn on_result_taken(
87        &self,
88        guard: &mut ContainerLockGuard,
89        player: &Player,
90    ) -> Option<ItemStack> {
91        let mut remainder_overflow: Vec<ItemStack> = Vec::new();
92
93        let remainders_and_positioned = {
94            let crafting = guard
95                .get_typed::<CraftingContainer>(self.crafting_id())
96                .expect("crafting container not locked");
97            recipe_manager::get_remaining_items(crafting, self.is_2x2())
98        };
99
100        let Some((remainders, positioned)) = remainders_and_positioned else {
101            guard
102                .get_typed_mut::<ResultContainer>(self.result_id())
103                .expect("result container not locked")
104                .set_item(0, ItemStack::empty());
105            return None;
106        };
107
108        {
109            let crafting = guard
110                .get_typed_mut::<CraftingContainer>(self.crafting_id())
111                .expect("crafting container not locked");
112
113            let input = &positioned.input;
114
115            for y in 0..input.height {
116                for x in 0..input.width {
117                    let grid_slot = positioned.to_grid_slot(x, y, self.grid_size);
118                    let remainder_idx = x + y * input.width;
119                    let replacement = if remainder_idx < remainders.len() {
120                        remainders[remainder_idx].clone()
121                    } else {
122                        ItemStack::empty()
123                    };
124
125                    {
126                        let item = crafting.get_item_mut(grid_slot);
127                        if !item.is_empty() {
128                            item.shrink(1);
129                        }
130                    }
131
132                    if !replacement.is_empty() {
133                        let current_item = crafting.get_item(grid_slot).clone();
134
135                        if current_item.is_empty() {
136                            crafting.set_item(grid_slot, replacement);
137                        } else if ItemStack::is_same_item_same_components(
138                            &current_item,
139                            &replacement,
140                        ) {
141                            crafting.get_item_mut(grid_slot).grow(replacement.count());
142                        } else {
143                            remainder_overflow.push(replacement);
144                        }
145                    }
146                }
147            }
148
149            crafting.set_changed();
150        }
151
152        self.update_result(guard);
153
154        for remainder in remainder_overflow {
155            player.add_item_or_drop_with_guard(guard, remainder);
156        }
157
158        None
159    }
160
161    fn is_result_valid(&self, guard: &ContainerLockGuard, _player: &Player) -> bool {
162        let Some(result) = guard.get(self.result_id()) else {
163            return false;
164        };
165        let result_item = result.get_item(0);
166        if result_item.is_empty() {
167            return false;
168        }
169
170        let Some(crafting) = guard.get_typed::<CraftingContainer>(self.crafting_id()) else {
171            return false;
172        };
173
174        let Some(recipe) = recipe_manager::find_recipe(crafting, self.is_2x2()) else {
175            return false;
176        };
177
178        ItemStack::matches(result_item, &recipe.assemble())
179    }
180}