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 fn crafting_id(&self) -> ContainerId {
40 ContainerId::from_arc(&self.crafting_container)
41 }
42
43 #[must_use]
45 pub fn crafting_container(&self) -> Shared<CraftingContainer> {
46 self.crafting_container.clone()
47 }
48
49 #[must_use]
51 pub fn result_id(&self) -> ContainerId {
52 ContainerId::from_arc(&self.result_container)
53 }
54}
55
56impl ResultHandler for CraftingHandler {
57 fn result_container(&self) -> ContainerRef {
58 ContainerRef::from(self.result_container.clone())
59 }
60
61 fn dependencies(&self) -> Vec<ContainerRef> {
62 vec![ContainerRef::from(self.crafting_container.clone())]
63 }
64
65 fn update_result(&self, guard: &mut ContainerLockGuard) {
66 let crafting = guard
68 .get_typed::<CraftingContainer>(self.crafting_id())
69 .expect("crafting container not locked");
70
71 let result_stack =
72 recipe_manager::assemble_for_container(crafting).unwrap_or_else(ItemStack::empty);
73
74 let result_container = guard
75 .get_typed_mut::<ResultContainer>(self.result_id())
76 .expect("result container not locked");
77 result_container.set_item(0, result_stack);
78 result_container.set_changed();
79 }
80
81 fn on_result_taken(
82 &self,
83 guard: &mut ContainerLockGuard,
84 player: &Player,
85 ) -> Option<ItemStack> {
86 let mut remainder_overflow: Vec<ItemStack> = Vec::new();
88
89 let remainders_and_positioned = {
90 let crafting = guard
91 .get_typed::<CraftingContainer>(self.crafting_id())
92 .expect("crafting container not locked");
93 recipe_manager::get_remaining_items(crafting)
94 };
95
96 let Some((remainders, positioned)) = remainders_and_positioned else {
97 guard
98 .get_typed_mut::<ResultContainer>(self.result_id())
99 .expect("result container not locked")
100 .set_item(0, ItemStack::empty());
101 return None;
102 };
103
104 {
105 let crafting = guard
106 .get_typed_mut::<CraftingContainer>(self.crafting_id())
107 .expect("crafting container not locked");
108
109 let input = &positioned.input;
110
111 for y in 0..input.height {
112 for x in 0..input.width {
113 let grid_slot = positioned.to_grid_slot(x, y, self.grid_size);
114 let remainder_idx = x + y * input.width;
115 let replacement = if remainder_idx < remainders.len() {
116 remainders[remainder_idx].clone()
117 } else {
118 ItemStack::empty()
119 };
120
121 {
122 let item = crafting.get_item_mut(grid_slot);
123 if !item.is_empty() {
124 item.shrink_one();
125 }
126 }
127
128 if !replacement.is_empty() {
129 let current_item = crafting.get_item(grid_slot).clone();
130
131 if current_item.is_empty() {
132 crafting.set_item(grid_slot, replacement);
133 } else if ItemStack::is_same_item_same_components(
134 ¤t_item,
135 &replacement,
136 ) {
137 crafting.get_item_mut(grid_slot).grow(replacement.count());
138 } else {
139 remainder_overflow.push(replacement);
140 }
141 }
142 }
143 }
144
145 crafting.set_changed();
146 }
147
148 self.update_result(guard);
149
150 for remainder in remainder_overflow {
151 player.add_item_or_drop_with_guard(guard, remainder);
152 }
153
154 None
155 }
156
157 fn is_result_valid(&self, guard: &ContainerLockGuard, _player: &Player) -> bool {
158 let Some(result) = guard.get(self.result_id()) else {
159 return false;
160 };
161 let result_item = result.get_item(0);
162 if result_item.is_empty() {
163 return false;
164 }
165
166 let Some(crafting) = guard.get_typed::<CraftingContainer>(self.crafting_id()) else {
167 return false;
168 };
169
170 let Some(assembled) = recipe_manager::assemble_for_container(crafting) else {
171 return false;
172 };
173
174 ItemStack::matches(result_item, &assembled)
175 }
176}