Recipe Generation
Recipes can be generated for a mod by subclassing RecipeProvider
and overriding #buildCraftingRecipes
. A recipe is supplied for data generation once a FinishedRecipe
view is accepted by the consumer. FinishedRecipe
s can either be created and supplied manually or, for convenience, created using a RecipeBuilder
.
After implementation, the provider must be added to the DataGenerator
.
RecipeBuilder
RecipeBuilder
is a convenience implementation for creating FinishedRecipe
s to generate. It provides basic definitions for unlocking, grouping, saving, and getting the result of a recipe. This is done through #unlockedBy
, #group
, #save
, and #getResult
respectively.
Important
ItemStack
outputs in recipes are not supported within vanilla recipe builders for RecipeProvider
. A FinishedRecipe
must be built in a different manner for existing vanilla recipe serializers to generate this data.
Warning
The item results being generated must have a valid CreativeModeTab
specified; otherwise, a NullPointerException
will be thrown. If no category accurately represents the item and no tab should be created, set the #tab
property during item initialization to CreativeModeTab#TAB_SEARCH
.
All recipe builders except for [SpecialRecipeBuilder
] require an advancement criteria to be specified. All recipes generate a criteria unlocking the recipe if the player has used the recipe previously. However, an additional criteria must be specified that allows the player to obtain the recipe without any prior knowledge. If any of the criteria specified is true, then the played will obtain the recipe for the recipe book.
Tip
Recipe criteria commonly use InventoryChangeTrigger
to unlock their recipe when certain items are present in the user’s inventory.
ShapedRecipeBuilder
ShapedRecipeBuilder
is used to generate shaped recipes. The builder can be initialized via #shaped
. The recipe group, input symbol pattern, symbol definition of ingredients, and the recipe unlock criteria can be specified before saving.
// In RecipeProvider#buildCraftingRecipes(writer)
ShapedRecipeBuilder builder = ShapedRecipeBuilder.shaped(result)
.pattern("a a") // Create recipe pattern
.define('a', item) // Define what the symbol represents
.unlockedBy("criteria", criteria) // How the recipe is unlocked
.save(writer); // Add data to builder
Additional Validation Checks
Shaped recipes have some additional validation checks performed before building:
- A pattern must be defined and take in more than one item.
- All pattern rows must be the same width.
- A symbol cannot be defined more than once.
- The space character (
' '
) is reserved for representing no item in a slot and, as such, cannot be defined. - A pattern must use all symbols defined by the user.
- A criteria besides from using the recipe must be specified to unlock the recipe.
ShapelessRecipeBuilder
ShapelessRecipeBuilder
is used to generate shapeless recipes. The builder can be initialized via #shapeless
. The recipe group, input ingredients, and the recipe unlock criteria can be specified before saving.
// In RecipeProvider#buildCraftingRecipes(writer)
ShapelessRecipeBuilder builder = ShapelessRecipeBuilder.shapeless(result)
.requires(item) // Add item to the recipe
.unlockedBy("criteria", criteria) // How the recipe is unlocked
.save(writer); // Add data to builder
SimpleCookingRecipeBuilder
SimpleCookingRecipeBuilder
is used to generate smelting, blasting, smoking, and campfire cooking recipes. Additionally, custom cooking recipes using the SimpleCookingSerializer
can also be data generated using this builder. The builder can be initialized via #smelting
, #blasting
, #smoking
, #campfireCooking
, or #cooking
respectively. The recipe group and the recipe unlock criteria can be specified before saving.
// In RecipeProvider#buildCraftingRecipes(writer)
SimpleCookingRecipeBuilder builder = SimpleCookingRecipeBuilder.smelting(input, result, experience, cookingTime)
.unlockedBy("criteria", criteria) // How the recipe is unlocked
.save(writer); // Add data to builder
SingleItemRecipeBuilder
SingleItemRecipeBuilder
is used to generate stonecutting recipes. Additionally, custom single item recipes using a serializer like SingleItemRecipe$Serializer
can also be data generated using this builder. The builder can be initialized via #stonecutting
or through the constructor respectively. The recipe group and the recipe unlock criteria can be specified before saving.
// In RecipeProvider#buildCraftingRecipes(writer)
SingleItemRecipeBuilder builder = SingleItemRecipeBuilder.stonecutting(input, result)
.unlockedBy("criteria", criteria) // How the recipe is unlocked
.save(writer); // Add data to builder
Non-RecipeBuilder
Builders
Some recipe builders do not implement RecipeBuilder
due to lacking features used by all previously mentioned recipes.
UpgradeRecipeBuilder
UpgradeRecipeBuilder
is used to generate smithing recipes. Additionally, custom upgrade recipes using a serializer like UpgradeRecipe$Serializer
can also be data generated using this builder. The builder can be initialized via #smithing
or through the constructor respectively. The recipe unlock criteria can be specified before saving.
// In RecipeProvider#buildCraftingRecipes(writer)
UpgradeRecipeBuilder builder = UpgradeRecipeBuilder.smithing(base, addition, result)
.unlocks("criteria", criteria) // How the recipe is unlocked
.save(writer, name); // Add data to builder
SpecialRecipeBuilder
SpecialRecipeBuilder
is used to generate empty JSONs for dynamic recipes that cannot easily be constrained to the recipe JSON format (dying armor, firework, etc.). The builder can be initialized via #special
.
// In RecipeProvider#buildCraftingRecipes(writer)
SpecialRecipeBuilder.special(dynamicRecipeSerializer)
.save(writer, name); // Add data to builder
Conditional Recipes
Conditional recipes can also be data generated via ConditionalRecipe$Builder
. The builder can be obtained using #builder
.
Conditions for each recipe can be specified by first calling #addCondition
and then calling #addRecipe
after all conditions have been specified. This process can be repeated as many times as the programmer would like.
After all recipes have been specified, advancements can be added for each recipe at the end using #generateAdvancement
. Alternatively, the conditional advancement can be set using #setAdvancement
.
// In RecipeProvider#buildCraftingRecipes(writer)
ConditionalRecipe.builder()
// Add the conditions for the recipe
.addCondition(...)
// Add recipe to return when conditions are true
.addRecipe(...)
// Add the next conditions for the next recipe
.addCondition(...)
// Add next recipe to return when the next conditions are true
.addRecipe(...)
// Create conditional advancement which uses the conditions
// and unlocking advancement in the recipes above
.generateAdvancement()
.build(writer, name);
IConditionBuilder
To simplify adding conditions to conditional recipes without having to construct the instances of each condition instance manually, the extended RecipeProvider
can implement IConditionBuilder
. The interface adds methods to easily construct condition instances.
// In ConditionalRecipe$Builder#addCondition
(
// If either 'examplemod:example_item'
// OR 'examplemod:example_item2' exists
// AND
// NOT FALSE
// Methods are defined by IConditionBuilder
and(
or(
itemExists("examplemod", "example_item"),
itemExists("examplemod", "example_item2")
),
not(
FALSE()
)
)
)
Custom Recipe Serializers
Custom recipe serializers can be data generated by creating a builder that can construct a FinishedRecipe
. The finished recipe encodes the recipe data and its unlocking advancement, when present, to JSON. Additionally, the name and serializer of the recipe is also specified to know where to write to and what can decode the object when loading. Once a FinishedRecipe
is constructed, it simply needs to be passed to the Consumer
supplied by RecipeProvider#buildCraftingRecipes
.
Tip
FinishedRecipe
s are flexible enough that any object transformation can be data generated, not just items.