Skip to content

CRÉATION DE RECETTES PERSONNALISÉES

Guide de développement complet pour Minecraft Forge 1.20.1

1. Introduction au système de recettes de la 1.20.1

Depuis les versions récentes de Minecraft, le système de recettes est entièrement piloté par les données (Data-driven) au format JSON. Dans Forge 1.20.1, ajouter une recette existante (comme une recette de fabrication sur table ou de cuisson au four) se fait uniquement via des fichiers JSON placés dans vos dossiers de ressources (src/main/resources/data/[modid]/recipes/).

Cependant, lorsque vous créez une machine personnalisée, un bloc spécifique ou un mécanisme inédit (par exemple, un broyeur ou une table d'infusion), vous devez définir un nouveau Recipe Type (Type de recette) et un Recipe Serializer (Sérialiseur de recette) en Java pour permettre à Minecraft de lire, comprendre et appliquer vos fichiers JSON personnalisés.


2. Utiliser les types de recettes Vanilla / Forge existants

Avant d'implémenter un système complexe en Java, sachez que vous pouvez utiliser les types de recettes natifs si votre objet se fabrique simplement. Il suffit de placer un fichier JSON dans votre dossier recipes.

Exemple : Recette en forme (Shaped Recipe) - JSON

json
{
  "type": "minecraft:crafting_shaped",
  "pattern": [
    "X",
    "XIX",
    "X"
  ],
  "key": {
    "X": { "item": "minecraft:diamond" },
    "I": { "item": "mymod:my_custom_item" }
  },
  "result": {
    "item": "mymod:my_advanced_block",
    "count": 1
  }
}

3. Création d'un système de recette personnalisé (Mod Recipes)

Pour créer une recette propre à votre mod (par exemple, une recette pour un "Broyeur" qui prend un ingrédient en entrée et produit un résultat après un certain temps), trois composants Java sont obligatoires:

  1. L'Interface / Classe de Recette (Recipe<Container>) : Définit les données logiques de la recette en mémoire (ingrédients, résultat, ID).

  2. Le Sérialiseur (RecipeSerializer) : Traduit le fichier JSON en objet Java (lecture) et gère la synchronisation réseau entre le serveur et le client.

  3. Le Type de Recette (RecipeType) : Un simple identifiant unique qui catégorise votre recette.

Étape A : Définir la classe de Recette

Voici l'implémentation standard d'une recette personnalisée prenant un ingrédient en entrée et fournissant un objet en sortie.

java
package com.example.mymod.recipe;

import com.example.mymod.MyMod;
import com.google.gson.JsonObject;
import net.minecraft.core.NonNullList;
import net.minecraft.core.RegistryAccess;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.GsonHelper;
import net.minecraft.world.Container;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.*;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.Nullable;

public class CrushingRecipe implements Recipe<Container> {
    private final ResourceLocation id;
    private final Ingredient input;
    private final ItemStack output;

    public CrushingRecipe(ResourceLocation id, Ingredient input, ItemStack output) {
        this.id = id;
        this.input = input;
        this.output = output;
    }

    @Override
    public boolean matches(Container container, Level level) {
        if (level.isClientSide()) return false;
        return input.test(container.getItem(0));
    }

    @Override
    public ItemStack assemble(Container container, RegistryAccess registryAccess) {
        return output.copy();
    }

    @Override
    public boolean canCraftInDimensions(int width, int height) {
        return true;
    }

    @Override
    public ItemStack getResultItem(RegistryAccess registryAccess) {
        return output;
    }

    @Override
    public ResourceLocation getId() {
        return id;
    }

    @Override
    public RecipeSerializer<?> getSerializer() {
        return ModRecipes.CRUSHING_SERIALIZER.get();
    }

    @Override
    public RecipeType<?> getType() {
        return Type.INSTANCE;
    }

    @Override
    public NonNullList<Ingredient> getIngredients() {
        NonNullList<Ingredient> ingredients = NonNullList.create();
        ingredients.add(this.input);
        return ingredients;
    }

    public static class Type implements RecipeType<CrushingRecipe> {
        public static final Type INSTANCE = new Type();
        public static final String ID = "crushing";
    }
}

Note sur la 1.20.1 : La méthode getResultItem() et assemble() prennent désormais un paramètre RegistryAccess requis pour gérer correctement les données dynamiques des registres (comme les enchantements ou les potions complexes).

Étape B : Créer le Sérialiseur de Recette (Serializer)

Le sérialiseur doit implémenter RecipeSerializer<T>. Il contient trois méthodes cruciales : fromJson (lecture du JSON data), fromNetwork (lecture des paquets réseau sur le client) et toNetwork (écriture des paquets depuis le serveur vers le client).

java
    public static class Serializer implements RecipeSerializer<CrushingRecipe> {
        public static final Serializer INSTANCE = new Serializer();
        public static final ResourceLocation ID = new ResourceLocation(MyMod.MODID, "crushing");

        @Override
        public CrushingRecipe fromJson(ResourceLocation recipeId, JsonObject json) {
            Ingredient input = Ingredient.fromJson(GsonHelper.getAsJsonObject(json, "ingredient"));
            ItemStack output = ShapedRecipe.itemStackFromJson(GsonHelper.getAsJsonObject(json, "result"));
            return new CrushingRecipe(recipeId, input, output);
        }

        @Override
        public @Nullable CrushingRecipe fromNetwork(ResourceLocation recipeId, FriendlyByteBuf buffer) {
            Ingredient input = Ingredient.fromNetwork(buffer);
            ItemStack output = buffer.readItem();
            return new CrushingRecipe(recipeId, input, output);
        }

        @Override
        public void toNetwork(FriendlyByteBuf buffer, CrushingRecipe recipe) {
            recipe.input.toNetwork(buffer);
            buffer.writeItem(recipe.output);
        }
    }

4. Enregistrement (Registration) auprès de Forge

Pour que Forge reconnaisse votre nouveau type de recette et son sérialiseur, vous devez utiliser le mécanisme de DeferredRegister. Créez une classe dédiée nommée ModRecipes:

java
package com.example.mymod.recipe;

import com.example.mymod.MyMod;
import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraft.world.item.crafting.RecipeType;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.registries.RegistryObject;

public class ModRecipes {
    public static final DeferredRegister<RecipeSerializer<?>> SERIALIZERS = 
        DeferredRegister.create(ForgeRegistries.RECIPE_SERIALIZERS, MyMod.MODID);
        
    public static final DeferredRegister<RecipeType<?>> RECIPE_TYPES = 
        DeferredRegister.create(ForgeRegistries.RECIPE_TYPES, MyMod.MODID);

    public static final RegistryObject<RecipeSerializer<CrushingRecipe>> CRUSHING_SERIALIZER = 
        SERIALIZERS.register("crushing", () -> CrushingRecipe.Serializer.INSTANCE);

    public static final RegistryObject<RecipeType<CrushingRecipe>> CRUSHING_TYPE = 
        RECIPE_TYPES.register("crushing", () -> CrushingRecipe.Type.INSTANCE);

    public static void register(IEventBus eventBus) {
        SERIALIZERS.register(eventBus);
        RECIPE_TYPES.register(eventBus);
    }
}

N'oubliez pas d'appeler ModRecipes.register(modEventBus); dans le constructeur principal de votre classe de Mod (@Mod(MyMod.MODID)).


5. Structure du fichier JSON personnalisé

Maintenant que le code Java est prêt, vous pouvez écrire vos recettes personnalisées en JSON. Créez un fichier à l'emplacement suivant:

src/main/resources/data/mymod/recipes/iron_dust_from_crushing.json

json
{
  "type": "mymod:crushing",
  "ingredient": {
    "item": "minecraft:iron_ingot"
  },
  "result": {
    "item": "mymod:iron_dust",
    "count": 2
  }
}

6. Utilisation de la recette dans une machine (BlockEntity)

Pour utiliser cette recette dans votre bloc/machine, vous devez chercher la recette correspondante dans le monde à l'aide du RecipeManager disponible via l'instance de la classe Level.

java
// Exemple de méthode de validation dans votre BlockEntity (méthode tick)
private static void craftItem(Level level, BlockPos pos, MyBlockEntity entity) {
    // Création d'un conteneur temporaire pour tester la recette
    SimpleContainer container = new SimpleContainer(entity.getContainerSize());
    container.setItem(0, entity.getItem(0)); // Slot d'entrée

    // Recherche de la recette correspondante
    Optional<CrushingRecipe> recipe = level.getRecipeManager()
        .getRecipeFor(CrushingRecipe.Type.INSTANCE, container, level);

    if (recipe.isPresent()) {
        ItemStack result = recipe.get().getResultItem(level.registryAccess());
        
        // Procéder au traitement (retirer l'ingrédient, insérer le résultat)
        entity.removeItem(0, 1);
        entity.setItem(1, new ItemStack(result.getItem(), entity.getItem(1).getCount() + result.getCount()));
    }
}

7. Génération Automatique des Recettes (Data Generation)

Pour éviter de créer manuellement des dizaines de fichiers JSON, Forge inclut un système de Data Generation. Vous pouvez étendre la classe RecipeProvider pour générer vos recettes personnalisées proprement.

java
package com.example.mymod.datagen;

import com.example.mymod.recipe.CrushingRecipe;
import net.minecraft.data.PackOutput;
import net.minecraft.data.recipes.FinishedRecipe;
import net.minecraft.data.recipes.RecipeProvider;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.crafting.Ingredient;
import java.util.function.Consumer;

public class ModRecipeProvider extends RecipeProvider {
    public ModRecipeProvider(PackOutput output) {
        super(output);
    }

    @Override
    protected void buildRecipes(Consumer<FinishedRecipe> writer) {
        // Exemple d'implémentation de génération de recette personnalisée
        // Vous devez généralement créer un builder dédié pour vos recettes spécifiques
    }
}

8. Résumé des points de vigilance en 1.20.1

ÉlémentVigilance particulière en 1.20.1
Registry AccessToujours passer le level.registryAccess() lors de la récupération du résultat (getResultItem).
FriendlyByteBufL'ordre d'écriture dans toNetwork doit être strictement identique à l'ordre de lecture dans fromNetwork.
Side SafetyAssurez-vous de n'effectuer les validations de recettes que du côté Serveur (!level.isClientSide()).