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.
version | |
---|---|
OS | Windows10 |
Forge | 28.2.0 |
JDK | AdoptOpenJDK 8u242-b08 |
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
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.
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.
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,
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.
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);
}
}
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
}
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.
Recommended Posts