Difference between revisions of "Custom Recipes"

From Forge Community Wiki
(Update to 1.17 and fix all the wrong information, probably should come back later to finish at some point)
(Improve overall documentation for custom recipes)
Line 1: Line 1:
= Custom Recipe Serializers =
+
Recipes are a core concept within the use of items. They allow you to transform some item into another through the use of crafting, smelting, smithing, etc. all of which can be defined in JSON through datapacks. However, the recipe system in its entirety is not only limited to what vanilla can provide. Any modder can create their own custom recipe implementation.
  
Want to make a custom recipe type for a machine, or some mechanic your mod has? Great! It means users can participate in the datapack system for recipes, so you get automatic syncing, updating, and ease of maintenance on servers. Here's what you need to get started:
+
== Creating a New Recipe ==
  
== Classes/Interfaces for the inventories and serialization ==
+
Creating a new recipe boils down to three things: <code>Recipe</code>, <code>RecipeType</code>, and <code>RecipeSerializer</code>.
  
==== RecipeSerializer ====
+
For a general overview:
The thing that actually serializes recipe files. It implements the vanilla recipe serializer interface and provides means of reading from JSON and performing read/write operations on network buffer objects. One should look into using [[Codecs]] for this class, as using them simplifies the logic significantly.
 
  
<syntaxhighlight lang="java">
+
* <code>Recipe</code>: The actual recipe implementation which handles the matching logic, construction logic, and the display data. All custom recipes need some implementation of this class.
public class MyRecipeSerializer
+
* <code>RecipeType</code>: The category the recipe belongs to. For example, a recipe placed in the <code>#CRAFTING</code> category is expected to work within the crafting table. A custom recipe category indicates that recipes of that type will be used in that particular context.
  extends ForgeRegistryEntry<RecipeSerializer<?>>  
+
* <code>RecipeSerializer</code>: The serializer which handles decoding the data from JSON and handling the data when syncing across the network. Each serializer can serialize to one <code>Recipe</code> implementation. The <code>type</code> field within any recipe JSON references the registry name of this serializer.
  implements RecipeSerializer<MyRecipe> {
 
 
 
  // your serializer code here
 
  
}
+
How one uses the recipe and its data is up to the implementing modder.
</syntaxhighlight>
 
  
==== RecipeType ====
+
{{Tip/Warning|Creating a Recipe Book implementation will not be covered here. That is currently hardcoded to only accept vanilla categories.}}
Links the recipe type to its implementation class(es). This is a very lightweight class that vanilla uses to determine what recipes are associated with what implementors (e.g. <code>CRAFTING</code> is for <code>SHAPED_RECIPE</code>, <code>SHAPELESS_RECIPE</code>, etc.).
 
  
==== Container ====
+
=== Recipe ===
Vanilla's inventory system used in recipes to grab references to implementors to see if the associated recipe matches the current container data.
 
  
==== Recipe ====
+
<code>Recipe</code> is an interface which holds data about the recipe being represented and how to test whether it is valid. It has a single parameterized type of some <code>Container</code> subtype. This is used to get a snapshot of the current inventory container, and as such should only be read from in this class. In most cases, this can just be left as <code>Container</code>.
Your actual recipe class or an abstraction to use your own recipe matching system. Takes the <code>Container</code> from above and matches it. Your recipe class should return the <code>RecipeType</code> from registration in <code>#getType</code> and the serializer in <code>#getSerializer</code>.
 
  
== Setup ==
+
The following methods must be implemented within your implementation:
Once you have the above implementations, here's how to wire everything up:
 
  
# Register your recipe serializer to the <code>RECIPE_SERIALIZERS</code> Forge registry.
+
{| class="wikitable"
# Register the recipe type during the common setup event or statically. See below.
+
|+ Required Methods
# Place a JSON file in <code>data/recipes/my_recipe.json</code> and ensure it has <code>"type": "mymod:my_recipe_serializer_registry_name"</code> in the JSON structure.  
+
|-
 +
! Method !! Description
 +
|-
 +
| getId || This method represents the unique name of the recipe. This usually refers to the name of the JSON file and is passed in while decoding.
 +
|-
 +
| getSerializer || This method represents the serializer used to decode and send this recipe across the network.
 +
|-
 +
| getType || This method represents the category the recipe is in.
 +
|-
 +
| canCraftInDimensions || This takes in a width and height and checks whether the recipe can be created within those bounds. This is only used within the Recipe Book.
 +
|-
 +
| matches || This method checks if the passed in inventory container can craft using the recipe stored in this class. Using the standard <code>RecipeManager</code> interface, this is always used to check whether the recipe can be used.
 +
|-
 +
| assemble || This creates the resulting <code>ItemStack</code> to return to the player if the recipe has been crafted. This should always return a unique instance. If you are unsure whether yours does, call <code>ItemStack#copy</code> on the decoded result before returning.
 +
|-
 +
| getResultItem || This shows the resulting item in displays like the Recipe Book. This does not need to return a unique instance and usually represents the decoded result directly.
 +
|}
  
<syntaxhighlight lang="java">
+
{{Tip/Important|If you would like to use your own recipe checker because the inventory container does not provide you with enough data, you will need to reimplement the <code>RecipeManager</code> methods yourself.}}
RecipeType.register("mymod:my_recipe_type");
 
</syntaxhighlight>
 
  
This will load the recipe using the recipe serialization system, and wire it back to the serializer via matching the registry name of the serializer to the value you specified while registering the serializer in step 1.
+
There are some defaulted methods as well. Although optional, they will be covered as well:
 +
 
 +
{| class="wikitable"
 +
|+ Optional Methods
 +
|-
 +
! Method !! Description
 +
|-
 +
| getGroup || This gets the group the recipe is currently associated with. This is used by the Recipe Book to group similar recipes into one entry to reduce cluttering. This would be passed in from the decoded JSON. By default, this returns an empty string.
 +
|-
 +
| getIngredients || This gets the ingredients of the recipe. This is once again used by the Recipe Book to display the ingredients of the recipe. These are usually the decoded <code>Ingredient</code>s from JSON. By default, this returns an empty list.
 +
|-
 +
| isSpecial || This usually signals that the recipe cannot purely be represented in JSON as it has some dynamic metadata preventing it from being so. Special recipes will not appear in the Recipe Book for this reason as they provide a number of combinations which cannot be simply expressed. By default, this is false.
 +
|-
 +
| getToastSymbol || This returns the symbol that appears when the recipe is seen in a toast, usually from unlocking via an advancement. This is not the actual recipe output itself, but it signifies what the recipe was made within. By default this returns a crafting table stack.
 +
|-
 +
| getRemainingItems || This returns what items remain in the inventory container after the recipe has been created. By default, this will return a list of container items.
 +
|-
 +
| isIncomplete || This returns whether the supplied ingredients is empty or if any <code>Ingredient</code> has no items to test against. This is another check by the Recipe Book to prevent impossible recipes from appearing.
 +
|}
 +
 
 +
When in practice, these will be decoded from a <code>RecipeSerializer</code> and stored in a map with a key of the <code>RecipeType</code> category it belongs to. The recipe itself is dynamically registered to the <code>RecipeManager</code> for each decoded instance.
 +
 
 +
=== RecipeType ===
 +
 
 +
<code>RecipeType</code> is another interface; however, it does not store any data itself. It is simply a non-forge wrapped registry object which represents the category the <code>Recipe</code> implementation belongs to. As such, it only needs to be supplied its registry name. This can be registered via <code>RecipeType#register</code> by either static initialization or deferring the registration until <code>FMLCommonSetupEvent</code>.
 +
 
 +
{{Tip/Warning|If you choose to defer the registration, you will need to register the event within the synchronous work queue provided by <code>#enqueueWork</code> since <code>RecipeType#register</code> is not threadsafe.}}
 +
 
 +
=== RecipeSerializer ===
 +
 
 +
<code>RecipeSerializer</code> is an interface which takes some JSON and transforms it into a <code>Recipe</code>. It is also responsible for syncing the recipe data to the client. This is a forge wrapped registry object; however, since it is an interface, there is a bit more work needed for ease of implementation. This is because <code>RecipeSerializer</code> extends <code>IForgeRegistryEntry</code> which has three abstract methods. For ease of use, the implementation should extends <code>ForgeRegistryEntry<RecipeSerializer<?>></code> such that you do not need to implement these methods yourself.
 +
 
 +
There are only three methods within <code>RecipeSerializer</code>, but they all must be implemented:
 +
 
 +
{| class="wikitable"
 +
|+ Required Methods
 +
|-
 +
! Method !! Description
 +
|-
 +
| fromJson || This decodes a <code>Recipe</code> from JSON. The <code>ResourceLocation</code> id is separated from the JSON object as it represents the file name and not data within the file.
 +
|-
 +
| toNetwork || This encodes a <code>Recipe</code> on the server to send to the client. The data should be written to the <code>FriendlyByteBuf</code> except for the id as that has already been written.
 +
|-
 +
| fromNetwork || This decodes a <code>Recipe</code> on the client. Besides the id, the rest of the data can be read from the <code>FriendlyByteBuf</code> in order of insertion.
 +
|}
 +
 
 +
{{Tip|There are some helpful methods for deserializing some objects from JSON such as <code>Ingredient#fromJson</code> and <code>CraftingHelper#getItemStack</code>.}}
 +
 
 +
This can be registered like any other forge wrapped registry object. The registry name supplied will represent the <code>type</code> supplied in the recipe JSON.
 +
 
 +
Once that is done, you can create a recipe JSON within <code>data/<modid>/recipes/<path>.json</code> and set the <code>type</code> field, along with any other data, to that handled by your serializer.
 +
 
 +
== Data Generation ==
 +
 
 +
Custom recipes can also be generated by the <code>RecipeProvider</code> or as your own implementation. For simplicity purposes, this will be addressed as if one was creating a recipe within <code>RecipeProvider#buildCraftingRecipes</code>.
 +
 
 +
For a recipe to be consumed by the data provider, whatever builder that is implemented must result in a <code>FinishedRecipe</code>.
 +
 
 +
All finished recipes must implement the following methods:
 +
 
 +
{| class="wikitable"
 +
|+ Required Methods
 +
|-
 +
! Method !! Description
 +
|-
 +
| getId || This gets the id of the recipe. When serialized, the recipe will be saved to <code>data/<namespace>/recipes/<path>.json</code>.
 +
|-
 +
| getType || This gets the <code>RecipeSerializer</code> of the recipe. The registry name is set to the <code>type</code> field using this within <code>#serializeRecipe</code>.
 +
|-
 +
| serializeRecipeData || This encodes the data into the supplied JSON object. The data supplied should be the same as when needed to decode via the <code>RecipeSerializer</code>. The <code>type</code> field does not need to be specified as that is already set before this method is called.
 +
|-
 +
| getAdvancementId || This gets the advancement id of the recipe. This will be saved to <code>data/<namespace>/advancements/<path>.json</code>. Usually the path contains <code>recipes/</code> to represent it has a recipe and any other subdividing types like the <code>CreativeModeTab</code>. If unwanted, this should return <code>null</code>.
 +
|-
 +
| serializeAdvancement || This encodes the advancement data into a JSON. If working with an <code>Advancement$Builder</code>, this can be done simply by calling <code>Advancement$Builder#serializeToJson</code>. If unwanted, this should return <code>null</code>.
 +
|}
 +
 
 +
Once done, have the consumer accept the finished recipe.

Revision as of 21:26, 25 September 2021

Recipes are a core concept within the use of items. They allow you to transform some item into another through the use of crafting, smelting, smithing, etc. all of which can be defined in JSON through datapacks. However, the recipe system in its entirety is not only limited to what vanilla can provide. Any modder can create their own custom recipe implementation.

Creating a New Recipe

Creating a new recipe boils down to three things: Recipe, RecipeType, and RecipeSerializer.

For a general overview:

  • Recipe: The actual recipe implementation which handles the matching logic, construction logic, and the display data. All custom recipes need some implementation of this class.
  • RecipeType: The category the recipe belongs to. For example, a recipe placed in the #CRAFTING category is expected to work within the crafting table. A custom recipe category indicates that recipes of that type will be used in that particular context.
  • RecipeSerializer: The serializer which handles decoding the data from JSON and handling the data when syncing across the network. Each serializer can serialize to one Recipe implementation. The type field within any recipe JSON references the registry name of this serializer.

How one uses the recipe and its data is up to the implementing modder.

Warning

Creating a Recipe Book implementation will not be covered here. That is currently hardcoded to only accept vanilla categories.

Recipe

Recipe is an interface which holds data about the recipe being represented and how to test whether it is valid. It has a single parameterized type of some Container subtype. This is used to get a snapshot of the current inventory container, and as such should only be read from in this class. In most cases, this can just be left as Container.

The following methods must be implemented within your implementation:

Required Methods
Method Description
getId This method represents the unique name of the recipe. This usually refers to the name of the JSON file and is passed in while decoding.
getSerializer This method represents the serializer used to decode and send this recipe across the network.
getType This method represents the category the recipe is in.
canCraftInDimensions This takes in a width and height and checks whether the recipe can be created within those bounds. This is only used within the Recipe Book.
matches This method checks if the passed in inventory container can craft using the recipe stored in this class. Using the standard RecipeManager interface, this is always used to check whether the recipe can be used.
assemble This creates the resulting ItemStack to return to the player if the recipe has been crafted. This should always return a unique instance. If you are unsure whether yours does, call ItemStack#copy on the decoded result before returning.
getResultItem This shows the resulting item in displays like the Recipe Book. This does not need to return a unique instance and usually represents the decoded result directly.

Important

If you would like to use your own recipe checker because the inventory container does not provide you with enough data, you will need to reimplement the RecipeManager methods yourself.

There are some defaulted methods as well. Although optional, they will be covered as well:

Optional Methods
Method Description
getGroup This gets the group the recipe is currently associated with. This is used by the Recipe Book to group similar recipes into one entry to reduce cluttering. This would be passed in from the decoded JSON. By default, this returns an empty string.
getIngredients This gets the ingredients of the recipe. This is once again used by the Recipe Book to display the ingredients of the recipe. These are usually the decoded Ingredients from JSON. By default, this returns an empty list.
isSpecial This usually signals that the recipe cannot purely be represented in JSON as it has some dynamic metadata preventing it from being so. Special recipes will not appear in the Recipe Book for this reason as they provide a number of combinations which cannot be simply expressed. By default, this is false.
getToastSymbol This returns the symbol that appears when the recipe is seen in a toast, usually from unlocking via an advancement. This is not the actual recipe output itself, but it signifies what the recipe was made within. By default this returns a crafting table stack.
getRemainingItems This returns what items remain in the inventory container after the recipe has been created. By default, this will return a list of container items.
isIncomplete This returns whether the supplied ingredients is empty or if any Ingredient has no items to test against. This is another check by the Recipe Book to prevent impossible recipes from appearing.

When in practice, these will be decoded from a RecipeSerializer and stored in a map with a key of the RecipeType category it belongs to. The recipe itself is dynamically registered to the RecipeManager for each decoded instance.

RecipeType

RecipeType is another interface; however, it does not store any data itself. It is simply a non-forge wrapped registry object which represents the category the Recipe implementation belongs to. As such, it only needs to be supplied its registry name. This can be registered via RecipeType#register by either static initialization or deferring the registration until FMLCommonSetupEvent.

Warning

If you choose to defer the registration, you will need to register the event within the synchronous work queue provided by #enqueueWork since RecipeType#register is not threadsafe.

RecipeSerializer

RecipeSerializer is an interface which takes some JSON and transforms it into a Recipe. It is also responsible for syncing the recipe data to the client. This is a forge wrapped registry object; however, since it is an interface, there is a bit more work needed for ease of implementation. This is because RecipeSerializer extends IForgeRegistryEntry which has three abstract methods. For ease of use, the implementation should extends ForgeRegistryEntry<RecipeSerializer<?>> such that you do not need to implement these methods yourself.

There are only three methods within RecipeSerializer, but they all must be implemented:

Required Methods
Method Description
fromJson This decodes a Recipe from JSON. The ResourceLocation id is separated from the JSON object as it represents the file name and not data within the file.
toNetwork This encodes a Recipe on the server to send to the client. The data should be written to the FriendlyByteBuf except for the id as that has already been written.
fromNetwork This decodes a Recipe on the client. Besides the id, the rest of the data can be read from the FriendlyByteBuf in order of insertion.
There are some helpful methods for deserializing some objects from JSON such as Ingredient#fromJson and CraftingHelper#getItemStack.

This can be registered like any other forge wrapped registry object. The registry name supplied will represent the type supplied in the recipe JSON.

Once that is done, you can create a recipe JSON within data/<modid>/recipes/<path>.json and set the type field, along with any other data, to that handled by your serializer.

Data Generation

Custom recipes can also be generated by the RecipeProvider or as your own implementation. For simplicity purposes, this will be addressed as if one was creating a recipe within RecipeProvider#buildCraftingRecipes.

For a recipe to be consumed by the data provider, whatever builder that is implemented must result in a FinishedRecipe.

All finished recipes must implement the following methods:

Required Methods
Method Description
getId This gets the id of the recipe. When serialized, the recipe will be saved to data/<namespace>/recipes/<path>.json.
getType This gets the RecipeSerializer of the recipe. The registry name is set to the type field using this within #serializeRecipe.
serializeRecipeData This encodes the data into the supplied JSON object. The data supplied should be the same as when needed to decode via the RecipeSerializer. The type field does not need to be specified as that is already set before this method is called.
getAdvancementId This gets the advancement id of the recipe. This will be saved to data/<namespace>/advancements/<path>.json. Usually the path contains recipes/ to represent it has a recipe and any other subdividing types like the CreativeModeTab. If unwanted, this should return null.
serializeAdvancement This encodes the advancement data into a JSON. If working with an Advancement$Builder, this can be done simply by calling Advancement$Builder#serializeToJson. If unwanted, this should return null.

Once done, have the consumer accept the finished recipe.