[JAVA] Minecraft1.14.4 Mémorandum de développement MOD 1 [Recette originale]

introduction

Quand j'ai commencé à développer MOD pour minecraft1.14.4, j'ai lutté avec le manque d'informations sur le développement, donc j'aimerais faire une note pour moi-même et (peut-être) aider ceux qui développeront MOD à l'avenir. Dans cet article, je vous expliquerai lors de la création d'un format de recette unique à MOD.

Environnement de développement

version
OS Windows10
Forge 28.2.0
JDK AdoptOpenJDK 8u242-b08

Environnement

Il y a déjà des articles écrits par d'autres personnes qui expliquent la construction de l'environnement, alors veuillez vous y référer.

↓ C'est un article auquel j'ai fait référence lors de la création de l'environnement. Création de Minecraft 1.14.4 Forge Mod Part 1 [Préparation de l'environnement de développement IntelliJ IDEA] TNT Modders: Construction de l'environnement

Recette originale

Ceci est nécessaire lorsque vous voulez faire quelque chose qui ne peut pas être réalisé avec une recette normale, comme le raffinage de deux articles en même temps pour obtenir un alliage.

procédure

  1. Implémentez l'interface IRecipe
  2. Implémentez un sérialiseur qui lit les recettes à partir de fichiers JSON
  3. Enregistrez le sérialiseur avec Forge

La procédure approximative est comme ça. Il est nécessaire d'exploiter des entités de tuiles etc., mais cette fois, nous expliquerons principalement la lecture et l'enregistrement des recettes.

la mise en oeuvre

Je ne pouvais penser à rien d'autre, donc mon objectif est d'enregistrer une recette comme celle que j'ai écrite ci-dessus.

Implémentons-le selon la procédure. Commencez par implémenter l'interface IRecipe . Le contenu est comme ça.

IRecipe.class


public interface IRecipe<C extends IInventory> {
    //L'article de l'inventaire correspond-il aux ingrédients de la recette?
    boolean matches(C var1, World var2);

    //Renvoie une copie du résultat de fabrication
    ItemStack getCraftingResult(C var1);

    //Je ne suis pas sûr de cela. Renvoie le vrai de base
    boolean canFit(int var1, int var2);

    //Renvoie le résultat de la fabrication
    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);
    }

    //Renvoie l'ID de la recette
    ResourceLocation getId();

    //Renvoie un sérialiseur qui lit et écrit le fichier JSON au format de recette à créer
    IRecipeSerializer<?> getSerializer();

    //Renvoie le type de recette
    IRecipeType<?> getType();
}

Puisqu'il est nécessaire d'implémenter autre que default,

  • boolean mathes(C var1, World var2);
  • ItemStack getCraftingResult(C var1);
  • boolean canFit(int var1, int var2);
  • ItemStack getRecipeOutput();
  • ResourceLocation getId();
  • IRecipeSerializer<?> getSerializer();
  • IRecipeType<?> getType();

Nous mettrons en œuvre les sept. Une fois implémenté, le code ressemblera à ceci.

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;
    }

    //L'article de l'inventaire correspond-il aux ingrédients de la recette?
    @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;
    }

    //Renvoie une copie du résultat de fabrication
    @Override
    public ItemStack getCraftingResult(IInventory inventory) {
        return this.result.copy();
    }

    //Je ne suis pas sûr de cela. Renvoie le vrai de base
    @Override
    public boolean canFit(int i, int i1) {
        return true;
    }

    //Renvoie le résultat de la fabrication
    @Override
    public ItemStack getRecipeOutput() {
        return this.result;
    }

    //Renvoie l'ID de la recette (modid:recipe_name)
    @Override
    public ResourceLocation getId() {
        return this.id;
    }

    //Renvoie le type de recette
    @Override
    public IRecipeType<?> getType() {
        return RECIPE_TYPE;
    }
}

À part matches () et canFit (), c'est une fonction getter qui renvoie le contenu de la recette, donc je ne pense pas que ce soit difficile.

mathes () utilise RecipeMatcher.findMatches () pour voir si les éléments de l'inventaire correspondent à la recette.

Je ne sais pas quand canFit () sera utilisé, mais pour autant que je puisse voir le code vanilla, je n'ai rien fait d'autre que de retourner true. Je ne sais pas non plus pourquoi il y a deux fonctions qui renvoient des résultats de craft. Cela fait également référence à la vanille et renvoie une copie du résultat.

Au fait, je n'ai pas implémenté getSerializer () car je n'ai pas encore implémenté le sérialiseur de retour. Ajoutez-le lorsque vous avez terminé d'implémenter le sérialiseur.

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);
        }

        //Pour la communication avec le serveur
        @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);
        }

        //Pour la communication avec le serveur
        @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);
        }
    }

Voici le code du sérialiseur. Il y a deux read (), mais la différence est que le premier provient de JSON et le second de paquets réseau.

Dans le constructeur, l'ID est attribué à ce sérialiseur. Cet ID sera le type que vous spécifiez lors de la définition de la recette dans le fichier JSON. Remplacez modId et serializer_name selon le cas.

readIngredients () est une fonction auto-conçue car JSONUtils n'a pas de fonction pour lire plusieurs ingrédients.


Mise en garde

Une chose à garder à l'esprit est l'ordre dans lequel ils sont lus. La lecture à partir de JSON n'a pas d'ordre, vous n'avez donc pas à vous en soucier. Cependant, si vous ne lisez pas le paquet dans le même ordre que vous l'avez écrit avec write (), vous obtiendrez une erreur dans la communication lors de la connexion au serveur. En effet, la lecture du paquet est effectuée dans l'ordre depuis le début. C'est bien pour jouer hors ligne (car il lit uniquement à partir de JSON), mais il ne rentrera pas sur le serveur qui a ce bogue. Ceci est fatal, alors faites très attention à la commande.


Maintenant, je voudrais mettre le sérialiseur implémenté dans 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;
    }

    //L'article de l'inventaire correspond-il aux ingrédients de la recette?
    @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;
    }

    //Renvoie une copie du résultat de fabrication
    @Override
    public ItemStack getCraftingResult(IInventory inventory) {
        return this.result.copy();
    }

    //Je ne suis pas sûr de cela. Renvoie le vrai de base
    @Override
    public boolean canFit(int i, int i1) {
        return true;
    }

    //Renvoie le résultat de la fabrication
    @Override
    public ItemStack getRecipeOutput() {
        return this.result;
    }

    //Renvoie l'ID de la recette (modid:recipe_name)
    @Override
    public ResourceLocation getId() {
        return this.id;
    }

    //Renvoie le type de recette
    @Override
    public IRecipeType<?> getType() {
        return RECIPE_TYPE;
    }

    //Renvoyer le sérialiseur
    @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);
        }
    }
}

Il s'agit de la forme finale de code qui hérite de l'interface IRecipe et crée vos propres recettes. Ajout de getSerializer () et de variables à ExampleRecipe.java.

Après cela, enregistrez le sérialiseur dans Forge et terminez.

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);
    }
}

Définir une recette

Enfin, définissez la recette dans le format de recette créé cette fois.

example.json


{
  "type": "modId:serializer_name",
  "ingredients": [
    {
       "item": "itemId"
    },
    {
       "item": "itemId"
    }
  ]
  "result": "itemId"
  "process_time": 100
}

en conclusion

Je ne sais pas comment créer une entité tuile, je vais donc terminer l'explication par un lien vers un site qui semble utile.

Exemple de mod publié par M. Cadiboo

↑ Ceci est un référentiel pour les tutoriels MOD créés par des étrangers. Bien qu'il soit en anglais, l'explication du code est écrite, alors jetez un œil.