Skip to main content

steel_registry/recipe/
ingredient.rs

1//! Ingredient matching for recipes.
2
3use steel_utils::Identifier;
4
5use crate::{REGISTRY, TaggedRegistryExt, item_stack::ItemStack, items::ItemRef};
6
7/// Represents what items can satisfy a recipe slot.
8/// Matches Java's `Ingredient` class.
9#[derive(Debug, Clone)]
10pub enum Ingredient {
11    /// Matches nothing (empty slot required).
12    Empty,
13    /// Matches a single specific item.
14    Item(ItemRef),
15    /// Matches any item in a tag.
16    Tag(Identifier),
17    /// Matches any of the listed items (OR).
18    Choice(&'static [ItemRef]),
19}
20
21impl Ingredient {
22    /// Tests if the given item stack satisfies this ingredient.
23    #[must_use]
24    pub fn test(&self, stack: &ItemStack) -> bool {
25        match self {
26            Self::Empty => stack.is_empty(),
27            Self::Item(item) => !stack.is_empty() && *item == stack.item,
28            Self::Tag(tag) => {
29                if stack.is_empty() {
30                    return false;
31                }
32                REGISTRY.items.is_in_tag(stack.item, tag)
33            }
34            Self::Choice(items) => {
35                if stack.is_empty() {
36                    return false;
37                }
38                items.contains(&stack.item)
39            }
40        }
41    }
42
43    /// Returns true if this ingredient matches an empty slot.
44    #[must_use]
45    pub fn is_empty(&self) -> bool {
46        matches!(self, Self::Empty)
47    }
48
49    /// Returns all items that could satisfy this ingredient (for recipe book display).
50    #[must_use]
51    pub fn get_items(&self) -> Vec<ItemRef> {
52        match self {
53            Self::Empty => Vec::new(),
54            Self::Item(item) => vec![*item],
55            Self::Tag(tag) => REGISTRY
56                .items
57                .get_tag(tag)
58                .map(|items| items.to_vec())
59                .unwrap_or_default(),
60            Self::Choice(items) => items.to_vec(),
61        }
62    }
63
64    /// Compares two ingredients for equality (used for symmetry detection).
65    #[must_use]
66    pub fn eq_ingredient(&self, other: &Self) -> bool {
67        match (self, other) {
68            (Self::Empty, Self::Empty) => true,
69            (Self::Item(a), Self::Item(b)) => a == b,
70            (Self::Tag(a), Self::Tag(b)) => a == b,
71            (Self::Choice(a), Self::Choice(b)) => {
72                a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| x == y)
73            }
74            _ => false,
75        }
76    }
77}