steel_core/inventory/slots/
crafting_slots.rs1use 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#[derive(Clone)]
17pub struct CraftingHandler {
18 crafting_container: Shared<CraftingContainer>,
19 result_container: Shared<ResultContainer>,
20 grid_size: usize,
21}
22
23impl CraftingHandler {
24 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 #[must_use]
39 pub const fn is_2x2(&self) -> bool {
40 self.grid_size == 2
41 }
42
43 #[must_use]
45 pub fn crafting_id(&self) -> ContainerId {
46 ContainerId::from_arc(&self.crafting_container)
47 }
48
49 #[must_use]
51 pub fn crafting_container(&self) -> Shared<CraftingContainer> {
52 self.crafting_container.clone()
53 }
54
55 #[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 ¤t_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}