Pumpkin/tools/pumpkin-codegen/src/recipes.rs
Alexander Medvedev 2be3e8be88 fix: test
2026-09-02 12:04:21 +02:00

916 lines
33 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<String>,
/// 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<RecipeCategoryTypes>,
/// Optional recipe group used for advancement tracking.
group: Option<String>,
/// The single ingredient required by this recipe.
ingredient: RecipeIngredientTypes,
/// Number of ticks required to cook this recipe.
cookingtime: Option<i32>,
/// 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<RecipeCategoryTypes>,
/// Optional recipe group used for advancement tracking.
group: Option<String>,
/// Whether to show a toast notification when unlocked.
show_notification: Option<bool>,
/// Map from pattern key characters to their ingredient types.
key: BTreeMap<String, RecipeIngredientTypes>,
/// Row strings defining the crafting grid layout.
pattern: Vec<String>,
/// 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::<Vec<_>>();
let pattern = self
.pattern
.iter()
.map(quote::ToTokens::to_token_stream)
.collect::<Vec<_>>();
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<RecipeCategoryTypes>,
/// Optional recipe group used for advancement tracking.
group: Option<String>,
/// The unordered list of ingredients required.
ingredients: Vec<RecipeIngredientTypes>,
/// 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::<Vec<_>>();
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<RecipeCategoryTypes>,
/// Optional recipe group used for advancement tracking.
group: Option<String>,
/// 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<RecipeCategoryTypes>,
}
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<u8>,
// TODO: components: Option<RecipeResultComponentsStruct>,
}
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<String>),
}
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::<Vec<_>>();
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<String, RecipeTypes> = 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::<RecipeTypes>(&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<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 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<f32> {
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)
})
}
}
}