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