When I started developing mods for minecraft1.14.4, I struggled with the lack of information about development, so I would like to make a note for myself and (maybe) help those who will develop mods in the future. In this article, I will explain when creating a recipe format unique to MOD.
version | |
---|---|
OS | Windows10 |
Forge | 28.2.0 |
JDK | AdoptOpenJDK 8u242-b08 |
There is already an article that explains the environment construction written by another person, so please refer to that.
↓ This is an article that I referred to when building the environment. Creation of Minecraft 1.14.4 Forge Mod Part 1 [Preparation of development environment IntelliJ IDEA] TNT Modders: Environment Construction
This is necessary when you want to do something that cannot be achieved with a normal recipe, such as refining two items at the same time to obtain an alloy.
The rough procedure is like this. It is necessary to operate tile entities, etc., but this time we will mainly explain reading and registering recipes.
I couldn't think of anything else, so my goal is to register a recipe like the one I wrote above.
Let's implement it according to the procedure. First, implement the IRecipe interface. Contents is like this.
IRecipe.class
public interface IRecipe<C extends IInventory> {
//Do items in your inventory match recipe ingredients?
boolean matches(C var1, World var2);
//Returns a copy of the crafting result
ItemStack getCraftingResult(C var1);
//I'm not sure about this. Returns basic true
boolean canFit(int var1, int var2);
//Returns the result of crafting
ItemStack getRecipeOutput();
default NonNullList<ItemStack> getRemainingItems(C p_179532_1_) {
NonNullList<ItemStack> nonnulllist = NonNullList.withSize(p_179532_1_.getSizeInventory(), ItemStack.EMPTY);
for(int i = 0; i < nonnulllist.size(); ++i) {
ItemStack item = p_179532_1_.getStackInSlot(i);
if (item.hasContainerItem()) {
nonnulllist.set(i, item.getContainerItem());
}
}
return nonnulllist;
}
default NonNullList<Ingredient> getIngredients() {
return NonNullList.create();
}
default boolean isDynamic() {
return false;
}
default String getGroup() {
return "";
}
default ItemStack getIcon() {
return new ItemStack(Blocks.CRAFTING_TABLE);
}
//Returns the recipe ID
ResourceLocation getId();
//Returns a serializer that reads and writes the recipe format JSON file to be created
IRecipeSerializer<?> getSerializer();
//Returns the recipe type
IRecipeType<?> getType();
}
Since it is necessary to implement other than default,
We will implement the seven. When implemented, the code will look like this.
ExampleRecipe.java
import com.google.common.collect.Lists;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.item.crafting.IRecipe;
import net.minecraft.item.crafting.IRecipeSerializer;
import net.minecraft.item.crafting.IRecipeType;
import net.minecraft.item.crafting.Ingredient;
import net.minecraft.util.NonNullList;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.World;
import net.minecraftforge.common.util.RecipeMatcher;
import java.util.List;
public class ExampleRecipe implements IRecipe<IInventory> {
public static final IRecipeType<ExampleRecipe> RECIPE_TYPE = new IRecipeType<ExampleRecipe>() {
};
protected final ResourceLocation id;
protected NonNullList<Ingredient> ingredients;
protected ItemStack result;
protected int cookTime;
public ExampleRecipe(ResourceLocation id, NonNullList<Ingredient> ingredients, ItemStack result, int cookTime){
this.id = id;
this.ingredients = ingredients;
this.result = result;
this.cookTime = cookTime;
}
//Do items in your inventory match recipe ingredients?
@Override
public boolean matches(IInventory inventory, World world) {
List<ItemStack> inputs = Lists.newArrayList();
for(int index = 0; index < inventory.getSizeInventory(); ++index){
ItemStack input = inventory.getStackInSlot(index);
if(!input.isEmpty()){
inputs.add(input);
}
}
return RecipeMatcher.findMatches(inputs, this.ingredients) != null;
}
//Returns a copy of the crafting result
@Override
public ItemStack getCraftingResult(IInventory inventory) {
return this.result.copy();
}
//I'm not sure about this. Returns basic true
@Override
public boolean canFit(int i, int i1) {
return true;
}
//Returns the result of crafting
@Override
public ItemStack getRecipeOutput() {
return this.result;
}
//Returns the recipe ID (modid:recipe_name)
@Override
public ResourceLocation getId() {
return this.id;
}
//Returns the recipe type
@Override
public IRecipeType<?> getType() {
return RECIPE_TYPE;
}
}
Other than matches () and canFit (), it is a getter function that returns the contents of the recipe, so I don't think it's difficult.
mathes () uses RecipeMatcher.findMatches () to check if the items in the inventory match the recipe.
I don't know when canFit () will be used, but as far as I can see the vanilla code, I haven't done anything other than return true. I also don't know why there are two functions that return the crafting result. This also refers to vanilla and returns a copy of the result.
By the way, I haven't implemented getSerializer () because I haven't implemented the returning serializer yet. Add it when you have finished implementing the serializer.
import net.minecraft.item.crafting.IRecipeSerializer;
import net.minecraft.util.JSONUtils;
public static class Serializer extends ForgeRegistryEntry<IRecipeSerializer<?>> implements IRecipeSerializer<ExampleRecipe>{
public Serializer(){
this.setRegistryName(new ResourceLocation(modId, "serializer_name"));
}
private static NonNullList<Ingredient> readIngredients(JsonArray array){
NonNullList<Ingredient> ingredients = NonNullList.create();
for(JsonElement element: array){
ingredients.add(CraftingHelper.getIngredient(element));
}
if(ingredients.isEmpty()){
throw new JsonParseException("No ingredients for smelting recipe");
}
return ingredients;
}
@Override
public ExampleRecipe read(ResourceLocation recipeId, JsonObject jsonObject) {
ItemStack result = CraftingHelper.getItemStack(JSONUtils.getJsonObject(jsonObject, "result"), true);
NonNullList<Ingredient> ingredients = readIngredients(JSONUtils.getJsonArray(jsonObject, "ingredients"));
int cookTime = JSONUtils.getInt(jsonObject, "process_time", 50);
return new ExampleRecipe(recipeId, ingredients, result, cookTime);
}
//For communication with the server
@Nullable
@Override
public ExampleRecipe read(ResourceLocation recipeId, PacketBuffer packetBuffer) {
final int index = packetBuffer.readVarInt();
NonNullList<Ingredient> ingredients = NonNullList.withSize(index, Ingredient.EMPTY);
for(int col = 0; col < index; ++col){
ingredients.set(col, Ingredient.read(packetBuffer));
}
ItemStack result = packetBuffer.readItemStack();
int cookTime = packetBuffer.readVarInt();
return new ExampleRecipe(recipeId, ingredients, result, cookTime);
}
//For communication with the server
@Override
public void write(PacketBuffer packetBuffer, ExampleRecipe exampleRecipe) {
packetBuffer.writeVarInt(exampleRecipe.ingredients.size());
for (Ingredient ingredient : exampleRecipe.ingredients) {
ingredient.write(packetBuffer);
}
packetBuffer.writeItemStack(exampleRecipe.result);
packetBuffer.writeVarInt(exampleRecipe.cookTime);
}
}
Here is the serializer code. There are two read (), but the difference is that the first is from JSON and the second is from network packets.
In the constructor, the ID is assigned to this serializer. This ID will be the type you specify when defining the recipe in the JSON file. Replace modId and serializer_name as appropriate.
readIngredients () is a self-made function because JSONUtils does not have a function to read multiple Ingredients.
One thing to keep in mind is the order in which they are read. Reading from JSON has no order, so you don't have to worry about it. However, if you do not read from the packet in the same order as you wrote it with write (), you will get an error in the communication when logging in to the server. This is because the reading from the packet is performed in order from the beginning. It's fine for playing offline (because it only reads from JSON), but it won't fit on the server where this bug is occurring. This is fatal, so pay close attention to the order.
Now, I would like to put the implemented serializer into ExampleRecipe.java.
ExampleRecipe.java
import com.google.common.collect.Lists;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.item.crafting.IRecipe;
import net.minecraft.item.crafting.IRecipeSerializer;
import net.minecraft.item.crafting.IRecipeType;
import net.minecraft.item.crafting.Ingredient;
import net.minecraft.network.PacketBuffer;
import net.minecraft.util.JSONUtils;
import net.minecraft.util.NonNullList;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.World;
import net.minecraftforge.common.crafting.CraftingHelper;
import net.minecraftforge.common.util.RecipeMatcher;
import net.minecraftforge.registries.ForgeRegistryEntry;
import javax.annotation.Nullable;
import java.util.List;
public class ExampleRecipe implements IRecipe<IInventory> {
public static final IRecipeType<ExampleRecipe>
public static final Serializer SERIALIZER = new Serializer();
RECIPE_TYPE = new IRecipeType<ExampleRecipe>() {
};
protected final ResourceLocation id;
protected NonNullList<Ingredient> ingredients;
protected ItemStack result;
protected int cookTime;
public ExampleRecipe(ResourceLocation id, NonNullList<Ingredient> ingredients, ItemStack result, int cookTime){
this.id = id;
this.ingredients = ingredients;
this.result = result;
this.cookTime = cookTime;
}
//Do items in your inventory match recipe ingredients?
@Override
public boolean matches(IInventory inventory, World world) {
List<ItemStack> inputs = Lists.newArrayList();
for(int index = 0; index < inventory.getSizeInventory(); ++index){
ItemStack input = inventory.getStackInSlot(index);
if(!input.isEmpty()){
inputs.add(input);
}
}
return RecipeMatcher.findMatches(inputs, this.ingredients) != null;
}
//Returns a copy of the crafting result
@Override
public ItemStack getCraftingResult(IInventory inventory) {
return this.result.copy();
}
//I'm not sure about this. Returns basic true
@Override
public boolean canFit(int i, int i1) {
return true;
}
//Returns the result of crafting
@Override
public ItemStack getRecipeOutput() {
return this.result;
}
//Returns the recipe ID (modid:recipe_name)
@Override
public ResourceLocation getId() {
return this.id;
}
//Returns the recipe type
@Override
public IRecipeType<?> getType() {
return RECIPE_TYPE;
}
//Returns the serializer
@Override
public IRecipeSerializer<?> getSerializer() {
return SERIALIZER;
}
public static class Serializer extends ForgeRegistryEntry<IRecipeSerializer<?>> implements IRecipeSerializer<ExampleRecipe>{
public Serializer(){
this.setRegistryName(new ResourceLocation(modId, "serializer_name"));
}
private static NonNullList<Ingredient> readIngredients(JsonArray array){
NonNullList<Ingredient> ingredients = NonNullList.create();
for(JsonElement element: array){
ingredients.add(CraftingHelper.getIngredient(element));
}
if(ingredients.isEmpty()){
throw new JsonParseException("No ingredients for smelting recipe");
}
return ingredients;
}
@Override
public ExampleRecipe read(ResourceLocation recipeId, JsonObject jsonObject) {
ItemStack result = CraftingHelper.getItemStack(JSONUtils.getJsonObject(jsonObject, "result"), true);
NonNullList<Ingredient> ingredients = readIngredients(JSONUtils.getJsonArray(jsonObject, "ingredients"));
int cookTime = JSONUtils.getInt(jsonObject, "process_time", 50);
return new ExampleRecipe(recipeId, ingredients, result, cookTime);
}
@Nullable
@Override
public ExampleRecipe read(ResourceLocation recipeId, PacketBuffer packetBuffer) {
final int index = packetBuffer.readVarInt();
NonNullList<Ingredient> ingredients = NonNullList.withSize(index, Ingredient.EMPTY);
for(int col = 0; col < index; ++col){
ingredients.set(col, Ingredient.read(packetBuffer));
}
ItemStack result = packetBuffer.readItemStack();
int cookTime = packetBuffer.readVarInt();
return new ExampleRecipe(recipeId, ingredients, result, cookTime);
}
@Override
public void write(PacketBuffer packetBuffer, ExampleRecipe exampleRecipe) {
packetBuffer.writeVarInt(exampleRecipe.ingredients.size());
for (Ingredient ingredient : exampleRecipe.ingredients) {
ingredient.write(packetBuffer);
}
packetBuffer.writeItemStack(exampleRecipe.result);
packetBuffer.writeVarInt(exampleRecipe.cookTime);
}
}
}
This is the final form of code that inherits the IRecipe interface and creates your own recipes. Added getSerializer () and variables to ExampleRecipe.java.
After that, register the serializer with Forge and finish.
RegisterRecipeTypes.java
import net.minecraft.item.crafting.IRecipeSerializer;
import net.minecraftforge.event.RegistryEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
@Mod.EventBusSubscriber(modid = modId, bus = Mod.EventBusSubscriber.Bus.MOD)
public class RegisterRecipeTypes {
@SubscribeEvent
public static void registerRecipeSerializer(RegistryEvent.Register<IRecipeSerializer<?>> event){
event.getRegistry().register(ExampleRecipe.SERIALIZER);
}
}
Finally, define the recipe in the recipe format created this time.
example.json
{
"type": "modId:serializer_name",
"ingredients": [
{
"item": "itemId"
},
{
"item": "itemId"
}
]
"result": "itemId"
"process_time": 100
}
I don't know how to create a tile entity, so I will end the explanation with a link to a site that seems to be helpful.
Example Mod published by Mr. Cadiboo
↑ This is a repository for MOD tutorials created by foreigners. Although it is in English, the explanation of the code is written, so please take a look.
Recommended Posts