Skip to main content

pumpkinplus/modules/recipes/
recipe.rs

1//! Recipe system for PumpkinPlus.
2//!
3//! Each recipe category is implemented as a module implementing the [`Recipe`] trait.
4//! Recipes are registered with the server in bulk via [`Recipe::register`].
5//!
6//! ## Supported Recipe Types
7//!
8//! | Type        | Pumpkin API Status | Description                          |
9//! |-------------|--------------------|--------------------------------------|
10//! | `shaped`    | ✅ Available       | Crafting recipes with a fixed layout |
11//! | `shapeless` | ✅ Available       | Crafting recipes with loose items    |
12//! | `cooking`   | ✅ Available       | Furnace, smoker, campfire, blast     |
13//! | `potion`    | ⛔ Unavailable     | Potion brewing recipes               |
14
15use std::time::Instant;
16use tracing::info;
17
18/// A trait representing a collection of custom recipes that can be registered.
19///
20/// Types implementing this trait provide one or more recipes to be added to the
21/// server when [`Recipe::register`] is called. All recipes are returned in
22/// bulk to allow for efficient registration.
23///
24/// # Example
25///
26/// ```rust,ignore
27/// pub struct MyRecipes;
28///
29/// impl Recipe for MyRecipes {
30///     fn recipes(&self) -> Vec<RecipeKind> {
31///         vec![
32///             RecipeKind::Shaped {
33///                 // ...
34///             },
35///         ]
36///     }
37/// }
38/// ```
39pub trait Recipe {
40    /// Returns the shaped crafting recipes to be registered.
41    ///
42    /// Each entry describes a recipe with a fixed grid pattern. Override this
43    /// to provide shaped recipes. Defaults to an empty vector.
44    fn shaped(&self) -> Vec<ShapedRecipe> {
45        vec![]
46    }
47
48    /// Returns the shapeless crafting recipes to be registered.
49    ///
50    /// Each entry describes a recipe where ingredients can be placed in any
51    /// slot of the crafting grid. Override this to provide shapeless recipes.
52    /// Defaults to an empty vector.
53    fn shapeless(&self) -> Vec<ShapelessRecipe> {
54        vec![]
55    }
56
57    /// Returns the cooking recipes to be registered.
58    ///
59    /// Covers furnace, smoker, blast furnace, and campfire recipes. Override
60    /// this to provide cooking recipes. Defaults to an empty vector.
61    fn cooking(&self) -> Vec<CookingRecipe> {
62        vec![]
63    }
64
65    /// Returns `true` if there is at least one recipe to register.
66    fn has_recipes(&self) -> bool {
67        !self.shaped().is_empty() || !self.shapeless().is_empty() || !self.cooking().is_empty()
68    }
69
70    /// Registers all recipes returned by the trait methods.
71    ///
72    /// Logs the count and time taken. If no recipes are present this is a
73    /// no-op.
74    fn register(&self) {
75        if !self.has_recipes() {
76            return;
77        }
78
79        let start = Instant::now();
80
81        let shaped = self.shaped();
82        let shapeless = self.shapeless();
83        let cooking = self.cooking();
84
85        let total = shaped.len() + shapeless.len() + cooking.len();
86
87        // TODO: Wire to pumpkin_plugin_api::recipe once re-exported upstream.
88        // https://github.com/Pumpkin-MC/Pumpkin/issues/XXX
89        //
90        // Example desired call:
91        // context.register_shaped_recipes(&shaped);
92        // context.register_shapeless_recipes(&shapeless);
93        // context.register_cooking_recipes(&cooking);
94
95        let elapsed = start.elapsed().as_millis();
96        info!(
97            "Registered: {} recipe(s) ({} shaped, {} shapeless, {} cooking) | Took {}ms",
98            total,
99            shaped.len(),
100            shapeless.len(),
101            cooking.len(),
102            elapsed
103        );
104    }
105}
106
107/// A shaped crafting recipe.
108///
109/// Patterns use single-character keys mapped to [`Ingredient`] entries.
110/// Spaces represent empty slots.
111#[derive(Debug, Clone)]
112pub struct ShapedRecipe {
113    /// Unique recipe identifier (e.g. `"pumpkinplus:diamond_horse_armor"`).
114    pub id: String,
115    /// Grid height (1–3).
116    pub height: u8,
117    /// Grid width (1–3).
118    pub width: u8,
119    /// Pattern rows. Each string must be exactly `width` characters.
120    /// Use a space `' '` for an empty slot.
121    pub pattern: Vec<String>,
122    /// Mapping from pattern characters to ingredients.
123    pub keys: Vec<(char, Ingredient)>,
124    /// The result item.
125    pub result: ItemStack,
126}
127
128/// A shapeless crafting recipe.
129///
130/// Ingredients can be placed in any slot of the crafting grid.
131#[derive(Debug, Clone)]
132pub struct ShapelessRecipe {
133    /// Unique recipe identifier.
134    pub id: String,
135    /// List of ingredients (may include duplicates for multi-count slots).
136    pub ingredients: Vec<Ingredient>,
137    /// The result item.
138    pub result: ItemStack,
139}
140
141/// A furnace / smoker / blast furnace / campfire recipe.
142#[derive(Debug, Clone)]
143pub struct CookingRecipe {
144    /// Unique recipe identifier.
145    pub id: String,
146    /// The input ingredient.
147    pub ingredient: Ingredient,
148    /// The result item.
149    pub result: ItemStack,
150    /// Base cooking time in ticks (e.g. 200 for furnace).
151    pub cook_time: u32,
152    /// Experience granted when the item is removed.
153    pub experience: f32,
154    /// Which cooking block this applies to.
155    pub kind: CookingKind,
156}
157
158/// Variant of cooking recipe.
159#[derive(Debug, Clone, Copy, PartialEq, Eq)]
160pub enum CookingKind {
161    /// Standard furnace.
162    Smelting,
163    /// Blast furnace (2× speed).
164    Blasting,
165    /// Smoker (2× speed).
166    Smoking,
167    /// Campfire (3× duration).
168    Campfire,
169}
170
171/// An ingredient accepted by a recipe.
172///
173/// Mirrors the Pumpkin WIT `RecipeIngredient` type.
174#[derive(Debug, Clone)]
175pub enum Ingredient {
176    /// Accept an exact item by its identifier (e.g. `"minecraft:diamond"`).
177    Item { id: String },
178    /// Accept any item in a tag (e.g. `"minecraft:logs"`).
179    Tag { id: String },
180}
181
182/// A stack of items produced by a recipe.
183///
184/// Mirrors the Pumpkin WIT `ItemStack` type.
185#[derive(Debug, Clone)]
186pub struct ItemStack {
187    /// Item identifier (e.g. `"minecraft:diamond_horse_armor"`).
188    pub id: String,
189    /// Number of items in the stack.
190    pub count: u8,
191}
192
193impl Default for ItemStack {
194    fn default() -> Self {
195        Self {
196            id: String::new(),
197            count: 1,
198        }
199    }
200}