1mod matching;
4
5use steel_utils::{DowncastType, DowncastTypeKey};
6
7use crate::data_components::vanilla_components::FireworkExplosionShape;
8use crate::item_stack::ItemStack;
9use crate::item_stack_template::ItemStackTemplate;
10
11use super::{Ingredient, RecipeData, RecipeInput, RecipeMatches, RecipeProperties};
12
13#[derive(Debug)]
15pub struct ShapedRecipe {
16 pub properties: RecipeProperties,
17 pub width: usize,
18 pub height: usize,
19 pub pattern: Box<[Ingredient]>,
20 pub result: ItemStackTemplate,
21 pub symmetrical: bool,
23}
24
25impl ShapedRecipe {
26 #[must_use]
27 pub fn new(
28 mut properties: RecipeProperties,
29 width: usize,
30 height: usize,
31 pattern: Box<[Ingredient]>,
32 result: ItemStackTemplate,
33 ) -> Self {
34 let symmetrical = Self::compute_symmetrical(width, &pattern);
35 if properties.placement.is_none() {
36 properties.placement = Some(super::PlacementInfo::from_optional_ingredients(&pattern));
37 }
38 Self {
39 properties,
40 width,
41 height,
42 pattern,
43 result,
44 symmetrical,
45 }
46 }
47
48 #[must_use]
49 pub fn with_placement(mut self, placement: super::PlacementInfo) -> Self {
50 self.properties.placement = Some(placement);
51 self
52 }
53
54 fn compute_symmetrical(width: usize, pattern: &[Ingredient]) -> bool {
55 if width == 0 {
56 return true;
57 }
58 let height = pattern.len() / width;
59 for y in 0..height {
60 for x in 0..width / 2 {
61 if !pattern[y * width + x].eq_ingredient(&pattern[y * width + (width - 1 - x)]) {
62 return false;
63 }
64 }
65 }
66 true
67 }
68
69 #[must_use]
70 pub const fn fits_in(&self, width: usize, height: usize) -> bool {
71 self.width <= width && self.height <= height
72 }
73}
74
75#[derive(Debug)]
77pub struct ShapelessRecipe {
78 pub properties: RecipeProperties,
79 pub ingredients: Box<[Ingredient]>,
80 pub result: ItemStackTemplate,
81}
82
83impl ShapelessRecipe {
84 #[must_use]
85 pub fn new(
86 mut properties: RecipeProperties,
87 ingredients: Box<[Ingredient]>,
88 result: ItemStackTemplate,
89 ) -> Self {
90 if properties.placement.is_none() {
91 properties.placement = Some(super::PlacementInfo::from_ingredients(&ingredients));
92 }
93 Self {
94 properties,
95 ingredients,
96 result,
97 }
98 }
99
100 #[must_use]
101 pub fn with_placement(mut self, placement: super::PlacementInfo) -> Self {
102 self.properties.placement = Some(placement);
103 self
104 }
105
106 #[must_use]
107 pub const fn fits_in(&self, width: usize, height: usize) -> bool {
108 self.ingredients.len() <= width * height
109 }
110}
111
112#[derive(Debug)]
113pub struct TransmuteRecipe {
114 pub properties: RecipeProperties,
115 pub input: Ingredient,
116 pub material: Ingredient,
117 pub min_material_count: usize,
118 pub max_material_count: usize,
119 pub result: ItemStackTemplate,
120 pub add_material_count_to_result: bool,
121}
122
123#[derive(Debug)]
124pub struct DyeRecipe {
125 pub properties: RecipeProperties,
126 pub target: Ingredient,
127 pub dye: Ingredient,
128 pub result: ItemStackTemplate,
129}
130
131#[derive(Debug)]
132pub struct DecoratedPotRecipe {
133 pub properties: RecipeProperties,
134 pub back: Ingredient,
135 pub left: Ingredient,
136 pub right: Ingredient,
137 pub front: Ingredient,
138 pub result: ItemStackTemplate,
139}
140
141#[derive(Debug)]
142pub struct ImbueRecipe {
143 pub properties: RecipeProperties,
144 pub source: Ingredient,
145 pub material: Ingredient,
146 pub result: ItemStackTemplate,
147}
148
149#[derive(Debug)]
150pub struct BannerDuplicateRecipe {
151 pub properties: RecipeProperties,
152 pub banner: Ingredient,
153 pub result: ItemStackTemplate,
154}
155
156#[derive(Debug)]
157pub struct BookCloningRecipe {
158 pub properties: RecipeProperties,
159 pub source: Ingredient,
160 pub material: Ingredient,
161 pub min_generation: i32,
162 pub max_generation: i32,
163 pub result: ItemStackTemplate,
164}
165
166#[derive(Debug)]
167pub struct FireworkRocketRecipe {
168 pub properties: RecipeProperties,
169 pub shell: Ingredient,
170 pub fuel: Ingredient,
171 pub star: Ingredient,
172 pub result: ItemStackTemplate,
173}
174
175#[derive(Debug)]
176pub struct FireworkStarRecipe {
177 pub properties: RecipeProperties,
178 pub shapes: Box<[(FireworkExplosionShape, Ingredient)]>,
179 pub trail: Ingredient,
180 pub twinkle: Ingredient,
181 pub fuel: Ingredient,
182 pub dye: Ingredient,
183 pub result: ItemStackTemplate,
184}
185
186#[derive(Debug)]
187pub struct FireworkStarFadeRecipe {
188 pub properties: RecipeProperties,
189 pub target: Ingredient,
190 pub dye: Ingredient,
191 pub result: ItemStackTemplate,
192}
193
194#[derive(Debug)]
195pub struct MapExtendingRecipe {
196 pub properties: RecipeProperties,
197 pub map: Ingredient,
198 pub material: Ingredient,
199 pub result: ItemStackTemplate,
200}
201
202#[derive(Debug)]
203pub struct RepairItemRecipe {
204 pub properties: RecipeProperties,
205}
206
207#[derive(Debug)]
208pub struct ShieldDecorationRecipe {
209 pub properties: RecipeProperties,
210 pub banner: Ingredient,
211 pub target: Ingredient,
212 pub result: ItemStackTemplate,
213}
214
215#[derive(Debug)]
217pub enum CraftingRecipe {
218 Shaped(ShapedRecipe),
219 Shapeless(ShapelessRecipe),
220 Transmute(TransmuteRecipe),
221 Dye(DyeRecipe),
222 DecoratedPot(DecoratedPotRecipe),
223 Imbue(ImbueRecipe),
224 BannerDuplicate(BannerDuplicateRecipe),
225 BookCloning(BookCloningRecipe),
226 FireworkRocket(FireworkRocketRecipe),
227 FireworkStar(FireworkStarRecipe),
228 FireworkStarFade(FireworkStarFadeRecipe),
229 MapExtending(MapExtendingRecipe),
230 RepairItem(RepairItemRecipe),
231 ShieldDecoration(ShieldDecorationRecipe),
232}
233
234impl CraftingRecipe {
235 #[must_use]
236 pub const fn properties(&self) -> &RecipeProperties {
237 match self {
238 Self::Shaped(recipe) => &recipe.properties,
239 Self::Shapeless(recipe) => &recipe.properties,
240 Self::Transmute(recipe) => &recipe.properties,
241 Self::Dye(recipe) => &recipe.properties,
242 Self::DecoratedPot(recipe) => &recipe.properties,
243 Self::Imbue(recipe) => &recipe.properties,
244 Self::BannerDuplicate(recipe) => &recipe.properties,
245 Self::BookCloning(recipe) => &recipe.properties,
246 Self::FireworkRocket(recipe) => &recipe.properties,
247 Self::FireworkStar(recipe) => &recipe.properties,
248 Self::FireworkStarFade(recipe) => &recipe.properties,
249 Self::MapExtending(recipe) => &recipe.properties,
250 Self::RepairItem(recipe) => &recipe.properties,
251 Self::ShieldDecoration(recipe) => &recipe.properties,
252 }
253 }
254
255 #[must_use]
256 pub const fn result(&self) -> Option<&ItemStackTemplate> {
257 match self {
258 Self::Shaped(recipe) => Some(&recipe.result),
259 Self::Shapeless(recipe) => Some(&recipe.result),
260 Self::Transmute(recipe) => Some(&recipe.result),
261 Self::Dye(recipe) => Some(&recipe.result),
262 Self::DecoratedPot(recipe) => Some(&recipe.result),
263 Self::Imbue(recipe) => Some(&recipe.result),
264 Self::BannerDuplicate(recipe) => Some(&recipe.result),
265 Self::BookCloning(recipe) => Some(&recipe.result),
266 Self::FireworkRocket(recipe) => Some(&recipe.result),
267 Self::FireworkStar(recipe) => Some(&recipe.result),
268 Self::FireworkStarFade(recipe) => Some(&recipe.result),
269 Self::MapExtending(recipe) => Some(&recipe.result),
270 Self::RepairItem(_) => None,
271 Self::ShieldDecoration(recipe) => Some(&recipe.result),
272 }
273 }
274}
275
276unsafe impl DowncastType for CraftingRecipe {
278 const TYPE_KEY: DowncastTypeKey = DowncastTypeKey::new("steel:recipe_data/crafting");
279}
280
281impl RecipeData for CraftingRecipe {
282 fn properties(&self) -> Option<&RecipeProperties> {
283 Some(self.properties())
284 }
285}
286
287#[derive(Debug, Clone, Copy, PartialEq, Eq)]
289pub struct MapRecipeData {
290 pub exploration_map: bool,
291 pub scale: u8,
292}
293
294#[derive(Debug, Clone)]
296pub struct CraftingInput {
297 pub width: usize,
298 pub height: usize,
299 pub items: Vec<ItemStack>,
300 ingredient_count: usize,
301 map_data: Vec<Option<MapRecipeData>>,
302}
303
304impl CraftingInput {
305 pub const EMPTY: Self = Self {
306 width: 0,
307 height: 0,
308 items: Vec::new(),
309 ingredient_count: 0,
310 map_data: Vec::new(),
311 };
312
313 #[must_use]
314 pub fn new(width: usize, height: usize, items: Vec<ItemStack>) -> Self {
315 debug_assert_eq!(items.len(), width * height);
316 let ingredient_count = items.iter().filter(|stack| !stack.is_empty()).count();
317 let map_data = vec![None; items.len()];
318 Self {
319 width,
320 height,
321 items,
322 ingredient_count,
323 map_data,
324 }
325 }
326
327 #[must_use]
328 pub fn with_map_data(mut self, slot: usize, data: MapRecipeData) -> Self {
329 if let Some(map_data) = self.map_data.get_mut(slot) {
330 *map_data = Some(data);
331 }
332 self
333 }
334
335 #[must_use]
336 pub fn map_data(&self, slot: usize) -> Option<MapRecipeData> {
337 self.map_data.get(slot).copied().flatten()
338 }
339
340 #[must_use]
341 pub fn positioned(
342 width: usize,
343 height: usize,
344 items: Vec<ItemStack>,
345 ) -> PositionedCraftingInput {
346 if width == 0 || height == 0 {
347 return PositionedCraftingInput::EMPTY;
348 }
349
350 let mut left = width;
351 let mut right = 0;
352 let mut top = height;
353 let mut bottom = 0;
354 for y in 0..height {
355 for x in 0..width {
356 if !items[y * width + x].is_empty() {
357 left = left.min(x);
358 right = right.max(x);
359 top = top.min(y);
360 bottom = bottom.max(y);
361 }
362 }
363 }
364 if left > right || top > bottom {
365 return PositionedCraftingInput::EMPTY;
366 }
367
368 let new_width = right - left + 1;
369 let new_height = bottom - top + 1;
370 if new_width == width && new_height == height {
371 return PositionedCraftingInput {
372 input: Self::new(width, height, items),
373 left,
374 top,
375 };
376 }
377
378 let mut positioned = Vec::with_capacity(new_width * new_height);
379 for y in 0..new_height {
380 for x in 0..new_width {
381 positioned.push(items[(x + left) + (y + top) * width].clone());
382 }
383 }
384 PositionedCraftingInput {
385 input: Self::new(new_width, new_height, positioned),
386 left,
387 top,
388 }
389 }
390
391 #[must_use]
392 pub fn get(&self, x: usize, y: usize) -> &ItemStack {
393 &self.items[y * self.width + x]
394 }
395
396 #[must_use]
397 pub const fn ingredient_count(&self) -> usize {
398 self.ingredient_count
399 }
400}
401
402unsafe impl DowncastType for CraftingInput {
404 const TYPE_KEY: DowncastTypeKey = DowncastTypeKey::new("steel:recipe_input/crafting");
405}
406
407impl RecipeInput for CraftingInput {
408 fn is_empty(&self) -> bool {
409 self.ingredient_count == 0
410 }
411}
412
413impl RecipeMatches<CraftingInput> for CraftingRecipe {
414 fn matches(&self, input: &CraftingInput) -> bool {
415 matching::matches(self, input)
416 }
417}
418
419#[derive(Debug, Clone)]
421pub struct PositionedCraftingInput {
422 pub input: CraftingInput,
423 pub left: usize,
424 pub top: usize,
425}
426
427impl PositionedCraftingInput {
428 pub const EMPTY: Self = Self {
429 input: CraftingInput::EMPTY,
430 left: 0,
431 top: 0,
432 };
433
434 #[must_use]
435 pub const fn to_grid_slot(&self, x: usize, y: usize, grid_width: usize) -> usize {
436 (x + self.left) + (y + self.top) * grid_width
437 }
438}