use std::{collections::BTreeMap, fs}; use proc_macro2::TokenStream; use quote::{ToTokens, quote}; use serde::Deserialize; /// Deserialized recipe entry from `recipes.json`, tagged by the `"type"` field. #[derive(Deserialize)] #[serde(tag = "type")] pub enum RecipeTypes { /// Blast furnace recipe. #[serde(rename = "minecraft:blasting")] Blasting(CookingRecipeStruct), /// Campfire cooking recipe. #[serde(rename = "minecraft:campfire_cooking")] CampfireCooking(CookingRecipeStruct), /// Shaped crafting table recipe. #[serde(rename = "minecraft:crafting_shaped")] CraftingShaped(CraftingShapedRecipeStruct), /// Shapeless crafting table recipe. #[serde(rename = "minecraft:crafting_shapeless")] CraftingShapeless(CraftingShapelessRecipeStruct), /// Transmute crafting recipe (preserves NBT/components from one slot to another). #[serde(rename = "minecraft:crafting_transmute")] CraftingTransmute(CraftingTransmuteRecipeStruct), /// Decorated-pot crafting recipe. #[serde(rename = "minecraft:crafting_decorated_pot")] CraftingDecoratedPot(CraftingDecoratedPotStruct), /// Furnace smelting recipe. #[serde(rename = "minecraft:smelting")] Smelting(CookingRecipeStruct), /// Smithing table transform recipe. #[serde(rename = "minecraft:smithing_transform")] SmithingTransform(SmithingTransformRecipeStruct), /// Smithing table armor-trim recipe. #[serde(rename = "minecraft:smithing_trim")] SmithingTrim(SmithingTrimRecipeStruct), /// Smoker cooking recipe. #[serde(rename = "minecraft:smoking")] Smoking(CookingRecipeStruct), /// Stonecutter recipe. #[serde(rename = "minecraft:stonecutting")] Stonecutting(StonecuttingRecipeStruct), /// Special crafting recipe types. #[serde(rename = "minecraft:crafting_special_bannerduplicate")] CraftingSpecialBannerDuplicate, #[serde(rename = "minecraft:crafting_special_bookcloning")] CraftingSpecialBookCloning, #[serde(rename = "minecraft:crafting_special_firework_rocket")] CraftingSpecialFireworkRocket, #[serde(rename = "minecraft:crafting_special_firework_star")] CraftingSpecialFireworkStar, #[serde(rename = "minecraft:crafting_special_firework_star_fade")] CraftingSpecialFireworkStarFade, #[serde(rename = "minecraft:crafting_special_mapextending")] CraftingSpecialMapExtending, #[serde(rename = "minecraft:crafting_special_repairitem")] CraftingSpecialRepairItem, #[serde(rename = "minecraft:crafting_special_shielddecoration")] CraftingSpecialShieldDecoration, #[serde(rename = "minecraft:crafting_dye")] CraftingDye, #[serde(rename = "minecraft:crafting_imbue")] CraftingImbue, /// Any other special crafting recipe type. #[serde(other)] #[serde(rename = "minecraft:crafting_special_*")] CraftingSpecial, } /// Deserialized smithing table transform recipe. #[derive(Deserialize)] pub struct SmithingTransformRecipeStruct { template: RecipeIngredientTypes, base: RecipeIngredientTypes, addition: RecipeIngredientTypes, result: RecipeResultStruct, } impl ToTokens for SmithingTransformRecipeStruct { fn to_tokens(&self, tokens: &mut TokenStream) { let template = self.template.to_token_stream(); let base = self.base.to_token_stream(); let addition = self.addition.to_token_stream(); let result = self.result.to_token_stream(); tokens.extend(quote! { SmithingTransformRecipe { template: #template, base: #base, addition: #addition, result: #result, } }); } } /// Deserialized smithing table armor-trim recipe. #[derive(Deserialize)] pub struct SmithingTrimRecipeStruct { template: RecipeIngredientTypes, base: RecipeIngredientTypes, addition: RecipeIngredientTypes, pattern: String, } impl ToTokens for SmithingTrimRecipeStruct { fn to_tokens(&self, tokens: &mut TokenStream) { let template = self.template.to_token_stream(); let base = self.base.to_token_stream(); let addition = self.addition.to_token_stream(); let pattern = &self.pattern; tokens.extend(quote! { SmithingTrimRecipe { template: #template, base: #base, addition: #addition, pattern: #pattern, } }); } } /// Deserialized stonecutter recipe. #[derive(Deserialize)] pub struct StonecuttingRecipeStruct { /// Optional recipe group used for advancement tracking. group: Option, /// The single ingredient required by this recipe. ingredient: RecipeIngredientTypes, /// The item produced by this recipe. result: RecipeResultStruct, } impl ToTokens for StonecuttingRecipeStruct { fn to_tokens(&self, tokens: &mut TokenStream) { let group = if let Some(group) = &self.group { quote! { Some(#group) } } else { quote! { None } }; let ingredient = self.ingredient.to_token_stream(); let result = self.result.to_token_stream(); tokens.extend(quote! { StonecutterRecipe { group: #group, ingredient: #ingredient, result: #result, } }); } } /// Deserialized cooking recipe (furnace, blast furnace, smoker, or campfire). #[derive(Deserialize)] pub struct CookingRecipeStruct { /// UI category for this recipe. category: Option, /// Optional recipe group used for advancement tracking. group: Option, /// The single ingredient required by this recipe. ingredient: RecipeIngredientTypes, /// Number of ticks required to cook this recipe. cookingtime: Option, /// Experience points awarded when the result is extracted. experience: f32, /// The item produced by this recipe. result: RecipeResultStruct, } impl CookingRecipeStruct { /// Generate a recipe ID based on result, ingredient, and cooking type /// Format: minecraft:{result}_from_{`cooking_type`}_{ingredient} fn generate_recipe_id(&self, cooking_type: &str) -> String { let result_name = self .result .id .strip_prefix("minecraft:") .unwrap_or(&self.result.id); let ingredient_name = match &self.ingredient { RecipeIngredientTypes::Simple(s) => { if s.starts_with('#') { // Tagged ingredient - strip # and replace : with _ s.strip_prefix('#').unwrap_or(s).replace(':', "_") } else { s.strip_prefix("minecraft:").unwrap_or(s).to_string() } } RecipeIngredientTypes::OneOf(items) => { // Use first item for ID generation items .first() .map_or("unknown", |s| s.strip_prefix("minecraft:").unwrap_or(s)) .to_string() } }; format!("minecraft:{result_name}_from_{cooking_type}_{ingredient_name}") } /// Emits the cooking-recipe struct fields including the provided `recipe_id`. /// /// # Arguments /// – `tokens` – the token stream to extend. /// – `recipe_id` – the pre-generated vanilla-format recipe ID string. fn to_tokens_with_id( &self, tokens: &mut TokenStream, recipe_id: &str, default_cookingtime: i32, ) { let category = match &self.category { Some(category) => category.to_token_stream(), None => RecipeCategoryTypes::Misc.to_token_stream(), }; let group = if let Some(group) = &self.group { quote! { Some(#group) } } else { quote! { None } }; let ingredient = self.ingredient.to_token_stream(); let cookingtime = self .cookingtime .unwrap_or(default_cookingtime) .to_token_stream(); let experience = self.experience.to_token_stream(); let result = self.result.to_token_stream(); tokens.extend(quote! { recipe_id: #recipe_id, category: #category, group: #group, ingredient: #ingredient, cookingtime: #cookingtime, experience: #experience, result: #result, }); } } impl ToTokens for CookingRecipeStruct { fn to_tokens(&self, tokens: &mut TokenStream) { let category = match &self.category { Some(category) => category.to_token_stream(), None => RecipeCategoryTypes::Misc.to_token_stream(), }; let group = if let Some(group) = &self.group { quote! { Some(#group) } } else { quote! { None } }; let ingredient = self.ingredient.to_token_stream(); let cookingtime = self.cookingtime.unwrap_or(200).to_token_stream(); let experience = self.experience.to_token_stream(); let result = self.result.to_token_stream(); tokens.extend(quote! { //CookingRecipeType::Blasting,CampfireCooking,Smelting,Smoking{ category: #category, group: #group, ingredient: #ingredient, cookingtime: #cookingtime, experience: #experience, result: #result, //} }); } } /// Deserialized shaped crafting recipe. #[derive(Deserialize)] pub struct CraftingShapedRecipeStruct { /// UI category for this recipe. category: Option, /// Optional recipe group used for advancement tracking. group: Option, /// Whether to show a toast notification when unlocked. show_notification: Option, /// Map from pattern key characters to their ingredient types. key: BTreeMap, /// Row strings defining the crafting grid layout. pattern: Vec, /// The item produced by this recipe. result: RecipeResultStruct, } impl ToTokens for CraftingShapedRecipeStruct { fn to_tokens(&self, tokens: &mut TokenStream) { let category = match &self.category { Some(category) => category.to_token_stream(), None => RecipeCategoryTypes::Misc.to_token_stream(), }; let group = if let Some(group) = &self.group { quote! { Some(#group) } } else { quote! { None } }; let show_notification = self.show_notification.unwrap_or(true); let key = self .key .iter() .map(|(key, ingredient)| { let key = key.chars().next().unwrap(); quote! { (#key, #ingredient) } }) .collect::>(); let pattern = self .pattern .iter() .map(quote::ToTokens::to_token_stream) .collect::>(); let result = self.result.to_token_stream(); tokens.extend(quote! { CraftingRecipeTypes::CraftingShaped { category: #category, group: #group, show_notification: #show_notification, key: &[#(#key),*], pattern: &[#(#pattern),*], result: #result, } }); } } /// Deserialized shapeless crafting recipe. #[derive(Deserialize)] pub struct CraftingShapelessRecipeStruct { /// UI category for this recipe. category: Option, /// Optional recipe group used for advancement tracking. group: Option, /// The unordered list of ingredients required. ingredients: Vec, /// The item produced by this recipe. result: RecipeResultStruct, } impl ToTokens for CraftingShapelessRecipeStruct { fn to_tokens(&self, tokens: &mut TokenStream) { let category = match &self.category { Some(category) => category.to_token_stream(), None => RecipeCategoryTypes::Misc.to_token_stream(), }; let group = if let Some(group) = &self.group { quote! { Some(#group) } } else { quote! { None } }; let ingredients = self .ingredients .iter() .map(quote::ToTokens::to_token_stream) .collect::>(); let result = self.result.to_token_stream(); tokens.extend(quote! { CraftingRecipeTypes::CraftingShapeless { category: #category, group: #group, ingredients: &[#(#ingredients),*], result: #result, } }); } } /// Deserialized transmute crafting recipe (copies components from input to result). #[derive(Deserialize)] pub struct CraftingTransmuteRecipeStruct { /// UI category for this recipe. category: Option, /// Optional recipe group used for advancement tracking. group: Option, /// The item whose data components are copied to the result. input: RecipeIngredientTypes, /// The material item consumed alongside `input`. material: RecipeIngredientTypes, /// The base item type of the result (inherits components from `input`). result: RecipeResultStruct, } impl ToTokens for CraftingTransmuteRecipeStruct { fn to_tokens(&self, tokens: &mut TokenStream) { let category = match &self.category { Some(category) => category.to_token_stream(), None => RecipeCategoryTypes::Misc.to_token_stream(), }; let group = if let Some(group) = &self.group { quote! { Some(#group) } } else { quote! { None } }; let input = self.input.to_token_stream(); let material = self.material.to_token_stream(); let result = self.result.to_token_stream(); tokens.extend(quote! { CraftingRecipeTypes::CraftingTransmute { category: #category, group: #group, input: #input, material: #material, result: #result, } }); } } /// Deserialized decorated-pot crafting recipe. #[derive(Deserialize)] pub struct CraftingDecoratedPotStruct { /// UI category for this recipe. category: Option, } impl ToTokens for CraftingDecoratedPotStruct { fn to_tokens(&self, tokens: &mut TokenStream) { let category = match &self.category { Some(category) => category.to_token_stream(), None => RecipeCategoryTypes::Misc.to_token_stream(), }; tokens.extend(quote! { CraftingRecipeTypes::CraftingDecoratedPot { category: #category, } }); } } /// Deserialized recipe result specifying the output item and count. #[derive(Deserialize)] pub struct RecipeResultStruct { /// Registry key of the result item. id: String, /// Number of result items produced (defaults to 1). count: Option, // TODO: components: Option, } impl ToTokens for RecipeResultStruct { fn to_tokens(&self, tokens: &mut TokenStream) { let id = self.id.to_token_stream(); let count = self.count.unwrap_or(1).to_token_stream(); tokens.extend(quote! { RecipeResultStruct { id: #id, count: #count, } }); } } /// Deserialized recipe ingredient, which is either a single item/tag or a list of alternatives. #[derive(Deserialize)] #[serde(untagged)] pub enum RecipeIngredientTypes { /// A single item registry key or tag (prefixed with `#`). Simple(String), /// A list of acceptable alternative item registry keys. OneOf(Vec), } impl ToTokens for RecipeIngredientTypes { fn to_tokens(&self, tokens: &mut TokenStream) { let name = match self { Self::Simple(ingredient) => { if ingredient.starts_with('#') { quote! { RecipeIngredientTypes::Tagged(#ingredient) } } else { quote! { RecipeIngredientTypes::Simple(#ingredient) } } } Self::OneOf(ingredients) => { let ingredients = ingredients .iter() .map(quote::ToTokens::to_token_stream) .collect::>(); quote! { RecipeIngredientTypes::OneOf(&[#(#ingredients),*]) } } }; tokens.extend(name); } } /// Deserialized recipe UI category used to group recipes in the recipe book. #[derive(Deserialize)] pub enum RecipeCategoryTypes { /// Equipment recipes (tools, weapons, armor). #[serde(rename = "equipment")] Equipment, /// Building block recipes. #[serde(rename = "building")] Building, /// Redstone component recipes. #[serde(rename = "redstone")] Restone, /// Miscellaneous recipes that don't fit other categories. #[serde(rename = "misc")] Misc, /// Food item recipes. #[serde(rename = "food")] Food, /// Block recipes. #[serde(rename = "blocks")] Blocks, } impl ToTokens for RecipeCategoryTypes { fn to_tokens(&self, tokens: &mut TokenStream) { let name = match self { Self::Equipment => { quote! { RecipeCategoryTypes::Equipment } } Self::Building => { quote! { RecipeCategoryTypes::Building } } Self::Restone => { quote! { RecipeCategoryTypes::Restone } } Self::Misc => { quote! { RecipeCategoryTypes::Misc } } Self::Food => { quote! { RecipeCategoryTypes::Food } } Self::Blocks => { quote! { RecipeCategoryTypes::Blocks } } }; tokens.extend(name); } } /// Reads recipe JSON files from the 26.2 datapack and emits the complete recipe constants and helpers `TokenStream`. pub fn build() -> TokenStream { let dir = std::path::Path::new("../../assets/datapacks/26_2/data/minecraft/recipe"); let mut recipes_assets: BTreeMap = BTreeMap::new(); let mut entries: Vec<_> = fs::read_dir(dir) .expect("Missing recipe directory") .flatten() .filter(|e| e.path().extension().is_some_and(|ext| ext == "json")) .collect(); entries.sort_by_key(|e| e.path()); for entry in entries { let path = entry.path(); let stem = path.file_stem().unwrap().to_string_lossy().into_owned(); let key = format!("minecraft:{stem}"); let content = fs::read_to_string(&path).expect("Failed to read recipe file"); let recipe = serde_json::from_str::(&content) .unwrap_or_else(|e| panic!("Failed to parse recipe {}: {e}", path.display())); recipes_assets.insert(key, recipe); } let mut crafting_recipes = Vec::new(); let mut cooking_recipes = Vec::new(); let mut stonecutting_recipes = Vec::new(); let mut smithing_trim_recipes = Vec::new(); let mut smithing_transform_recipes = Vec::new(); for (recipe_id, recipe) in recipes_assets { match recipe { RecipeTypes::Blasting(recipe) => { let mut common_cooking_token = TokenStream::new(); recipe.to_tokens_with_id(&mut common_cooking_token, &recipe_id, 100); let blasting_token = quote! { CookingRecipeType::Blasting (CookingRecipe { #common_cooking_token }) }; cooking_recipes.push(blasting_token); } RecipeTypes::CampfireCooking(recipe) => { let mut common_cooking_token = TokenStream::new(); recipe.to_tokens_with_id(&mut common_cooking_token, &recipe_id, 100); let campfire_token = quote! { CookingRecipeType::CampfireCooking (CookingRecipe { #common_cooking_token }) }; cooking_recipes.push(campfire_token); } RecipeTypes::CraftingShaped(recipe) => { crafting_recipes.push(recipe.to_token_stream()); } RecipeTypes::CraftingShapeless(recipe) => { crafting_recipes.push(recipe.to_token_stream()); } RecipeTypes::CraftingTransmute(recipe) => { crafting_recipes.push(recipe.to_token_stream()); } RecipeTypes::CraftingDecoratedPot(recipe) => { crafting_recipes.push(recipe.to_token_stream()); } RecipeTypes::Smelting(recipe) => { let mut common_cooking_token = TokenStream::new(); recipe.to_tokens_with_id(&mut common_cooking_token, &recipe_id, 200); let smelting_token = quote! { CookingRecipeType::Smelting(CookingRecipe { #common_cooking_token }) }; cooking_recipes.push(smelting_token); } RecipeTypes::SmithingTransform(recipe) => { smithing_transform_recipes.push(recipe.to_token_stream()); } RecipeTypes::SmithingTrim(recipe) => { smithing_trim_recipes.push(recipe.to_token_stream()); } RecipeTypes::Smoking(recipe) => { let mut common_cooking_token = TokenStream::new(); recipe.to_tokens_with_id(&mut common_cooking_token, &recipe_id, 100); let smoking_token = quote! { CookingRecipeType::Smoking(CookingRecipe{ #common_cooking_token }) }; cooking_recipes.push(smoking_token); } RecipeTypes::Stonecutting(recipe) => { stonecutting_recipes.push(recipe.to_token_stream()); } RecipeTypes::CraftingSpecial | RecipeTypes::CraftingSpecialBannerDuplicate | RecipeTypes::CraftingSpecialBookCloning | RecipeTypes::CraftingSpecialFireworkRocket | RecipeTypes::CraftingSpecialFireworkStar | RecipeTypes::CraftingSpecialFireworkStarFade | RecipeTypes::CraftingSpecialMapExtending | RecipeTypes::CraftingSpecialRepairItem | RecipeTypes::CraftingSpecialShieldDecoration | RecipeTypes::CraftingDye | RecipeTypes::CraftingImbue => {} } } quote! { use crate::tag::Taggable; use crate::item::Item; use serde::{Serialize, Deserialize}; #[derive(Clone, Debug, Serialize)] pub enum CraftingRecipeTypes { CraftingShaped { category: RecipeCategoryTypes, group: Option<&'static str>, show_notification: bool, key: &'static [(char, RecipeIngredientTypes)], pattern: &'static [&'static str], result: RecipeResultStruct, }, CraftingShapeless { category: RecipeCategoryTypes, group: Option<&'static str>, ingredients: &'static [RecipeIngredientTypes], result: RecipeResultStruct, }, CraftingTransmute { category: RecipeCategoryTypes, group: Option<&'static str>, input: RecipeIngredientTypes, material: RecipeIngredientTypes, result: RecipeResultStruct, }, CraftingDecoratedPot { category: RecipeCategoryTypes, }, CraftingSpecial, } #[allow(dead_code)] #[derive(Clone, Debug, Serialize)] pub struct CookingRecipe { /// Vanilla-compatible recipe ID (e.g., "minecraft:iron_ingot_from_smelting_iron_ore") pub recipe_id: &'static str, pub category: RecipeCategoryTypes, pub group: Option<&'static str>, pub ingredient: RecipeIngredientTypes, pub cookingtime: i32, pub experience: f32, pub result: RecipeResultStruct, } #[derive(Clone, Debug, Serialize)] pub enum CookingRecipeType { Blasting(CookingRecipe), Smelting(CookingRecipe), Smoking(CookingRecipe), CampfireCooking(CookingRecipe), } #[derive(Clone, Debug, Serialize)] pub enum CookingRecipeKind { Blasting, Smelting, Smoking, CampfireCooking, } impl From<&CookingRecipeType> for CookingRecipeKind { fn from(recipe_type: &CookingRecipeType) -> Self { match recipe_type { CookingRecipeType::Blasting(_) => Self::Blasting, CookingRecipeType::Smelting(_) => Self::Smelting, CookingRecipeType::Smoking(_) => Self::Smoking, CookingRecipeType::CampfireCooking(_) => Self::CampfireCooking, } } } impl From for CookingRecipeKind { fn from(recipe_type: CookingRecipeType) -> Self { match recipe_type { CookingRecipeType::Blasting(_) => Self::Blasting, CookingRecipeType::Smelting(_) => Self::Smelting, CookingRecipeType::Smoking(_) => Self::Smoking, CookingRecipeType::CampfireCooking(_) => Self::CampfireCooking, } } } impl CookingRecipeKind { #[must_use] pub const fn to_type(self, recipe: CookingRecipe) -> CookingRecipeType { match self { Self::Blasting => CookingRecipeType::Blasting(recipe), Self::Smelting => CookingRecipeType::Smelting(recipe), Self::Smoking => CookingRecipeType::Smoking(recipe), Self::CampfireCooking => CookingRecipeType::CampfireCooking(recipe), } } } #[derive(Clone, Debug, Serialize)] pub struct StonecutterRecipe { pub group: Option<&'static str>, pub ingredient: RecipeIngredientTypes, pub result: RecipeResultStruct, } #[derive(Clone, Debug, Serialize)] pub struct RecipeResultStruct { pub id: &'static str, pub count: u8, } #[derive(Clone, Debug, Serialize)] pub enum RecipeIngredientTypes { Simple(&'static str), Tagged(&'static str), OneOf(&'static [&'static str]), } impl RecipeIngredientTypes { #[must_use] pub fn match_item(&self, item: &Item) -> bool { match self { Self::Simple(ingredient) => { let name = format!("minecraft:{}", item.registry_key); name == *ingredient } Self::Tagged(tag) => item .is_tagged_with(tag) .expect("Crafting recipe used invalid tag"), Self::OneOf(ingredients) => { let name = format!("minecraft:{}", item.registry_key); ingredients.contains(&name.as_str()) } } } } #[derive(Clone, Debug, Serialize)] pub enum RecipeCategoryTypes { Equipment, Building, Restone, Misc, Food, Blocks, } pub static RECIPES_CRAFTING: &[CraftingRecipeTypes] = &[ #(#crafting_recipes),* ]; pub static RECIPES_COOKING: &[CookingRecipeType] = &[ #(#cooking_recipes ),* ]; pub static RECIPES_STONECUTTING: &[StonecutterRecipe] = &[ #(#stonecutting_recipes),* ]; pub static RECIPES_SMITHING_TRIM: &[SmithingTrimRecipe] = &[ #(#smithing_trim_recipes),* ]; pub static RECIPES_SMITHING_TRANSFORM: &[SmithingTransformRecipe] = &[ #(#smithing_transform_recipes),* ]; #[derive(Clone, Debug, Serialize)] pub struct SmithingTrimRecipe { pub template: RecipeIngredientTypes, pub base: RecipeIngredientTypes, pub addition: RecipeIngredientTypes, pub pattern: &'static str, } impl SmithingTrimRecipe { #[must_use] pub fn matches(&self, template: &Item, base: &Item, addition: &Item) -> bool { self.template.match_item(template) && self.base.match_item(base) && self.addition.match_item(addition) } } #[must_use] pub fn get_smithing_trim_recipe( template: &Item, base: &Item, addition: &Item, ) -> Option<&'static SmithingTrimRecipe> { RECIPES_SMITHING_TRIM .iter() .find(|recipe| recipe.matches(template, base, addition)) } #[derive(Clone, Debug, Serialize)] pub struct SmithingTransformRecipe { pub template: RecipeIngredientTypes, pub base: RecipeIngredientTypes, pub addition: RecipeIngredientTypes, pub result: RecipeResultStruct, } impl SmithingTransformRecipe { #[must_use] pub fn matches(&self, template: &Item, base: &Item, addition: &Item) -> bool { self.template.match_item(template) && self.base.match_item(base) && self.addition.match_item(addition) } } #[must_use] pub fn get_smithing_transform_recipe( template: &Item, base: &Item, addition: &Item, ) -> Option<&'static SmithingTransformRecipe> { RECIPES_SMITHING_TRANSFORM .iter() .find(|recipe| recipe.matches(template, base, addition)) } /// Returns the trim material registry key for a given item, if it is a valid trim material. #[must_use] pub fn get_trim_material_for_item(item: &Item) -> Option<&'static str> { match item.registry_key { "amethyst_shard" => Some("minecraft:amethyst"), "copper_ingot" => Some("minecraft:copper"), "diamond" => Some("minecraft:diamond"), "emerald" => Some("minecraft:emerald"), "gold_ingot" => Some("minecraft:gold"), "iron_ingot" => Some("minecraft:iron"), "lapis_lazuli" => Some("minecraft:lapis"), "netherite_ingot" => Some("minecraft:netherite"), "quartz" => Some("minecraft:quartz"), "redstone" => Some("minecraft:redstone"), "resin_brick" | "resin_clump" => Some("minecraft:resin"), _ => None, } } #[must_use] pub fn get_cooking_recipe_with_ingredient(ingredient: &Item, recipe_type: CookingRecipeKind) -> Option<&'static CookingRecipe> { RECIPES_COOKING .iter() .find_map(|recipe| match (recipe, &recipe_type) { (CookingRecipeType::Blasting(cooking_recipe), CookingRecipeKind::Blasting) | (CookingRecipeType::Smelting(cooking_recipe), CookingRecipeKind::Smelting) | (CookingRecipeType::Smoking(cooking_recipe), CookingRecipeKind::Smoking) | ( CookingRecipeType::CampfireCooking(cooking_recipe), CookingRecipeKind::CampfireCooking, ) => { cooking_recipe.ingredient.match_item(ingredient).then_some(cooking_recipe) } _ => None, }) } /// Get the experience value for a recipe by its recipe ID. /// Used for calculating XP when extracting from furnace. /// Recipe IDs are in vanilla format like `"minecraft:iron_ingot_from_smelting_iron_ore"` #[must_use] pub fn get_recipe_experience(recipe_id: &str) -> Option { RECIPES_COOKING.iter().find_map(|recipe| { let cooking_recipe = match recipe { CookingRecipeType::Blasting(r) | CookingRecipeType::Smelting(r) | CookingRecipeType::Smoking(r) | CookingRecipeType::CampfireCooking(r) => r, }; (cooking_recipe.recipe_id == recipe_id).then_some(cooking_recipe.experience) }) } } }